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:
commit
a5b291177b
13
CHANGELOG.md
13
CHANGELOG.md
@ -4,6 +4,19 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [1.0.1] - 2021-03-16
|
||||||
### Changed
|
### Changed
|
||||||
- Modified plugin icon to meet IntelliJ guidelines
|
- Modified plugin icon to meet IntelliJ guidelines
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
pluginGroup = de.marhali.easyi18n
|
pluginGroup = de.marhali.easyi18n
|
||||||
pluginName = easy-i18n
|
pluginName = easy-i18n
|
||||||
pluginVersion = 1.0.1
|
pluginVersion = 1.1.0
|
||||||
pluginSinceBuild = 202
|
pluginSinceBuild = 202
|
||||||
pluginUntilBuild = 203.*
|
pluginUntilBuild = 203.*
|
||||||
# Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl
|
# Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package de.marhali.easyi18n.io;
|
package de.marhali.easyi18n.io;
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
|
||||||
import de.marhali.easyi18n.model.Translations;
|
import de.marhali.easyi18n.model.Translations;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -15,16 +17,19 @@ public interface TranslatorIO {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads localized messages from the persistence layer.
|
* 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 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.
|
* @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.
|
* Writes the provided messages (translations) to the persistence layer.
|
||||||
|
* @param project Opened intellij project
|
||||||
* @param translations Translations instance to save
|
* @param translations Translations instance to save
|
||||||
* @param directoryPath The full path for the directory which holds all locale files
|
* @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
|
* @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);
|
||||||
}
|
}
|
||||||
@ -2,12 +2,15 @@ package de.marhali.easyi18n.io.implementation;
|
|||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
|
|
||||||
import de.marhali.easyi18n.io.TranslatorIO;
|
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.JsonUtil;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -26,7 +29,7 @@ public class JsonTranslatorIO implements TranslatorIO {
|
|||||||
private static final String FILE_EXTENSION = "json";
|
private static final String FILE_EXTENSION = "json";
|
||||||
|
|
||||||
@Override
|
@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().saveAll(); // Save opened files (required if new locales were added)
|
||||||
|
|
||||||
ApplicationManager.getApplication().runReadAction(() -> {
|
ApplicationManager.getApplication().runReadAction(() -> {
|
||||||
@ -43,9 +46,17 @@ public class JsonTranslatorIO implements TranslatorIO {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
for(VirtualFile file : files) {
|
for(VirtualFile file : files) {
|
||||||
|
|
||||||
|
if(!IOUtil.isFileRelevant(project, file)) { // File does not matches pattern
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
locales.add(file.getNameWithoutExtension());
|
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));
|
callback.accept(new Translations(locales, nodes));
|
||||||
@ -58,20 +69,25 @@ public class JsonTranslatorIO implements TranslatorIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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();
|
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||||
try {
|
try {
|
||||||
for(String locale : translations.getLocales()) {
|
for(String locale : translations.getLocales()) {
|
||||||
JsonObject content = new JsonObject();
|
JsonObject content = new JsonObject();
|
||||||
writeTree(locale, content, translations.getNodes());
|
JsonUtil.writeTree(locale, content, translations.getNodes());
|
||||||
//JsonElement content = writeTree(locale, new JsonObject(), translations.getNodes());
|
|
||||||
|
|
||||||
String fullPath = directoryPath + "/" + locale + "." + FILE_EXTENSION;
|
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
|
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +1,19 @@
|
|||||||
package de.marhali.easyi18n.io.implementation;
|
package de.marhali.easyi18n.io.implementation;
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
|
|
||||||
import de.marhali.easyi18n.io.TranslatorIO;
|
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.TranslationsUtil;
|
import de.marhali.easyi18n.util.TranslationsUtil;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.*;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@ -27,7 +26,7 @@ public class PropertiesTranslatorIO implements TranslatorIO {
|
|||||||
public static final String FILE_EXTENSION = "properties";
|
public static final String FILE_EXTENSION = "properties";
|
||||||
|
|
||||||
@Override
|
@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().saveAll(); // Save opened files (required if new locales were added)
|
||||||
|
|
||||||
ApplicationManager.getApplication().runReadAction(() -> {
|
ApplicationManager.getApplication().runReadAction(() -> {
|
||||||
@ -44,9 +43,14 @@ public class PropertiesTranslatorIO implements TranslatorIO {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
for (VirtualFile file : files) {
|
for (VirtualFile file : files) {
|
||||||
|
|
||||||
|
if(!IOUtil.isFileRelevant(project, file)) { // File does not matches pattern
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
locales.add(file.getNameWithoutExtension());
|
locales.add(file.getNameWithoutExtension());
|
||||||
Properties properties = new Properties();
|
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);
|
readProperties(file.getNameWithoutExtension(), properties, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +64,9 @@ public class PropertiesTranslatorIO implements TranslatorIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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(() -> {
|
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||||
try {
|
try {
|
||||||
for(String locale : translations.getLocales()) {
|
for(String locale : translations.getLocales()) {
|
||||||
@ -70,8 +76,9 @@ public class PropertiesTranslatorIO implements TranslatorIO {
|
|||||||
String fullPath = directoryPath + "/" + locale + "." + FILE_EXTENSION;
|
String fullPath = directoryPath + "/" + locale + "." + FILE_EXTENSION;
|
||||||
VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(fullPath));
|
VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(fullPath));
|
||||||
|
|
||||||
ByteArrayOutputStream content = new ByteArrayOutputStream();
|
StringWriter content = new StringWriter();
|
||||||
properties.store(content, "I18n " + locale + " keys");
|
properties.store(content, "I18n " + locale + " keys");
|
||||||
|
|
||||||
file.setBinaryContent(content.toString().getBytes(file.getCharset()));
|
file.setBinaryContent(content.toString().getBytes(file.getCharset()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,10 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
public class SettingsState {
|
public class SettingsState {
|
||||||
|
|
||||||
public static final String DEFAULT_PREVIEW_LOCALE = "en";
|
public static final String DEFAULT_PREVIEW_LOCALE = "en";
|
||||||
|
public static final String DEFAULT_FILE_PATTERN = ".*";
|
||||||
|
|
||||||
private String localesPath;
|
private String localesPath;
|
||||||
|
private String filePattern;
|
||||||
private String previewLocale;
|
private String previewLocale;
|
||||||
|
|
||||||
public SettingsState() {}
|
public SettingsState() {}
|
||||||
@ -24,6 +26,14 @@ public class SettingsState {
|
|||||||
this.localesPath = localesPath;
|
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() {
|
public @NotNull String getPreviewLocale() {
|
||||||
return previewLocale != null ? previewLocale : DEFAULT_PREVIEW_LOCALE;
|
return previewLocale != null ? previewLocale : DEFAULT_PREVIEW_LOCALE;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ public class DataStore {
|
|||||||
} else {
|
} else {
|
||||||
TranslatorIO io = IOUtil.determineFormat(localesPath);
|
TranslatorIO io = IOUtil.determineFormat(localesPath);
|
||||||
|
|
||||||
io.read(localesPath, (translations) -> {
|
io.read(project, localesPath, (translations) -> {
|
||||||
if(translations != null) { // Read was successful
|
if(translations != null) { // Read was successful
|
||||||
this.translations = translations;
|
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
|
* @param callback Complete callback. Indicates if operation was successful(true) or not
|
||||||
*/
|
*/
|
||||||
public void saveToDisk(@NotNull Consumer<Boolean> callback) {
|
public void saveToDisk(@NotNull Consumer<Boolean> callback) {
|
||||||
@ -91,7 +91,7 @@ public class DataStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TranslatorIO io = IOUtil.determineFormat(localesPath);
|
TranslatorIO io = IOUtil.determineFormat(localesPath);
|
||||||
io.save(translations, localesPath, callback);
|
io.save(project, translations, localesPath, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -24,6 +24,7 @@ public class SettingsDialog {
|
|||||||
private final Project project;
|
private final Project project;
|
||||||
|
|
||||||
private TextFieldWithBrowseButton pathText;
|
private TextFieldWithBrowseButton pathText;
|
||||||
|
private JBTextField filePatternText;
|
||||||
private JBTextField previewText;
|
private JBTextField previewText;
|
||||||
|
|
||||||
public SettingsDialog(Project project) {
|
public SettingsDialog(Project project) {
|
||||||
@ -32,10 +33,12 @@ public class SettingsDialog {
|
|||||||
|
|
||||||
public void showAndHandle() {
|
public void showAndHandle() {
|
||||||
String localesPath = SettingsService.getInstance(project).getState().getLocalesPath();
|
String localesPath = SettingsService.getInstance(project).getState().getLocalesPath();
|
||||||
|
String filePattern = SettingsService.getInstance(project).getState().getFilePattern();
|
||||||
String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
|
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().setLocalesPath(pathText.getText());
|
||||||
|
SettingsService.getInstance(project).getState().setFilePattern(filePatternText.getText());
|
||||||
SettingsService.getInstance(project).getState().setPreviewLocale(previewText.getText());
|
SettingsService.getInstance(project).getState().setPreviewLocale(previewText.getText());
|
||||||
|
|
||||||
// Reload instance
|
// 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));
|
JPanel rootPanel = new JPanel(new GridLayout(0, 1, 2, 2));
|
||||||
|
|
||||||
JBLabel pathLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.text"));
|
JBLabel pathLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.text"));
|
||||||
@ -56,6 +59,13 @@ public class SettingsDialog {
|
|||||||
rootPanel.add(pathLabel);
|
rootPanel.add(pathLabel);
|
||||||
rootPanel.add(pathText);
|
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"));
|
JBLabel previewLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.preview"));
|
||||||
previewText = new JBTextField(previewLocale);
|
previewText = new JBTextField(previewLocale);
|
||||||
previewLabel.setLabelFor(previewText);
|
previewLabel.setLabelFor(previewText);
|
||||||
|
|||||||
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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) + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -77,6 +77,10 @@ public class TreeView implements DataSynchronizer {
|
|||||||
@Override
|
@Override
|
||||||
public void synchronize(@NotNull Translations translations, @Nullable String searchQuery) {
|
public void synchronize(@NotNull Translations translations, @Nullable String searchQuery) {
|
||||||
tree.setModel(new TreeModelTranslator(project, translations, searchQuery));
|
tree.setModel(new TreeModelTranslator(project, translations, searchQuery));
|
||||||
|
|
||||||
|
if(searchQuery != null && !searchQuery.isEmpty()) {
|
||||||
|
expandAll().run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePopup(MouseEvent e) {
|
private void handlePopup(MouseEvent e) {
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
package de.marhali.easyi18n.util;
|
package de.marhali.easyi18n.util;
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import de.marhali.easyi18n.io.implementation.JsonTranslatorIO;
|
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.implementation.PropertiesTranslatorIO;
|
||||||
import de.marhali.easyi18n.io.TranslatorIO;
|
import de.marhali.easyi18n.io.TranslatorIO;
|
||||||
|
|
||||||
|
import de.marhali.easyi18n.service.SettingsService;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.File;
|
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");
|
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()) {
|
switch (any.get().getFileType().getDefaultExtension().toLowerCase()) {
|
||||||
case "json":
|
case "json":
|
||||||
return new JsonTranslatorIO();
|
return new JsonTranslatorIO();
|
||||||
@ -48,4 +56,15 @@ public class IOUtil {
|
|||||||
any.get().getFileType().getDefaultExtension());
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
83
src/main/java/de/marhali/easyi18n/util/JsonUtil.java
Normal file
83
src/main/java/de/marhali/easyi18n/util/JsonUtil.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,5 +10,10 @@
|
|||||||
<extensions defaultExtensionNs="com.intellij">
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
<toolWindow id="Easy I18n" anchor="bottom" factoryClass="de.marhali.easyi18n.TranslatorToolWindowFactory" />
|
<toolWindow id="Easy I18n" anchor="bottom" factoryClass="de.marhali.easyi18n.TranslatorToolWindowFactory" />
|
||||||
<projectService serviceImplementation="de.marhali.easyi18n.service.SettingsService" />
|
<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>
|
</extensions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
@ -13,4 +13,5 @@ translation.key=Key
|
|||||||
translation.locales=Locales
|
translation.locales=Locales
|
||||||
settings.path.title=Locales Directory
|
settings.path.title=Locales Directory
|
||||||
settings.path.text=Locales directory
|
settings.path.text=Locales directory
|
||||||
|
settings.path.file-pattern=Translation file pattern
|
||||||
settings.preview=Preview locale
|
settings.preview=Preview locale
|
||||||
Loading…
x
Reference in New Issue
Block a user