Properly escape/unescape strings and sort properties files
For more information see issue #10
This commit is contained in:
parent
3323d05c28
commit
31bd21bb06
@ -27,6 +27,7 @@ import java.util.function.Consumer;
|
|||||||
public class JsonTranslatorIO implements TranslatorIO {
|
public class JsonTranslatorIO implements TranslatorIO {
|
||||||
|
|
||||||
private static final String FILE_EXTENSION = "json";
|
private static final String FILE_EXTENSION = "json";
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(@NotNull Project project, @NotNull String directoryPath, @NotNull Consumer<Translations> callback) {
|
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());
|
locales.add(file.getNameWithoutExtension());
|
||||||
|
|
||||||
JsonObject tree = JsonParser.parseReader(new InputStreamReader(file.getInputStream(),
|
JsonObject tree = GSON.fromJson(new InputStreamReader(file.getInputStream(),
|
||||||
file.getCharset())).getAsJsonObject();
|
file.getCharset()), JsonObject.class);
|
||||||
|
|
||||||
JsonUtil.readTree(file.getNameWithoutExtension(), tree, nodes);
|
JsonUtil.readTree(file.getNameWithoutExtension(), tree, nodes);
|
||||||
}
|
}
|
||||||
@ -71,9 +72,6 @@ public class JsonTranslatorIO implements TranslatorIO {
|
|||||||
@Override
|
@Override
|
||||||
public void save(@NotNull Project project, @NotNull Translations translations,
|
public void save(@NotNull Project project, @NotNull Translations translations,
|
||||||
@NotNull String directoryPath, @NotNull Consumer<Boolean> callback) {
|
@NotNull String directoryPath, @NotNull Consumer<Boolean> callback) {
|
||||||
|
|
||||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
|
||||||
|
|
||||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||||
try {
|
try {
|
||||||
for(String locale : translations.getLocales()) {
|
for(String locale : translations.getLocales()) {
|
||||||
@ -87,7 +85,7 @@ public class JsonTranslatorIO implements TranslatorIO {
|
|||||||
VirtualFile vf = created ? LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)
|
VirtualFile vf = created ? LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)
|
||||||
: LocalFileSystem.getInstance().findFileByIoFile(file);
|
: LocalFileSystem.getInstance().findFileByIoFile(file);
|
||||||
|
|
||||||
vf.setBinaryContent(gson.toJson(content).getBytes(vf.getCharset()));
|
vf.setBinaryContent(GSON.toJson(content).getBytes(vf.getCharset()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Successfully saved
|
// Successfully saved
|
||||||
|
@ -9,8 +9,11 @@ import de.marhali.easyi18n.io.TranslatorIO;
|
|||||||
import de.marhali.easyi18n.model.LocalizedNode;
|
import de.marhali.easyi18n.model.LocalizedNode;
|
||||||
import de.marhali.easyi18n.model.Translations;
|
import de.marhali.easyi18n.model.Translations;
|
||||||
import de.marhali.easyi18n.util.IOUtil;
|
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 de.marhali.easyi18n.util.TranslationsUtil;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringEscapeUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -49,7 +52,7 @@ public class PropertiesTranslatorIO implements TranslatorIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
locales.add(file.getNameWithoutExtension());
|
locales.add(file.getNameWithoutExtension());
|
||||||
Properties properties = new Properties();
|
SortedProperties properties = new SortedProperties();
|
||||||
properties.load(new InputStreamReader(file.getInputStream(), file.getCharset()));
|
properties.load(new InputStreamReader(file.getInputStream(), file.getCharset()));
|
||||||
readProperties(file.getNameWithoutExtension(), properties, nodes);
|
readProperties(file.getNameWithoutExtension(), properties, nodes);
|
||||||
}
|
}
|
||||||
@ -70,7 +73,7 @@ public class PropertiesTranslatorIO implements TranslatorIO {
|
|||||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||||
try {
|
try {
|
||||||
for(String locale : translations.getLocales()) {
|
for(String locale : translations.getLocales()) {
|
||||||
Properties properties = new Properties();
|
SortedProperties properties = new SortedProperties();
|
||||||
writeProperties(locale, properties, translations.getNodes(), "");
|
writeProperties(locale, properties, translations.getNodes(), "");
|
||||||
|
|
||||||
String fullPath = directoryPath + "/" + locale + "." + FILE_EXTENSION;
|
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) {
|
private void writeProperties(String locale, Properties props, LocalizedNode node, String parentPath) {
|
||||||
if(node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) {
|
if(node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) {
|
||||||
if(node.getValue().get(locale) != null) { // Translation is defined - track it
|
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 {
|
} else {
|
||||||
@ -124,7 +128,8 @@ public class PropertiesTranslatorIO implements TranslatorIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> messages = node.getValue();
|
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);
|
node.setValue(messages);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import com.google.gson.JsonPrimitive;
|
|||||||
|
|
||||||
import de.marhali.easyi18n.model.LocalizedNode;
|
import de.marhali.easyi18n.model.LocalizedNode;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringEscapeUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -25,7 +27,8 @@ public class JsonUtil {
|
|||||||
public static void writeTree(String locale, JsonObject parent, LocalizedNode node) {
|
public static void writeTree(String locale, JsonObject parent, LocalizedNode node) {
|
||||||
if(node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) {
|
if(node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) {
|
||||||
if(node.getValue().get(locale) != null) {
|
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 {
|
} else {
|
||||||
@ -75,7 +78,8 @@ public class JsonUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> messages = leafNode.getValue();
|
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);
|
leafNode.setValue(messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
src/main/java/de/marhali/easyi18n/util/SortedProperties.java
Normal file
31
src/main/java/de/marhali/easyi18n/util/SortedProperties.java
Normal 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()));
|
||||||
|
}
|
||||||
|
}
|
91
src/main/java/de/marhali/easyi18n/util/StringUtil.java
Normal file
91
src/main/java/de/marhali/easyi18n/util/StringUtil.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user