Merge pull request #7 from marhali/dev

Fix Issues // Add Features

### Added
- Filter option for translation files via regex / issue #5
- Support for splitted / modularized json files per locale / issue #4
- Basic translation key completion inside editor
- I18n key annotation inside editor

### Changed
- Tree view will be expanded if search function has been used

### Fixed
- Encoding for properties files / issue #6
This commit is contained in:
Marcel 2021-04-25 22:26:46 +02:00 committed by GitHub
commit a5b291177b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 458 additions and 77 deletions

View File

@ -4,6 +4,19 @@
## [Unreleased]
## [1.1.0] - 2021-04-25
### Added
- Filter option for translation files via regex / issue #5
- Support for splitted / modularized json files per locale / issue #4
- Basic translation key completion inside editor
- I18n key annotation inside editor
### Changed
- Tree view will be expanded if search function has been used
### Fixed
- Encoding for properties files / issue #6
## [1.0.1] - 2021-03-16
### Changed
- Modified plugin icon to meet IntelliJ guidelines

View File

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

View File

@ -1,5 +1,7 @@
package de.marhali.easyi18n.io;
import com.intellij.openapi.project.Project;
import de.marhali.easyi18n.model.Translations;
import org.jetbrains.annotations.NotNull;
@ -15,16 +17,19 @@ public interface TranslatorIO {
/**
* Reads localized messages from the persistence layer.
* @param project Opened intellij project
* @param directoryPath The full path for the directory which holds all locale files
* @param callback Contains loaded translations. Will be called after io operation. Content might be null on failure.
*/
void read(@NotNull String directoryPath, @NotNull Consumer<Translations> callback);
void read(@NotNull Project project, @NotNull String directoryPath, @NotNull Consumer<Translations> callback);
/**
* Writes the provided messages (translations) to the persistence layer.
* @param project Opened intellij project
* @param translations Translations instance to save
* @param directoryPath The full path for the directory which holds all locale files
* @param callback Will be called after io operation. Can be used to determine if action was successful(true) or not
*/
void save(@NotNull Translations translations, @NotNull String directoryPath, @NotNull Consumer<Boolean> callback);
void save(@NotNull Project project, @NotNull Translations translations,
@NotNull String directoryPath, @NotNull Consumer<Boolean> callback);
}

View File

@ -2,12 +2,15 @@ package de.marhali.easyi18n.io.implementation;
import com.google.gson.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
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.JsonUtil;
import org.jetbrains.annotations.NotNull;
@ -26,7 +29,7 @@ public class JsonTranslatorIO implements TranslatorIO {
private static final String FILE_EXTENSION = "json";
@Override
public void read(@NotNull String directoryPath, @NotNull Consumer<Translations> callback) {
public void read(@NotNull Project project, @NotNull String directoryPath, @NotNull Consumer<Translations> callback) {
ApplicationManager.getApplication().saveAll(); // Save opened files (required if new locales were added)
ApplicationManager.getApplication().runReadAction(() -> {
@ -43,9 +46,17 @@ public class JsonTranslatorIO implements TranslatorIO {
try {
for(VirtualFile file : files) {
if(!IOUtil.isFileRelevant(project, file)) { // File does not matches pattern
continue;
}
locales.add(file.getNameWithoutExtension());
JsonObject tree = JsonParser.parseReader(new InputStreamReader(file.getInputStream(), file.getCharset())).getAsJsonObject();
readTree(file.getNameWithoutExtension(), tree, nodes);
JsonObject tree = JsonParser.parseReader(new InputStreamReader(file.getInputStream(),
file.getCharset())).getAsJsonObject();
JsonUtil.readTree(file.getNameWithoutExtension(), tree, nodes);
}
callback.accept(new Translations(locales, nodes));
@ -58,20 +69,25 @@ public class JsonTranslatorIO implements TranslatorIO {
}
@Override
public void save(@NotNull Translations translations, @NotNull String directoryPath, @NotNull Consumer<Boolean> callback) {
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()) {
JsonObject content = new JsonObject();
writeTree(locale, content, translations.getNodes());
//JsonElement content = writeTree(locale, new JsonObject(), translations.getNodes());
JsonUtil.writeTree(locale, content, translations.getNodes());
String fullPath = directoryPath + "/" + locale + "." + FILE_EXTENSION;
VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(fullPath));
File file = new File(fullPath);
boolean created = file.createNewFile();
file.setBinaryContent(gson.toJson(content).getBytes(file.getCharset()));
VirtualFile vf = created ? LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)
: LocalFileSystem.getInstance().findFileByIoFile(file);
vf.setBinaryContent(gson.toJson(content).getBytes(vf.getCharset()));
}
// Successfully saved
@ -83,57 +99,4 @@ public class JsonTranslatorIO implements TranslatorIO {
}
});
}
private 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)));
}
} else {
for(LocalizedNode children : node.getChildren()) {
if(children.isLeaf()) {
writeTree(locale, parent, children);
} else {
JsonObject childrenJson = new JsonObject();
writeTree(locale, childrenJson, children);
if(childrenJson.size() > 0) {
parent.add(children.getKey(), childrenJson);
}
}
}
}
}
private void readTree(String locale, JsonObject json, LocalizedNode data) {
for(Map.Entry<String, JsonElement> entry : json.entrySet()) {
String key = entry.getKey();
try {
// Try to go one level deeper
JsonObject childObject = entry.getValue().getAsJsonObject();
LocalizedNode childrenNode = data.getChildren(key);
if(childrenNode == null) {
childrenNode = new LocalizedNode(key, new ArrayList<>());
data.addChildren(childrenNode);
}
readTree(locale, childObject, childrenNode);
} catch(IllegalStateException e) { // Reached end for this node
LocalizedNode leafNode = data.getChildren(key);
if(leafNode == null) {
leafNode = new LocalizedNode(key, new HashMap<>());
data.addChildren(leafNode);
}
Map<String, String> messages = leafNode.getValue();
messages.put(locale, entry.getValue().getAsString());
leafNode.setValue(messages);
}
}
}
}

