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]
|
||||
|
||||
## [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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
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">
|
||||
<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>
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user