Skip to content

Commit

Permalink
Merge pull request #90 from marhali/feat/json5
Browse files Browse the repository at this point in the history
Feat/json5
  • Loading branch information
marhali authored Feb 23, 2022
2 parents 36e5d5f + 386d345 commit 0ef78e3
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 7 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
# easy-i18n Changelog

## [Unreleased]
### Added
- Support for Json5 files

## [3.0.1]
### Changed
- Fresh projects will receive a notification instead of an exception to configure the plugin
### Changed
- Fresh projects will receive a notification instead of an exception to configure the plugin

### Fixed
### Fixed
- Exception on json array value mapping

## [3.0.0]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ This plugin can be used for any project based on one of the formats and structur

## Builtin Support
### File Types
**<kbd>JSON</kbd>** - **<kbd>YAML</kbd>** - **<kbd>Properties</kbd>**
**<kbd>JSON</kbd>** - **<kbd>JSON5</kbd>** - **<kbd>YAML</kbd>** - **<kbd>Properties</kbd>**

### Folder Structure
- Single Directory: All translation files are within one directory
Expand Down Expand Up @@ -90,7 +90,7 @@ _For more examples, please refer to the [Examples Directory](https://github.com/
<!-- ROADMAP -->
## Roadmap

- [ ] JSON5 Support
- [X] JSON5 Support
- [ ] XML Support
- [ ] Mark duplicate translation values

Expand Down
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ repositories {
mavenCentral()
}

dependencies {
implementation("de.marhali:json5-java:2.0.0")
}

// Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin
intellij {
pluginName.set(properties("pluginName"))
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pluginGroup = de.marhali.easyi18n
pluginName = easy-i18n
# SemVer format -> https://semver.org
pluginVersion = 3.0.1
pluginVersion = 3.1.0

# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.marhali.easyi18n.io.parser;

import de.marhali.easyi18n.io.parser.json.JsonParserStrategy;
import de.marhali.easyi18n.io.parser.json5.Json5ParserStrategy;
import de.marhali.easyi18n.io.parser.properties.PropertiesParserStrategy;
import de.marhali.easyi18n.io.parser.yaml.YamlParserStrategy;

Expand All @@ -10,6 +11,7 @@
*/
public enum ParserStrategyType {
JSON(JsonParserStrategy.class),
JSON5(Json5ParserStrategy.class),
YAML(YamlParserStrategy.class),
YML(YamlParserStrategy.class),
PROPERTIES(PropertiesParserStrategy.class),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package de.marhali.easyi18n.io.parser.json5;

import de.marhali.easyi18n.io.parser.ArrayMapper;
import de.marhali.easyi18n.util.StringUtil;
import de.marhali.json5.Json5;
import de.marhali.json5.Json5Array;
import de.marhali.json5.Json5Primitive;

import org.apache.commons.lang.math.NumberUtils;

import java.io.IOException;

/**
* Map json5 array values.
* @author marhali
*/
public class Json5ArrayMapper extends ArrayMapper {

private static final Json5 JSON5 = Json5.builder(builder ->
builder.allowInvalidSurrogate().quoteSingle().indentFactor(0).build());

public static String read(Json5Array array) {
return read(array.iterator(), (jsonElement -> {
try {
return jsonElement.isJson5Array() || jsonElement.isJson5Object()
? "\\" + JSON5.serialize(jsonElement)
: jsonElement.getAsString();
} catch (IOException e) {
throw new AssertionError(e.getMessage(), e.getCause());
}
}));
}

public static Json5Array write(String concat) {
Json5Array array = new Json5Array();

write(concat, (element) -> {
if(element.startsWith("\\")) {
array.add(JSON5.parse(element.replace("\\", "")));
} else {
if(StringUtil.isHexString(element)) {
array.add(Json5Primitive.of(element, true));
} else if(NumberUtils.isNumber(element)) {
array.add(Json5Primitive.of(NumberUtils.createNumber(element)));
} else {
array.add(Json5Primitive.of(element));
}
}
});

return array;
}
}
73 changes: 73 additions & 0 deletions src/main/java/de/marhali/easyi18n/io/parser/json5/Json5Mapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package de.marhali.easyi18n.io.parser.json5;

import de.marhali.easyi18n.model.Translation;
import de.marhali.easyi18n.model.TranslationNode;
import de.marhali.easyi18n.util.StringUtil;

import de.marhali.json5.Json5Element;
import de.marhali.json5.Json5Object;
import de.marhali.json5.Json5Primitive;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.math.NumberUtils;

import java.util.Map;

/**
* Mapper for mapping json5 objects into translation nodes and backwards.
* @author marhali
*/
public class Json5Mapper {
public static void read(String locale, Json5Object json, TranslationNode node) {
for(Map.Entry<String, Json5Element> entry : json.entrySet()) {
String key = entry.getKey();
Json5Element value = entry.getValue();

TranslationNode childNode = node.getOrCreateChildren(key);

if(value.isJson5Object()) {
// Nested element - run recursively
read(locale, value.getAsJson5Object(), childNode);
} else {
Translation translation = childNode.getValue();

String content = value.isJson5Array()
? Json5ArrayMapper.read(value.getAsJson5Array())
: StringUtil.escapeControls(value.getAsString(), true);

translation.put(locale, content);
childNode.setValue(translation);
}
}
}

public static void write(String locale, Json5Object json, TranslationNode node) {
for(Map.Entry<String, TranslationNode> entry : node.getChildren().entrySet()) {
String key = entry.getKey();
TranslationNode childNode = entry.getValue();

if(!childNode.isLeaf()) {
// Nested node - run recursively
Json5Object childJson = new Json5Object();
write(locale, childJson, childNode);
if(childJson.size() > 0) {
json.add(key, childJson);
}

} else {
Translation translation = childNode.getValue();
String content = translation.get(locale);
if(content != null) {
if(Json5ArrayMapper.isArray(content)) {
json.add(key, Json5ArrayMapper.write(content));
} else if(StringUtil.isHexString(content)) {
json.add(key, Json5Primitive.of(content, true));
} else if(NumberUtils.isNumber(content)) {
json.add(key, Json5Primitive.of(NumberUtils.createNumber(content)));
} else {
json.add(key, Json5Primitive.of(StringEscapeUtils.unescapeJava(content)));
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package de.marhali.easyi18n.io.parser.json5;

import com.intellij.openapi.vfs.VirtualFile;

import de.marhali.easyi18n.io.parser.ParserStrategy;
import de.marhali.easyi18n.model.SettingsState;
import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.TranslationFile;
import de.marhali.easyi18n.model.TranslationNode;
import de.marhali.json5.Json5;
import de.marhali.json5.Json5Element;
import de.marhali.json5.Json5Object;

import org.jetbrains.annotations.NotNull;

import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Objects;

/**
* Json5 file format parser strategy
* @author marhali
*/
public class Json5ParserStrategy extends ParserStrategy {

private static final Json5 JSON5 = Json5.builder(builder ->
builder.allowInvalidSurrogate().trailingComma().indentFactor(4).build());

public Json5ParserStrategy(@NotNull SettingsState settings) {
super(settings);
}

@Override
public void read(@NotNull TranslationFile file, @NotNull TranslationData data) throws Exception {
data.addLocale(file.getLocale());

VirtualFile vf = file.getVirtualFile();
TranslationNode targetNode = super.getOrCreateTargetNode(file, data);

try (Reader reader = new InputStreamReader(vf.getInputStream(), vf.getCharset())) {
Json5Element input = JSON5.parse(reader);
if(input != null && input.isJson5Object()) {
Json5Mapper.read(file.getLocale(), input.getAsJson5Object(), targetNode);
}
}
}

@Override
public void write(@NotNull TranslationData data, @NotNull TranslationFile file) throws Exception {
TranslationNode targetNode = super.getTargetNode(data, file);

Json5Object output = new Json5Object();
Json5Mapper.write(file.getLocale(), output, Objects.requireNonNull(targetNode));

VirtualFile vf = file.getVirtualFile();
vf.setBinaryContent(JSON5.serialize(output).getBytes(vf.getCharset()));
}
}
12 changes: 12 additions & 0 deletions src/main/java/de/marhali/easyi18n/util/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,25 @@
import org.jetbrains.annotations.NotNull;

import java.io.StringWriter;
import java.util.regex.Pattern;

/**
* String utilities
* @author marhali, Apache Commons
*/
public class StringUtil {

/**
* Checks if the provided String represents a hexadecimal number.
* For example: {@code 0x100...}, {@code -0x100...} and {@code +0x100...}.
* @param string String to evaluate
* @return true if hexadecimal string otherwise false
*/
public static boolean isHexString(@NotNull String string) {
final Pattern hexNumberPattern = Pattern.compile("[+-]?0[xX][0-9a-fA-F]+");
return hexNumberPattern.matcher(string).matches();
}

/**
* Escapes control characters for the given input string.
* Inspired by Apache Commons (see {@link org.apache.commons.lang.StringEscapeUtils}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ settings.path.text=Locales directory
settings.strategy.title=Translation file structure
settings.strategy.folder=Single Directory;Modularized: Locale / Namespace;Modularized: Namespace / Locale
settings.strategy.folder.tooltip=What is the folder structure of your translation files?
settings.strategy.parser=JSON;YAML;YML;Properties;ARB
settings.strategy.parser=JSON;JSON5;YAML;YML;Properties;ARB
settings.strategy.parser.tooltip=Which file parser should be used to process your translation files?
settings.strategy.file-pattern.tooltip=Defines a wildcard matcher to filter relevant translation files. For example *.json, *.???.
settings.preview=Preview locale
Expand Down
Loading

0 comments on commit 0ef78e3

Please sign in to comment.