View File

@ -0,0 +1,121 @@
package de.marhali.easyi18n.io.implementation;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
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.JsonUtil;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* IO operations for splitted / modularized json files. Each locale can have multiple translation files.
* @author marhali
*/
public class ModularizedJsonTranslatorIO implements TranslatorIO {
private static final String FILE_EXTENSION = "json";
@Override
public void read(@NotNull Project project, @NotNull String directoryPath, @NotNull Consumer<Translations> callback) {
ApplicationManager.getApplication().saveAll(); // Save opened files (required if new locales were added)
ApplicationManager.getApplication().runReadAction(() -> {
VirtualFile directory = LocalFileSystem.getInstance().findFileByIoFile(new File(directoryPath));
if(directory == null || directory.getChildren() == null) {
throw new IllegalArgumentException("Specified folder is invalid (" + directoryPath + ")");
}
VirtualFile[] localeDirectories = directory.getChildren();
List<String> locales = new ArrayList<>();
LocalizedNode nodes = new LocalizedNode(LocalizedNode.ROOT_KEY, new ArrayList<>());
try {
for(VirtualFile localeDir : localeDirectories) {
String locale = localeDir.getName();
locales.add(locale);
// Read all json modules
for(VirtualFile module : localeDir.getChildren()) {
if(!IOUtil.isFileRelevant(project, module)) { // File does not matches pattern
continue;
}
JsonObject tree = JsonParser.parseReader(new InputStreamReader(module.getInputStream(),
module.getCharset())).getAsJsonObject();
String moduleName = module.getNameWithoutExtension();
LocalizedNode moduleNode = nodes.getChildren(moduleName);
if(moduleNode == null) { // Create module / sub node
moduleNode = new LocalizedNode(moduleName, new ArrayList<>());
nodes.addChildren(moduleNode);
}
JsonUtil.readTree(locale, tree, moduleNode);
}
}
callback.accept(new Translations(locales, nodes));
} catch(IOException e) {
e.printStackTrace();
callback.accept(null);
}
});
}
@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()) {
// Use top level children as modules
for (LocalizedNode module : translations.getNodes().getChildren()) {
JsonObject content = new JsonObject();
JsonUtil.writeTree(locale, content, module);
String fullPath = directoryPath + "/" + locale + "/" + module.getKey() + "." + FILE_EXTENSION;
File file = new File(fullPath);
boolean created = file.createNewFile();
VirtualFile vf = created ? LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)
: LocalFileSystem.getInstance().findFileByIoFile(file);
vf.setBinaryContent(gson.toJson(content).getBytes(vf.getCharset()));
}
}
// Successfully saved
callback.accept(true);
} catch(IOException e) {
e.printStackTrace();
callback.accept(false);
}
});
}
}

View File

