Merge pull request #11 from marhali/dev

Properly escape/unescape strings and sort properties files
This commit is contained in:
Marcel 2021-05-15 22:58:41 +02:00 committed by GitHub
commit 11fd99e7f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 148 additions and 13 deletions

View File

@ -3,6 +3,12 @@
# easy-i18n Changelog
## [Unreleased]
### Added
- Sorting for properties files
### Fixed
- Unexpected character escaping for json/properties files / issue #10
## [1.1.1]
### Added
- Support for IntelliJ 2021.1

View File

@ -3,7 +3,7 @@
pluginGroup = de.marhali.easyi18n
pluginName = easy-i18n
pluginVersion = 1.1.1
pluginVersion = 1.2.0
pluginSinceBuild = 202
pluginUntilBuild = 211.*
# Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl

View File

@ -27,6 +27,7 @@ import java.util.function.Consumer;
public class JsonTranslatorIO implements TranslatorIO {
private static final String FILE_EXTENSION = "json";
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
@Override
public void read(@NotNull Project project, @NotNull String directoryPath, @NotNull Consumer<Translations> callback) {
@ -53,8 +54,8 @@ public class JsonTranslatorIO implements TranslatorIO {
locales.add(file.getNameWithoutExtension());
JsonObject tree = JsonParser.parseReader(new InputStreamReader(file.getInputStream(),
file.getCharset())).getAsJsonObject();
JsonObject tree = GSON.fromJson(new InputStreamReader(file.getInputStream(),
file.getCharset()), JsonObject.class);
JsonUtil.readTree(file.getNameWithoutExtension(), tree, nodes);
}
@ -71,9 +72,6 @@ public class JsonTranslatorIO implements TranslatorIO {
@Override
public void save(@NotNull Project project, @NotNull Translations translations,
@NotNull String directoryPath, @NotNull Consumer<Boolean> callback) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
ApplicationManager.getApplication().runWriteAction(() -> {
try {
for(String locale : translations.getLocales()) {
@ -87,7 +85,7 @@ public class JsonTranslatorIO implements TranslatorIO {
VirtualFile vf = created ? LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)
: LocalFileSystem.getInstance().findFileByIoFile(file);
vf.setBinaryContent(gson.toJson(content).getBytes(vf.getCharset()));
vf.setBinaryContent(GSON.toJson(content).getBytes(vf.getCharset()));
}
// Successfully saved

View File

@ -9,8 +9,11 @@ import de.marhali.easyi18n.io.TranslatorIO;
import de.marhali.easyi18n.model.LocalizedNode;
import de.marhali.easyi18n.model.Translations;
import de.marhali.easyi18n.util.IOUtil;
import de.marhali.easyi18n.util.SortedProperties;
import de.marhali.easyi18n.util.StringUtil;
import de.marhali.easyi18n.util.TranslationsUtil;
import org.apache.commons.lang.StringEscapeUtils;
import org.jetbrains.annotations.NotNull;
import java.io.*;
@ -49,7 +52,7 @@ public class PropertiesTranslatorIO implements TranslatorIO {
}
locales.add(file.getNameWithoutExtension());
Properties properties = new Properties();
SortedProperties properties = new SortedProperties();
properties.load(new InputStreamReader(file.getInputStream(), file.getCharset()));
readProperties(file.getNameWithoutExtension(), properties, nodes);
}
@ -70,7 +73,7 @@ public class PropertiesTranslatorIO implements TranslatorIO {
ApplicationManager.getApplication().runWriteAction(() -> {
try {
for(String locale : translations.getLocales()) {
Properties properties = new Properties();
SortedProperties properties = new SortedProperties();
writeProperties(locale, properties, translations.getNodes(), "");
String fullPath = directoryPath + "/" + locale + "." + FILE_EXTENSION;
@ -95,7 +98,8 @@ public class PropertiesTranslatorIO implements TranslatorIO {
private void writeProperties(String locale, Properties props, LocalizedNode node, String parentPath) {
if(node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) {
if(node.getValue().get(locale) != null) { // Translation is defined - track it
props.setProperty(parentPath, node.getValue().get(locale));
String value = StringEscapeUtils.unescapeJava(node.getValue().get(locale));
props.setProperty(parentPath, value);
}
} else {
@ -124,7 +128,8 @@ public class PropertiesTranslatorIO implements TranslatorIO {
}
Map<String, String> messages = node.getValue();
messages.put(locale, String.valueOf(value));
String escapedValue = StringUtil.escapeControls(String.valueOf(value), true);
messages.put(locale, escapedValue);
node.setValue(messages);
});
}

View File

@ -6,6 +6,8 @@ import com.google.gson.JsonPrimitive;
import de.marhali.easyi18n.model.LocalizedNode;
import org.apache.commons.lang.StringEscapeUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@ -25,7 +27,8 @@ public class JsonUtil {
public static void writeTree(String locale, JsonObject parent, LocalizedNode node) {
if(node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) {
if(node.getValue().get(locale) != null) {
parent.add(node.getKey(), new JsonPrimitive(node.getValue().get(locale)));
String value = StringEscapeUtils.unescapeJava(node.getValue().get(locale));
parent.add(node.getKey(), new JsonPrimitive(value));
}
} else {
@ -75,7 +78,8 @@ public class JsonUtil {
}
Map<String, String> messages = leafNode.getValue();
messages.put(locale, entry.getValue().getAsString());
String value = StringUtil.escapeControls(entry.getValue().getAsString(), true);
messages.put(locale, value);
leafNode.setValue(messages);
}
}

View File

@ -0,0 +1,31 @@
package de.marhali.easyi18n.util;
import java.util.*;
/**
* Applies sorting to {@link Properties} files.
* @author marhali
*/
public class SortedProperties extends Properties {
@Override
public Set<Object> keySet() {
return Collections.unmodifiableSet(new TreeSet<>(super.keySet()));
}
@Override
public Set<Map.Entry<Object, Object>> entrySet() {
TreeMap<Object, Object> sorted = new TreeMap<>();
for(Object key : super.keySet()) {
sorted.put(key, get(key));
}
return sorted.entrySet();
}
@Override
public synchronized Enumeration<Object> keys() {
return Collections.enumeration(new TreeSet<>(super.keySet()));
}
}

View File

@ -0,0 +1,91 @@
package de.marhali.easyi18n.util;
import org.jetbrains.annotations.NotNull;
import java.io.StringWriter;
/**
* String utilities
* @author marhali, Apache Commons
*/
public class StringUtil {
/**
* Escapes control characters for the given input string.
* Inspired by Apache Commons (see {@link org.apache.commons.lang.StringEscapeUtils}
* @param input The input string
* @param skipStrings Should every string literal indication ("", '') be skipped? (Needed e.g. for json)
* @return Escaped string
*/
public static @NotNull String escapeControls(@NotNull String input, boolean skipStrings) {
int length = input.length();
StringWriter out = new StringWriter(length * 2);
for(int i = 0; i < length; i++) {
char ch = input.charAt(i);
if(ch < ' ') {
switch(ch) {
case '\b':
out.write(92);
out.write(98);
break;
case '\t':
out.write(92);
out.write(116);
break;
case '\n':
out.write(92);
out.write(110);
break;
case '\u000b':
default:
if (ch > 15) {
out.write("\\u00" + hex(ch));
} else {
out.write("\\u000" + hex(ch));
}
break;
case '\f':
out.write(92);
out.write(102);
break;
case '\r':
out.write(92);
out.write(114);
}
} else {
switch(ch) {
case '"':
if(!skipStrings) {
out.write(92);
}
out.write(34);
break;
case '\'':
if(!skipStrings) {
out.write(92);
}
out.write(39);
break;
case '/':
out.write(92);
out.write(47);
break;
case '\\':
out.write(92);
out.write(92);
break;
default:
out.write(ch);
}
}
}
return out.toString();
}
private static @NotNull String hex(char ch) {
return Integer.toHexString(ch).toUpperCase();
}
}