@ -1,20 +1,19 @@
package de.marhali.easyi18n.io.implementation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
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.TranslationsUtil;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.*;
import java.util.*;
import java.util.function.Consumer;
@ -27,7 +26,7 @@ public class PropertiesTranslatorIO implements TranslatorIO {
public static final String FILE_EXTENSION = "properties";
@Override
public void read(@NotNull String directoryPath, @NotNull Consumer<Translations> callback) {
public void read(@NotNull Project project, @NotNull String directoryPath, @NotNull Consumer<Translations> callback) {
ApplicationManager.getApplication().saveAll(); // Save opened files (required if new locales were added)
ApplicationManager.getApplication().runReadAction(() -> {
@ -44,9 +43,14 @@ public class PropertiesTranslatorIO implements TranslatorIO {
try {
for (VirtualFile file : files) {
if(!IOUtil.isFileRelevant(project, file)) { // File does not matches pattern
continue;
}
locales.add(file.getNameWithoutExtension());
Properties properties = new Properties();
properties.load(new InputStreamReader(file.getInputStream(), file.getCharset()));;
properties.load(new InputStreamReader(file.getInputStream(), file.getCharset()));
readProperties(file.getNameWithoutExtension(), properties, nodes);
}
@ -60,7 +64,9 @@ public class PropertiesTranslatorIO implements TranslatorIO {
}
@Override
public void save(@NotNull Translations translations, @NotNull String directoryPath, @NotNull Consumer<Boolean> callback) {
public void save(@NotNull Project project, @NotNull Translations translations,
@NotNull String directoryPath, @NotNull Consumer<Boolean> callback) {
ApplicationManager.getApplication().runWriteAction(() -> {
try {
for(String locale : translations.getLocales()) {
@ -70,8 +76,9 @@ public class PropertiesTranslatorIO implements TranslatorIO {
String fullPath = directoryPath + "/" + locale + "." + FILE_EXTENSION;
VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(fullPath));
ByteArrayOutputStream content = new ByteArrayOutputStream();
StringWriter content = new StringWriter();
properties.store(content, "I18n " + locale + " keys");
file.setBinaryContent(content.toString().getBytes(file.getCharset()));
}

View File

@ -10,8 +10,10 @@ import org.jetbrains.annotations.Nullable;
public class SettingsState {
public static final String DEFAULT_PREVIEW_LOCALE = "en";
public static final String DEFAULT_FILE_PATTERN = ".*";
private String localesPath;
private String filePattern;
private String previewLocale;
public SettingsState() {}
@ -24,6 +26,14 @@ public class SettingsState {
this.localesPath = localesPath;
}
public @NotNull String getFilePattern() {
return filePattern != null ? filePattern : DEFAULT_FILE_PATTERN;
}
public void setFilePattern(String filePattern) {
this.filePattern = filePattern;
}
public @NotNull String getPreviewLocale() {
return previewLocale != null ? previewLocale : DEFAULT_PREVIEW_LOCALE;
}

View File

@ -63,7 +63,7 @@ public class DataStore {
} else {
TranslatorIO io = IOUtil.determineFormat(localesPath);
io.read(localesPath, (translations) -> {
io.read(project, localesPath, (translations) -> {
if(translations != null) { // Read was successful
this.translations = translations;
@ -80,7 +80,7 @@ public class DataStore {
}
/**
* Saves the current translation state to disk. See {@link TranslatorIO#save(Translations, String, Consumer)}
* Saves the current translation state to disk. See {@link TranslatorIO#save(Project, Translations, String, Consumer)}
* @param callback Complete callback. Indicates if operation was successful(true) or not
*/
public void saveToDisk(@NotNull Consumer<Boolean> callback) {
@ -91,7 +91,7 @@ public class DataStore {
}
TranslatorIO io = IOUtil.determineFormat(localesPath);
io.save(translations, localesPath, callback);
io.save(project, translations, localesPath, callback);
}
/**

View File

@ -24,6 +24,7 @@ public class SettingsDialog {
private final Project project;
private TextFieldWithBrowseButton pathText;
private JBTextField filePatternText;
private JBTextField previewText;
public SettingsDialog(Project project) {
@ -32,10 +33,12 @@ public class SettingsDialog {
public void showAndHandle() {
String localesPath = SettingsService.getInstance(project).getState().getLocalesPath();
String filePattern = SettingsService.getInstance(project).getState().getFilePattern();
String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
if(prepare(localesPath, previewLocale).show() == DialogWrapper.OK_EXIT_CODE) { // Save changes
if(prepare(localesPath, filePattern, previewLocale).show() == DialogWrapper.OK_EXIT_CODE) { // Save changes
SettingsService.getInstance(project).getState().setLocalesPath(pathText.getText());
SettingsService.getInstance(project).getState().setFilePattern(filePatternText.getText());
SettingsService.getInstance(project).getState().setPreviewLocale(previewText.getText());
// Reload instance
@ -43,7 +46,7 @@ public class SettingsDialog {
}
}
private DialogBuilder prepare(String localesPath, String previewLocale) {
private DialogBuilder prepare(String localesPath, String filePattern, String previewLocale) {
JPanel rootPanel = new JPanel(new GridLayout(0, 1, 2, 2));
JBLabel pathLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.text"));
@ -56,6 +59,13 @@ public class SettingsDialog {
rootPanel.add(pathLabel);
rootPanel.add(pathText);
JBLabel filePatternLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.file-pattern"));
filePatternText = new JBTextField(filePattern);
rootPanel.add(filePatternLabel);
rootPanel.add(filePatternText);
JBLabel previewLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.preview"));
previewText = new JBTextField(previewLocale);
previewLabel.setLabelFor(previewText);

View File

@ -0,0 +1,18 @@
package de.marhali.easyi18n.ui.editor;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.patterns.*;
import com.intellij.psi.PsiLiteralValue;
/**
* Show i18n key completion for literal values.
* @author marhali
*/
public class I18nCompletionContributor extends CompletionContributor {
public I18nCompletionContributor() {
extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralValue.class),
new I18nCompletionProvider());
}
}

View File

@ -0,0 +1,66 @@
package de.marhali.easyi18n.ui.editor;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.util.ProcessingContext;
import de.marhali.easyi18n.model.LocalizedNode;
import de.marhali.easyi18n.service.DataStore;
import de.marhali.easyi18n.service.SettingsService;
import de.marhali.easyi18n.util.TranslationsUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* I18n translation key completion provider.
* @author marhali
*/
public class I18nCompletionProvider extends CompletionProvider<CompletionParameters> {
private Project project;
private String previewLocale;
public I18nCompletionProvider() {
DataManager.getInstance().getDataContextFromFocusAsync().onSuccess(data -> {
project = PlatformDataKeys.PROJECT.getData(data);
previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
});
}
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
String query = result.getPrefixMatcher().getPrefix();
List<String> sections = TranslationsUtil.getSections(query);
String lastSection = null;
if(!sections.isEmpty() && !query.endsWith(".")) {
lastSection = sections.remove(sections.size() - 1);
}
String path = TranslationsUtil.sectionsToFullPath(sections);
LocalizedNode node = sections.isEmpty() ? DataStore.getInstance(project).getTranslations().getNodes()
: DataStore.getInstance(project).getTranslations().getNode(path);
for(LocalizedNode children : node.getChildren()) {
if(lastSection == null || children.getKey().startsWith(lastSection)) {
// Construct full key path / Fore nested objects add '.' to indicate deeper level
String fullKey = (path.isEmpty() ? children.getKey() : path + "." + children.getKey()) + (children.isLeaf() ? "" : ".");
result.addElement(LookupElementBuilder.create(fullKey).appendTailText(getTailText(children), true));
}
}
}
private String getTailText(LocalizedNode node) {
return !node.isLeaf() ? " I18n([])"
: " I18n(" + previewLocale + ": " + node.getValue().get(previewLocale) + ")";
}
}

View File

@ -0,0 +1,56 @@
package de.marhali.easyi18n.ui.editor;
import com.intellij.ide.DataManager;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiLiteralValue;
import de.marhali.easyi18n.model.LocalizedNode;
import de.marhali.easyi18n.service.DataStore;
import de.marhali.easyi18n.service.SettingsService;
import org.jetbrains.annotations.NotNull;
/**
* Translation key annotator.
* @author marhali
*/
public class I18nKeyAnnotator implements Annotator {
private Project project;
private String previewLocale;
public I18nKeyAnnotator() {
DataManager.getInstance().getDataContextFromFocusAsync().onSuccess(data -> {
project = PlatformDataKeys.PROJECT.getData(data);
previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
});
}
@Override
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
if(!(element instanceof PsiLiteralValue)) {
return;
}
PsiLiteralValue literalValue = (PsiLiteralValue) element;
String value = literalValue.getValue() instanceof String ? (String) literalValue.getValue() : null;
if(value == null) {
return;
}
LocalizedNode node = DataStore.getInstance(project).getTranslations().getNode(value);
if(node == null) { // Unknown translation. Just ignore it
return;
}
String tooltip = node.isLeaf() ? "I18n(" + previewLocale + ": " + node.getValue().get(previewLocale) + ")"
: "I18n ([])";
holder.newAnnotation(HighlightSeverity.INFORMATION, tooltip).create();
}
}

View File

@ -77,6 +77,10 @@ public class TreeView implements DataSynchronizer {
@Override
public void synchronize(@NotNull Translations translations, @Nullable String searchQuery) {
tree.setModel(new TreeModelTranslator(project, translations, searchQuery));
if(searchQuery != null && !searchQuery.isEmpty()) {
expandAll().run();
}
}
private void handlePopup(MouseEvent e) {

View File

@ -1,11 +1,14 @@
package de.marhali.easyi18n.util;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import de.marhali.easyi18n.io.implementation.JsonTranslatorIO;
import de.marhali.easyi18n.io.implementation.ModularizedJsonTranslatorIO;
import de.marhali.easyi18n.io.implementation.PropertiesTranslatorIO;
import de.marhali.easyi18n.io.TranslatorIO;
import de.marhali.easyi18n.service.SettingsService;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@ -36,6 +39,11 @@ public class IOUtil {
throw new IllegalStateException("Could not determine i18n format. At least one locale file must be defined");
}
// Split files - Should be always JSON
if(any.get().isDirectory()) {
return new ModularizedJsonTranslatorIO();
}
switch (any.get().getFileType().getDefaultExtension().toLowerCase()) {
case "json":
return new JsonTranslatorIO();
@ -48,4 +56,15 @@ public class IOUtil {
any.get().getFileType().getDefaultExtension());
}
}
/**
* Checks if the provided file matches the file pattern specified by configuration
* @param project Current intellij project
* @param file File to check
* @return True if relevant otherwise false
*/
public static boolean isFileRelevant(Project project, VirtualFile file) {
String pattern = SettingsService.getInstance(project).getState().getFilePattern();
return file.getName().matches(pattern);
}
}

View File

@ -0,0 +1,83 @@
package de.marhali.easyi18n.util;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import de.marhali.easyi18n.model.LocalizedNode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Json tree utilities for writing and reading {@link LocalizedNode}'s
* @author marhali
*/
public class JsonUtil {
/**
* Creates a {@link JsonObject} based from an {@link LocalizedNode}
* @param locale Current locale
* @param parent Parent json. Can be an entire json document
* @param node The node instance
*/
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)));
}
} else {
for(LocalizedNode children : node.getChildren()) {
if(children.isLeaf()) {
writeTree(locale, parent, children);
} else {
JsonObject childrenJson = new JsonObject();
writeTree(locale, childrenJson, children);
if(childrenJson.size() > 0) {
parent.add(children.getKey(), childrenJson);
}
}
}
}
}
/**
* Reads a {@link JsonObject} and writes the tree into the provided {@link LocalizedNode}
* @param locale Current locale
* @param json Json to read
* @param data Node. Can be a root node
*/
public static void readTree(String locale, JsonObject json, LocalizedNode data) {
for(Map.Entry<String, JsonElement> entry : json.entrySet()) {
String key = entry.getKey();
try {
// Try to go one level deeper
JsonObject childObject = entry.getValue().getAsJsonObject();
LocalizedNode childrenNode = data.getChildren(key);
if(childrenNode == null) {
childrenNode = new LocalizedNode(key, new ArrayList<>());
data.addChildren(childrenNode);
}
readTree(locale, childObject, childrenNode);
} catch(IllegalStateException e) { // Reached end for this node
LocalizedNode leafNode = data.getChildren(key);
if(leafNode == null) {
leafNode = new LocalizedNode(key, new HashMap<>());
data.addChildren(leafNode);
}
Map<String, String> messages = leafNode.getValue();
messages.put(locale, entry.getValue().getAsString());
leafNode.setValue(messages);
}
}
}
}

View File

@ -10,5 +10,10 @@
<extensions defaultExtensionNs="com.intellij">
<toolWindow id="Easy I18n" anchor="bottom" factoryClass="de.marhali.easyi18n.TranslatorToolWindowFactory" />
<projectService serviceImplementation="de.marhali.easyi18n.service.SettingsService" />
<completion.contributor language="any"
implementationClass="de.marhali.easyi18n.ui.editor.I18nCompletionContributor" />
<annotator language="" implementationClass="de.marhali.easyi18n.ui.editor.I18nKeyAnnotator" />
</extensions>
</idea-plugin>

View File

@ -13,4 +13,5 @@ translation.key=Key
translation.locales=Locales
settings.path.title=Locales Directory
settings.path.text=Locales directory
settings.path.file-pattern=Translation file pattern
settings.preview=Preview locale