diff --git a/CHANGELOG.md b/CHANGELOG.md index c2160d1..97c8a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,30 @@ # easy-i18n Changelog ## [Unreleased] +### BREAKING CHANGES +- Configuration rework. Existing settings will be lost and must be configured via the new configuration page + +### Added +- Key delimiters (namespace / section) can be configured +- Extract translation intention +- Full language support for Java, Kotlin, JavaScript / TypeScript, Vue and PHP +- Expand already expanded nodes after data update +- Experimental option to force translation key folding +- Individual icon for tool-window and lookup items +- Dedicated configuration file (easy-i18n.xml) inside .idea folder + +### Changed +- Editor assistance has been reengineered. This will affect key suggestion and annotation +- Moved configuration dialog into own page inside IDE Settings + +### Fixed +- AlreadyDisposedException on FileChangeListener after project dispose ## [3.2.0] -### Added -- Support for IntelliJ 2022.1 +### Added +- Support for IntelliJ 2022.1 -### Changed +### Changed - Updated dependencies ## [3.1.0] diff --git a/README.md b/README.md index c83ee0f..49b6c25 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,9 @@ _For more examples, please refer to the [Examples Directory](https://github.com/ ## Roadmap - [X] JSON5 Support +- [X] Configurable namespace and section separators +- [X] Define default namespace to use if none was provided +- [X] Enhance editor code assistance - [ ] XML Support - [ ] Mark duplicate translation values diff --git a/gradle.properties b/gradle.properties index b630b41..1486146 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = de.marhali.easyi18n pluginName = easy-i18n # SemVer format -> https://semver.org -pluginVersion = 3.2.0 +pluginVersion = 4.0.0-rc.1 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. @@ -17,7 +17,7 @@ platformVersion = 2021.3 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 -platformPlugins = org.jetbrains.kotlin, JavaScriptLanguage, org.jetbrains.plugins.vue:213.5744.223 +platformPlugins = org.jetbrains.kotlin, JavaScriptLanguage, org.jetbrains.plugins.vue:213.5744.223, com.jetbrains.php:213.5744.279 # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 javaVersion = 11 diff --git a/src/main/java/de/marhali/easyi18n/DataBus.java b/src/main/java/de/marhali/easyi18n/DataBus.java index d500232..2eb49d0 100644 --- a/src/main/java/de/marhali/easyi18n/DataBus.java +++ b/src/main/java/de/marhali/easyi18n/DataBus.java @@ -1,9 +1,9 @@ package de.marhali.easyi18n; -import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.bus.BusListener; import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.KeyPath; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/de/marhali/easyi18n/DataStore.java b/src/main/java/de/marhali/easyi18n/DataStore.java index 0d65c42..4b60c9f 100644 --- a/src/main/java/de/marhali/easyi18n/DataStore.java +++ b/src/main/java/de/marhali/easyi18n/DataStore.java @@ -7,10 +7,10 @@ import com.intellij.openapi.vfs.*; import de.marhali.easyi18n.exception.EmptyLocalesDirException; import de.marhali.easyi18n.io.IOHandler; -import de.marhali.easyi18n.model.SettingsState; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.service.FileChangeListener; -import de.marhali.easyi18n.service.SettingsService; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; import de.marhali.easyi18n.util.NotificationHelper; import org.jetbrains.annotations.NotNull; @@ -35,7 +35,7 @@ public class DataStore { this.changeListener = new FileChangeListener(project); VirtualFileManager.getInstance().addAsyncFileListener( - this.changeListener, Disposer.newDisposable("EasyI18n")); + this.changeListener, Disposer.newDisposable(project, "EasyI18n")); } public @NotNull TranslationData getData() { @@ -48,18 +48,18 @@ public class DataStore { * @param successResult Consumer will inform if operation was successful */ public void loadFromPersistenceLayer(@NotNull Consumer successResult) { - SettingsState settings = SettingsService.getInstance(this.project).getState(); + ProjectSettings settings = ProjectSettingsService.get(project).getState(); ApplicationManager.getApplication().saveAll(); // Save opened files (required if new locales were added) ApplicationManager.getApplication().runReadAction(() -> { try { this.data = new IOHandler(settings).read(); - this.changeListener.updateLocalesPath(settings.getLocalesPath()); + this.changeListener.updateLocalesPath(settings.getLocalesDirectory()); successResult.accept(true); } catch (Exception ex) { - this.data = new TranslationData(settings.isSortKeys()); + this.data = new TranslationData(settings.isSorting()); successResult.accept(false); if(ex instanceof EmptyLocalesDirException) { @@ -76,7 +76,7 @@ public class DataStore { * @param successResult Consumer will inform if operation was successful */ public void saveToPersistenceLayer(@NotNull Consumer successResult) { - SettingsState settings = SettingsService.getInstance(this.project).getState(); + ProjectSettings settings = ProjectSettingsService.get(project).getState(); ApplicationManager.getApplication().runWriteAction(() -> { try { diff --git a/src/main/java/de/marhali/easyi18n/InstanceManager.java b/src/main/java/de/marhali/easyi18n/InstanceManager.java index 129f1be..5fe9c55 100644 --- a/src/main/java/de/marhali/easyi18n/InstanceManager.java +++ b/src/main/java/de/marhali/easyi18n/InstanceManager.java @@ -3,7 +3,7 @@ package de.marhali.easyi18n; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; -import de.marhali.easyi18n.model.TranslationUpdate; +import de.marhali.easyi18n.model.action.TranslationUpdate; import org.jetbrains.annotations.NotNull; @@ -52,13 +52,22 @@ public class InstanceManager { return this.bus; } + /** + * Reloads the plugin instance. Unsaved cached data will be deleted. + * Fetches data from persistence layer and notifies all endpoints via {@link DataBus}. + */ + public void reload() { + store.loadFromPersistenceLayer((success) -> + bus.propagate().onUpdateData(store.getData())); + } + public void processUpdate(TranslationUpdate update) { if(update.isDeletion() || update.isKeyChange()) { // Remove origin translation this.store.getData().setTranslation(update.getOrigin().getKey(), null); } if(!update.isDeletion()) { // Create or re-create translation with changed data - this.store.getData().setTranslation(update.getChange().getKey(), update.getChange().getTranslation()); + this.store.getData().setTranslation(update.getChange().getKey(), update.getChange().getValue()); } this.store.saveToPersistenceLayer(success -> { diff --git a/src/main/java/de/marhali/easyi18n/action/AddAction.java b/src/main/java/de/marhali/easyi18n/action/AddAction.java index b356b9a..2505a18 100644 --- a/src/main/java/de/marhali/easyi18n/action/AddAction.java +++ b/src/main/java/de/marhali/easyi18n/action/AddAction.java @@ -6,10 +6,10 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; import com.intellij.ui.content.Content; -import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.KeyPathConverter; -import de.marhali.easyi18n.service.WindowManager; import de.marhali.easyi18n.dialog.AddDialog; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.service.WindowManager; +import de.marhali.easyi18n.util.KeyPathConverter; import de.marhali.easyi18n.util.TreeUtil; import org.jetbrains.annotations.NotNull; @@ -32,7 +32,7 @@ public class AddAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - new AddDialog(Objects.requireNonNull(e.getProject()), detectPreKey(e.getProject())).showAndHandle(); + new AddDialog(Objects.requireNonNull(e.getProject()), detectPreKey(e.getProject()), null).showAndHandle(); } private @Nullable KeyPath detectPreKey(@NotNull Project project) { @@ -58,7 +58,7 @@ public class AddAction extends AnAction { if(row >= 0) { String path = String.valueOf(window.getTableView().getTable().getValueAt(row, 0)); - return converter.split(path); + return converter.fromString(path); } } diff --git a/src/main/java/de/marhali/easyi18n/action/ReloadAction.java b/src/main/java/de/marhali/easyi18n/action/ReloadAction.java index 323ba44..5a01985 100644 --- a/src/main/java/de/marhali/easyi18n/action/ReloadAction.java +++ b/src/main/java/de/marhali/easyi18n/action/ReloadAction.java @@ -8,6 +8,7 @@ import de.marhali.easyi18n.InstanceManager; import org.jetbrains.annotations.NotNull; +import java.util.Objects; import java.util.ResourceBundle; /** @@ -23,9 +24,6 @@ public class ReloadAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - InstanceManager manager = InstanceManager.get(e.getProject()); - manager.store().loadFromPersistenceLayer((success) -> { - manager.bus().propagate().onUpdateData(manager.store().getData()); - }); + InstanceManager.get(Objects.requireNonNull(e.getProject())).reload(); } } \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/action/SettingsAction.java b/src/main/java/de/marhali/easyi18n/action/SettingsAction.java index 42feefa..fabd792 100644 --- a/src/main/java/de/marhali/easyi18n/action/SettingsAction.java +++ b/src/main/java/de/marhali/easyi18n/action/SettingsAction.java @@ -3,7 +3,10 @@ package de.marhali.easyi18n.action; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import de.marhali.easyi18n.dialog.SettingsDialog; +import com.intellij.openapi.options.ShowSettingsUtil; + +import de.marhali.easyi18n.settings.ProjectSettingsConfigurable; + import org.jetbrains.annotations.NotNull; import java.util.ResourceBundle; @@ -21,6 +24,6 @@ public class SettingsAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { - new SettingsDialog(e.getProject()).showAndHandle(); + ShowSettingsUtil.getInstance().showSettingsDialog(e.getProject(), ProjectSettingsConfigurable.class); } } \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/assistance/OptionalAssistance.java b/src/main/java/de/marhali/easyi18n/assistance/OptionalAssistance.java new file mode 100644 index 0000000..3d33b60 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/OptionalAssistance.java @@ -0,0 +1,17 @@ +package de.marhali.easyi18n.assistance; + +import com.intellij.openapi.project.Project; + +import de.marhali.easyi18n.settings.ProjectSettingsService; + +import org.jetbrains.annotations.NotNull; + +/** + * Used to define editor hooks as assistable. + * @author marhali + */ +public interface OptionalAssistance { + default boolean isAssistance(@NotNull Project project) { + return ProjectSettingsService.get(project).getState().isAssistance(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/completion/JavaCompletionContributor.java b/src/main/java/de/marhali/easyi18n/assistance/completion/JavaCompletionContributor.java new file mode 100644 index 0000000..3d2b705 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/completion/JavaCompletionContributor.java @@ -0,0 +1,17 @@ +package de.marhali.easyi18n.assistance.completion; + +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiLiteralExpression; + +/** + * Java specific completion contributor. + * @author marhali + */ +public class JavaCompletionContributor extends CompletionContributor { + public JavaCompletionContributor() { + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralExpression.class), + new KeyCompletionProvider()); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/completion/JsCompletionContributor.java b/src/main/java/de/marhali/easyi18n/assistance/completion/JsCompletionContributor.java new file mode 100644 index 0000000..3154937 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/completion/JsCompletionContributor.java @@ -0,0 +1,17 @@ +package de.marhali.easyi18n.assistance.completion; + +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.lang.javascript.psi.JSLiteralExpression; +import com.intellij.patterns.PlatformPatterns; + +/** + * JavaScript specific completion contributor. + * @author marhali + */ +public class JsCompletionContributor extends CompletionContributor { + public JsCompletionContributor() { + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(JSLiteralExpression.class), + new KeyCompletionProvider()); + } +} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/assistance/completion/KeyCompletionProvider.java b/src/main/java/de/marhali/easyi18n/assistance/completion/KeyCompletionProvider.java new file mode 100644 index 0000000..eccf9eb --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/completion/KeyCompletionProvider.java @@ -0,0 +1,60 @@ +package de.marhali.easyi18n.assistance.completion; + +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.util.ProcessingContext; + +import de.marhali.easyi18n.InstanceManager; +import de.marhali.easyi18n.assistance.OptionalAssistance; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; +import de.marhali.easyi18n.util.KeyPathConverter; + +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.Set; + +/** + * Provides existing translation keys for code completion. + * @author marhali + */ +class KeyCompletionProvider extends CompletionProvider implements OptionalAssistance { + + private static final Icon icon = IconLoader.getIcon("/icons/translate13.svg", KeyCompletionProvider.class); + + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, + @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { + Project project = parameters.getOriginalFile().getProject(); + + if(!isAssistance(project)) { + return; + } + + ProjectSettings settings = ProjectSettingsService.get(project).getState(); + TranslationData data = InstanceManager.get(project).store().getData(); + Set fullKeys = data.getFullKeys(); + + for (KeyPath key : fullKeys) { + result.addElement(constructLookup(new Translation(key, data.getTranslation(key)), settings)); + } + } + + private LookupElement constructLookup(Translation translation, ProjectSettings settings) { + KeyPathConverter converter = new KeyPathConverter(settings); + + return LookupElementBuilder + .create(converter.toString(translation.getKey())) + .withTailText(" " + translation.getValue().get(settings.getPreviewLocale()), true) + .withIcon(icon); + } +} diff --git a/src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyCompletionContributor.java b/src/main/java/de/marhali/easyi18n/assistance/completion/KtCompletionContributor.java similarity index 60% rename from src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyCompletionContributor.java rename to src/main/java/de/marhali/easyi18n/assistance/completion/KtCompletionContributor.java index 30a6a57..eadffb5 100644 --- a/src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyCompletionContributor.java +++ b/src/main/java/de/marhali/easyi18n/assistance/completion/KtCompletionContributor.java @@ -1,19 +1,16 @@ -package de.marhali.easyi18n.editor.kotlin; +package de.marhali.easyi18n.assistance.completion; import com.intellij.codeInsight.completion.CompletionContributor; import com.intellij.codeInsight.completion.CompletionType; import com.intellij.patterns.PlatformPatterns; - -import de.marhali.easyi18n.editor.KeyCompletionProvider; import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry; /** - * Kotlin specific translation key completion contributor. + * Kotlin specific completion contributor. * @author marhali */ -public class KotlinKeyCompletionContributor extends CompletionContributor { - - public KotlinKeyCompletionContributor() { +public class KtCompletionContributor extends CompletionContributor { + public KtCompletionContributor() { extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(KtLiteralStringTemplateEntry.class), new KeyCompletionProvider()); } diff --git a/src/main/java/de/marhali/easyi18n/assistance/completion/PhpCompletionContributor.java b/src/main/java/de/marhali/easyi18n/assistance/completion/PhpCompletionContributor.java new file mode 100644 index 0000000..f6c76b9 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/completion/PhpCompletionContributor.java @@ -0,0 +1,17 @@ +package de.marhali.easyi18n.assistance.completion; + +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.patterns.PlatformPatterns; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; + +/** + * Php specific completion contributor. + * @author marhali + */ +public class PhpCompletionContributor extends CompletionContributor { + public PhpCompletionContributor() { + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(StringLiteralExpression.class), + new KeyCompletionProvider()); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/documentation/AbstractDocumentationProvider.java b/src/main/java/de/marhali/easyi18n/assistance/documentation/AbstractDocumentationProvider.java new file mode 100644 index 0000000..a2a1d10 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/documentation/AbstractDocumentationProvider.java @@ -0,0 +1,104 @@ +package de.marhali.easyi18n.assistance.documentation; + +import com.intellij.lang.documentation.DocumentationMarkup; +import com.intellij.lang.documentation.DocumentationProvider; +import com.intellij.openapi.project.Project; + +import de.marhali.easyi18n.InstanceManager; +import de.marhali.easyi18n.assistance.OptionalAssistance; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.TranslationNode; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; +import de.marhali.easyi18n.util.KeyPathConverter; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * Provides locale values as documentation for translation keys. + * @author marhali + */ +abstract class AbstractDocumentationProvider implements DocumentationProvider, OptionalAssistance { + + private static final ResourceBundle bundle = ResourceBundle.getBundle("messages"); + + /** + * Checks if the provided key is a valid translation-key and generates the equivalent documentation for it. + * @param project Opened project + * @param key Designated translation key + * @return Generated documentation or null if not responsible + */ + protected @Nullable String generateDoc(@NotNull Project project, @Nullable String key) { + if(key == null || !isAssistance(project)) { + return null; + } + + ProjectSettings settings = ProjectSettingsService.get(project).getState(); + KeyPathConverter converter = new KeyPathConverter(settings); + KeyPath path = converter.fromString(key); + + // So we want to take care of context and pluralization here + // we should check the last key section for plural / context delims and if so provide all leafs within the last node + + if(path.isEmpty()) { + return null; + } + + TranslationData data = InstanceManager.get(project).store().getData(); + String leaf = path.remove(path.size() - 1); + TranslationNode leafNode = data.getRootNode(); + + for(String section : path) { + leafNode = leafNode.getChildren().get(section); + if(leafNode == null) { // Cannot resolve last node before leaf + return null; + } + } + + Map results = new LinkedHashMap<>(); + + // Filter results for matching leafs (contextual and pluralization support) + for (Map.Entry entry : leafNode.getChildren().entrySet()) { + if(entry.getKey().startsWith(leaf) && entry.getValue().isLeaf()) { + results.put(entry.getKey(), entry.getValue().getValue().get(settings.getPreviewLocale())); + } + } + + if(results.isEmpty()) { // No results to show + return null; + } + + StringBuilder builder = new StringBuilder(); + + builder.append(DocumentationMarkup.DEFINITION_START); + builder.append(bundle.getString("documentation")); + builder.append(DocumentationMarkup.DEFINITION_END); + + if(results.size() == 1) { // Single value + builder.append(DocumentationMarkup.CONTENT_START); + builder.append("").append(results.values().toArray()[0]).append(""); + builder.append(DocumentationMarkup.CONTENT_END); + + } else { // Pluralization | Contextual relevant values + builder.append(DocumentationMarkup.SECTIONS_START); + + for (Map.Entry entry : results.entrySet()) { + builder.append(DocumentationMarkup.SECTION_HEADER_START); + builder.append(entry.getKey()).append(":"); + builder.append(DocumentationMarkup.SECTION_SEPARATOR); + builder.append("

"); + builder.append("").append(entry.getValue()).append(""); + } + + builder.append(DocumentationMarkup.SECTIONS_END); + } + + return builder.toString(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/documentation/CommonDocumentationProvider.java b/src/main/java/de/marhali/easyi18n/assistance/documentation/CommonDocumentationProvider.java new file mode 100644 index 0000000..5ca0df0 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/documentation/CommonDocumentationProvider.java @@ -0,0 +1,26 @@ +package de.marhali.easyi18n.assistance.documentation; + +import com.intellij.psi.PsiElement; +import de.marhali.easyi18n.assistance.reference.PsiKeyReference; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.Nullable; + +/** + * Language unspecific documentation provider. Every supported language should register an extension to this EP. + * @author marhali + */ +public class CommonDocumentationProvider extends AbstractDocumentationProvider { + + @Override + public @Nullable + @Nls String generateDoc(PsiElement element, @Nullable PsiElement originalElement) { + if(!(element instanceof PsiKeyReference.TranslationReference)) { + return null; + } + + PsiKeyReference.TranslationReference keyReference = (PsiKeyReference.TranslationReference) element; + String value = keyReference.getName(); + + return generateDoc(element.getProject(), value); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/AbstractFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/AbstractFoldingBuilder.java new file mode 100644 index 0000000..691e30f --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/AbstractFoldingBuilder.java @@ -0,0 +1,104 @@ +package de.marhali.easyi18n.assistance.folding; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.folding.FoldingBuilderEx; +import com.intellij.lang.folding.FoldingDescriptor; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; + +import de.marhali.easyi18n.DataStore; +import de.marhali.easyi18n.InstanceManager; +import de.marhali.easyi18n.assistance.OptionalAssistance; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; +import de.marhali.easyi18n.util.KeyPathConverter; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Language specific translation key folding with representative locale value. + * @author marhali + */ +abstract class AbstractFoldingBuilder extends FoldingBuilderEx implements OptionalAssistance { + /** + * Extract all relevant folding regions for the desired root element. + * The implementation does not need to verify if the character literal is a valid translation. + * @param root Root element + * @return found regions + */ + abstract @NotNull List> extractRegions(@NotNull PsiElement root); + + /** + * Extract the text from the given node. + * @param node Node + * @return extracted text or null if not applicable + */ + abstract @Nullable String extractText(@NotNull ASTNode node); + + @Override + public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) { + + if(quick || !isAssistance(root.getProject())) { + return FoldingDescriptor.EMPTY; + } + + List descriptors = new ArrayList<>(); + + ProjectSettings settings = ProjectSettingsService.get(root.getProject()).getState(); + TranslationData data = InstanceManager.get(root.getProject()).store().getData(); + KeyPathConverter converter = new KeyPathConverter(settings); + + for(Pair region : extractRegions(root)) { + if(data.getTranslation(converter.fromString(region.first)) == null) { + continue; + } + + TextRange range = new TextRange(region.second.getTextRange().getStartOffset() + 1, + region.second.getTextRange().getEndOffset() - 1); + + // Some language implementations like [Vue Template] does not support FoldingGroup's + FoldingDescriptor descriptor = new FoldingDescriptor(region.second.getNode(), range, + null, Set.of(), settings.isAlwaysFold()); + + descriptors.add(descriptor); + } + + return descriptors.toArray(new FoldingDescriptor[0]); + } + + @Override + public @Nullable String getPlaceholderText(@NotNull ASTNode node) { + String text = extractText(node); + + if(text == null) { + return null; + } + + Project project = node.getPsi().getProject(); + DataStore store = InstanceManager.get(project).store(); + KeyPathConverter converter = new KeyPathConverter(project); + TranslationValue localeValues = store.getData().getTranslation(converter.fromString(text)); + + if(localeValues == null) { + return null; + } + + String previewLocale = ProjectSettingsService.get(project).getState().getPreviewLocale(); + return localeValues.get(previewLocale); + } + + @Override + public boolean isCollapsedByDefault(@NotNull ASTNode node) { + return true; + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/JavaFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/JavaFoldingBuilder.java new file mode 100644 index 0000000..e4904a5 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/JavaFoldingBuilder.java @@ -0,0 +1,32 @@ +package de.marhali.easyi18n.assistance.folding; + +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLiteralExpression; +import com.intellij.psi.util.PsiTreeUtil; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Java specific translation key folding. + * @author marhali + */ +public class JavaFoldingBuilder extends AbstractFoldingBuilder { + @Override + @NotNull List> extractRegions(@NotNull PsiElement root) { + return PsiTreeUtil.findChildrenOfType(root, PsiLiteralExpression.class).stream().map(literalExpression -> + Pair.pair(String.valueOf(literalExpression.getValue()), (PsiElement) literalExpression)) + .collect(Collectors.toList()); + } + + @Override + @Nullable String extractText(@NotNull ASTNode node) { + PsiLiteralExpression literalExpression = node.getPsi(PsiLiteralExpression.class); + return String.valueOf(literalExpression.getValue()); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/JsFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/JsFoldingBuilder.java new file mode 100644 index 0000000..e96b53a --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/JsFoldingBuilder.java @@ -0,0 +1,32 @@ +package de.marhali.easyi18n.assistance.folding; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.javascript.psi.JSLiteralExpression; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * JavaScript specific translation key folding. + * @author marhali + */ +public class JsFoldingBuilder extends AbstractFoldingBuilder { + @Override + @NotNull List> extractRegions(@NotNull PsiElement root) { + return PsiTreeUtil.findChildrenOfType(root, JSLiteralExpression.class).stream().map(literalExpression -> + Pair.pair(literalExpression.getStringValue(), (PsiElement) literalExpression)) + .collect(Collectors.toList()); + } + + @Override + @Nullable String extractText(@NotNull ASTNode node) { + JSLiteralExpression literalExpression = node.getPsi(JSLiteralExpression.class); + return literalExpression.getStringValue(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/KtFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/KtFoldingBuilder.java new file mode 100644 index 0000000..bd95f16 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/KtFoldingBuilder.java @@ -0,0 +1,45 @@ +package de.marhali.easyi18n.assistance.folding; + +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.psi.KtStringTemplateEntry; +import org.jetbrains.kotlin.psi.KtStringTemplateExpression; + +import java.util.ArrayList; +import java.util.List; + +/** + * Kotlin specific translation-key folding. + * @author marhali + */ +public class KtFoldingBuilder extends AbstractFoldingBuilder { + @Override + @NotNull List> extractRegions(@NotNull PsiElement root) { + List> regions = new ArrayList<>(); + + for (KtStringTemplateExpression templateExpression : PsiTreeUtil.findChildrenOfType(root, KtStringTemplateExpression.class)) { + for (KtStringTemplateEntry entry : templateExpression.getEntries()) { + regions.add(Pair.pair(entry.getText(), templateExpression)); + break; + } + } + + return regions; + } + + @Override + @Nullable String extractText(@NotNull ASTNode node) { + KtStringTemplateExpression templateExpression = node.getPsi(KtStringTemplateExpression.class); + + for (KtStringTemplateEntry entry : templateExpression.getEntries()) { + return entry.getText(); + } + + return null; + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/PhpFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/PhpFoldingBuilder.java new file mode 100644 index 0000000..f694b3b --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/PhpFoldingBuilder.java @@ -0,0 +1,31 @@ +package de.marhali.easyi18n.assistance.folding; + +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Php specific translation key folding. + * @author marhali + */ +public class PhpFoldingBuilder extends AbstractFoldingBuilder { + @Override + @NotNull List> extractRegions(@NotNull PsiElement root) { + return PsiTreeUtil.findChildrenOfType(root, StringLiteralExpression.class).stream().map(literalExpression -> + Pair.pair(literalExpression.getContents(), (PsiElement) literalExpression)) + .collect(Collectors.toList()); + } + + @Override + @Nullable String extractText(@NotNull ASTNode node) { + StringLiteralExpression literalExpression = node.getPsi(StringLiteralExpression.class); + return literalExpression.getContents(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractTranslationIntention.java new file mode 100644 index 0000000..99be4c2 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractTranslationIntention.java @@ -0,0 +1,138 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.codeInsight.intention.BaseElementAtCaretIntentionAction; +import com.intellij.codeInspection.util.IntentionFamilyName; +import com.intellij.codeInspection.util.IntentionName; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.util.IncorrectOperationException; + +import de.marhali.easyi18n.InstanceManager; +import de.marhali.easyi18n.assistance.OptionalAssistance; +import de.marhali.easyi18n.dialog.AddDialog; +import de.marhali.easyi18n.dialog.EditDialog; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; +import de.marhali.easyi18n.util.KeyPathConverter; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ResourceBundle; + +/** + * Intention for translation related use-cases. + * Can be used to extract (create) translations or to edit existing ones. + * @author marhali + */ +abstract class AbstractTranslationIntention extends BaseElementAtCaretIntentionAction implements OptionalAssistance { + + protected static final ResourceBundle bundle = ResourceBundle.getBundle("messages"); + + private boolean existingTranslation = false; + + @Override + public @IntentionName @NotNull String getText() { + return existingTranslation + ? bundle.getString("action.edit") + : bundle.getString("action.extract"); + } + + @Override + public @NotNull @IntentionFamilyName String getFamilyName() { + return "EasyI18n"; + } + + @Override + public boolean startInWriteAction() { + return false; + } + + /** + * This is the only method a language-specific translation intention needs to implement. + * The implementation needs to verify element type and extract the relevant key literal or value. + * @param element Element at caret + * @return extract translation key (not verified!) or null if intention is not applicable for this element + */ + protected abstract @Nullable String extractText(@NotNull PsiElement element); + + @NotNull TextRange convertRange(@NotNull TextRange input) { + return new TextRange(input.getStartOffset(), input.getEndOffset()); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { + if(!isAssistance(project)) { + return false; + } + + String text = extractText(element); + + if(text != null) { + KeyPathConverter converter = new KeyPathConverter(project); + existingTranslation = InstanceManager.get(project).store().getData() + .getTranslation(converter.fromString(text)) != null; + } + + return text != null; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) + throws IncorrectOperationException { + + ProjectSettings settings = ProjectSettingsService.get(project).getState(); + KeyPathConverter converter = new KeyPathConverter(settings); + + String text = extractText(element); + + if(text == null) { + throw new IncorrectOperationException("Cannot extract translation intention at caret"); + } + + TranslationData data = InstanceManager.get(project).store().getData(); + KeyPath path = converter.fromString(text); + TranslationValue existingTranslation = data.getTranslation(path); + + // Existing translation - edit dialog + if(existingTranslation != null) { + new EditDialog(project, new Translation(path, existingTranslation)).showAndHandle(); + return; + } + + // Extract translation by key + // We assume that a text is a translation-key if it contains section delimiters and does not end with them + if(text.contains(settings.getSectionDelimiter()) && !text.endsWith(settings.getSectionDelimiter())) { + new AddDialog(project, path, null).showAndHandle(); + return; + } + + // Extract translation by preview locale value + AddDialog dialog = new AddDialog(project, new KeyPath(), text); + + dialog.registerCallback(translationUpdate -> { // Replace text at caret with chosen translation key + if(editor != null) { + Document doc = editor.getDocument(); + Caret caret = editor.getCaretModel().getPrimaryCaret(); + TextRange range = convertRange(element.getTextRange()); + + WriteCommandAction.runWriteCommandAction(project, () -> + doc.replaceString(range.getStartOffset(), range.getEndOffset(), + converter.toString(translationUpdate.getChange().getKey()))); + + caret.removeSelection(); + } + }); + + dialog.showAndHandle(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/JavaTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/JavaTranslationIntention.java new file mode 100644 index 0000000..aed1b98 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/JavaTranslationIntention.java @@ -0,0 +1,27 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Java specific translation intention. + * @author marhali + */ +public class JavaTranslationIntention extends AbstractTranslationIntention { + @Override + protected @Nullable String extractText(@NotNull PsiElement element) { + if(!(element.getParent() instanceof PsiLiteralExpression)) { + return null; + } + + return String.valueOf(((PsiLiteralExpression) element.getParent()).getValue()); + } + + @Override + @NotNull TextRange convertRange(@NotNull TextRange input) { + return new TextRange(input.getStartOffset() + 1, input.getEndOffset() - 1); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/JsTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/JsTranslationIntention.java new file mode 100644 index 0000000..da526a5 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/JsTranslationIntention.java @@ -0,0 +1,28 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.lang.javascript.psi.JSLiteralExpression; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * JavaScript specific translation key intention. + * @author marhali + */ +public class JsTranslationIntention extends AbstractTranslationIntention { + @Override + protected @Nullable String extractText(@NotNull PsiElement element) { + if(!(element.getParent() instanceof JSLiteralExpression)) { + return null; + } + + return ((JSLiteralExpression) element.getParent()).getStringValue(); + } + + @Override + @NotNull TextRange convertRange(@NotNull TextRange input) { + return new TextRange(input.getStartOffset() + 1, input.getEndOffset() - 1); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/KtTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/KtTranslationIntention.java new file mode 100644 index 0000000..7ed3f31 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/KtTranslationIntention.java @@ -0,0 +1,23 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.psi.PsiElement; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry; + +/** + * Kotlin specific translation key intention. + * @author marhali + */ +public class KtTranslationIntention extends AbstractTranslationIntention { + @Override + protected @Nullable String extractText(@NotNull PsiElement element) { + if(!(element.getParent() instanceof KtLiteralStringTemplateEntry)) { + return null; + } + + KtLiteralStringTemplateEntry expression = (KtLiteralStringTemplateEntry) element.getParent(); + return expression.getText(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/PhpTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/PhpTranslationIntention.java new file mode 100644 index 0000000..76693a3 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/PhpTranslationIntention.java @@ -0,0 +1,23 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Php specific translation intention + * @author marhali + */ +public class PhpTranslationIntention extends AbstractTranslationIntention { + @Override + protected @Nullable String extractText(@NotNull PsiElement element) { + if(!(element.getParent() instanceof StringLiteralExpression)) { + return null; + } + + return ((StringLiteralExpression) element.getParent()).getContents(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/reference/AbstractKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/assistance/reference/AbstractKeyReferenceContributor.java new file mode 100644 index 0000000..b964a4c --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/reference/AbstractKeyReferenceContributor.java @@ -0,0 +1,53 @@ +package de.marhali.easyi18n.assistance.reference; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceContributor; +import de.marhali.easyi18n.InstanceManager; +import de.marhali.easyi18n.assistance.OptionalAssistance; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; +import de.marhali.easyi18n.util.KeyPathConverter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Language specific translation key reference contributor. + * @author marhali + */ +abstract class AbstractKeyReferenceContributor extends PsiReferenceContributor implements OptionalAssistance { + /** + * Searches for relevant translation-key references + * @param project Opened project + * @param element Targeted element + * @param text Designated translation key + * @return Matched translation-key reference(s) + */ + protected @NotNull PsiReference[] getReferences( + @NotNull Project project, @NotNull PsiElement element, @Nullable String text) { + + if(text == null || text.isEmpty() || !isAssistance(project)) { + return PsiReference.EMPTY_ARRAY; + } + + ProjectSettings settings = ProjectSettingsService.get(project).getState(); + KeyPathConverter converter = new KeyPathConverter(settings); + + // TODO: We should provide multiple references if not a leaf node was provided (contextual / plurals support) + + KeyPath path = converter.fromString(text); + TranslationValue values = InstanceManager.get(project).store().getData().getTranslation(path); + + if(values == null) { // We only reference valid and existing translations + return PsiReference.EMPTY_ARRAY; + } + + return new PsiReference[] { + new PsiKeyReference(converter, new Translation(path, values), element) + }; + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/reference/JavaKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/assistance/reference/JavaKeyReferenceContributor.java new file mode 100644 index 0000000..7d80e66 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/reference/JavaKeyReferenceContributor.java @@ -0,0 +1,41 @@ +package de.marhali.easyi18n.assistance.reference; + +import com.intellij.openapi.project.Project; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.*; + + +import com.intellij.util.ProcessingContext; + +import org.jetbrains.annotations.NotNull; + +/** + * Java specific key reference binding. + * @author marhali + */ +public class JavaKeyReferenceContributor extends AbstractKeyReferenceContributor { + + @Override + public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { + registrar.registerReferenceProvider( + PlatformPatterns.psiElement(PsiLiteralExpression.class), + getProvider()); + } + + private PsiReferenceProvider getProvider() { + return new PsiReferenceProvider() { + @Override + public PsiReference @NotNull [] getReferencesByElement( + @NotNull PsiElement element, @NotNull ProcessingContext context) { + + Project project = element.getProject(); + PsiLiteralExpression literalExpression = (PsiLiteralExpression) element; + String value = literalExpression.getValue() instanceof String + ? (String) literalExpression.getValue() + : null; + + return getReferences(project, element, value); + } + }; + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/reference/JsKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/assistance/reference/JsKeyReferenceContributor.java new file mode 100644 index 0000000..6edd656 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/reference/JsKeyReferenceContributor.java @@ -0,0 +1,39 @@ +package de.marhali.easyi18n.assistance.reference; + +import com.intellij.lang.javascript.psi.JSLiteralExpression; +import com.intellij.openapi.project.Project; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceProvider; +import com.intellij.psi.PsiReferenceRegistrar; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; + +/** + * JavaScript specific translation-key reference binding. + * @author marhali + */ +public class JsKeyReferenceContributor extends AbstractKeyReferenceContributor { + @Override + public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { + registrar.registerReferenceProvider( + PlatformPatterns.psiElement(JSLiteralExpression.class), + getProvider()); + } + + private PsiReferenceProvider getProvider() { + return new PsiReferenceProvider() { + @Override + public PsiReference @NotNull [] getReferencesByElement( + @NotNull PsiElement element, @NotNull ProcessingContext context) { + + Project project = element.getProject(); + JSLiteralExpression literalExpression = (JSLiteralExpression) element; + String value = literalExpression.getStringValue(); + + return getReferences(project, element, value); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/assistance/reference/KtKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/assistance/reference/KtKeyReferenceContributor.java new file mode 100644 index 0000000..dc1b0e1 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/reference/KtKeyReferenceContributor.java @@ -0,0 +1,46 @@ +package de.marhali.easyi18n.assistance.reference; + +import com.intellij.openapi.project.Project; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceProvider; +import com.intellij.psi.PsiReferenceRegistrar; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry; +import org.jetbrains.kotlin.psi.KtStringTemplateExpression; + +import java.util.Arrays; +import java.util.Optional; + +/** + * Kotlin specific translation-key reference binding. + * @author marhali + */ +public class KtKeyReferenceContributor extends AbstractKeyReferenceContributor { + @Override + public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { + registrar.registerReferenceProvider( + PlatformPatterns.psiElement().inside(KtStringTemplateExpression.class), + getProvider()); + } + + private PsiReferenceProvider getProvider() { + return new PsiReferenceProvider() { + @Override + public PsiReference @NotNull [] getReferencesByElement( + @NotNull PsiElement element, @NotNull ProcessingContext context) { + + Optional targetElement = Arrays.stream(element.getChildren()).filter(child -> + child instanceof KtLiteralStringTemplateEntry).findAny(); + + if(targetElement.isEmpty()) { + return PsiReference.EMPTY_ARRAY; + } + + return getReferences(element.getProject(), element, targetElement.get().getText()); + } + }; + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/reference/PhpKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/assistance/reference/PhpKeyReferenceContributor.java new file mode 100644 index 0000000..0d11599 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/reference/PhpKeyReferenceContributor.java @@ -0,0 +1,37 @@ +package de.marhali.easyi18n.assistance.reference; + +import com.intellij.openapi.project.Project; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceProvider; +import com.intellij.psi.PsiReferenceRegistrar; +import com.intellij.util.ProcessingContext; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; + +import org.jetbrains.annotations.NotNull; + +/** + * Php specific key reference binding + */ +public class PhpKeyReferenceContributor extends AbstractKeyReferenceContributor { + @Override + public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { + registrar.registerReferenceProvider( + PlatformPatterns.psiElement(StringLiteralExpression.class), + getProvider()); + } + + private PsiReferenceProvider getProvider() { + return new PsiReferenceProvider() { + @Override + public PsiReference @NotNull [] getReferencesByElement( + @NotNull PsiElement element, @NotNull ProcessingContext context) { + + Project project = element.getProject(); + StringLiteralExpression literalExpression = (StringLiteralExpression) element; + return getReferences(project, element, literalExpression.getContents()); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/assistance/reference/PsiKeyReference.java b/src/main/java/de/marhali/easyi18n/assistance/reference/PsiKeyReference.java new file mode 100644 index 0000000..58a45e7 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/reference/PsiKeyReference.java @@ -0,0 +1,72 @@ +package de.marhali.easyi18n.assistance.reference; + +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReferenceBase; +import com.intellij.psi.SyntheticElement; +import com.intellij.psi.impl.FakePsiElement; + +import de.marhali.easyi18n.dialog.AddDialog; +import de.marhali.easyi18n.dialog.EditDialog; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.util.KeyPathConverter; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * References translation keys inside editor with corresponding {@link EditDialog} / {@link AddDialog}. + * @author marhali + */ +public class PsiKeyReference extends PsiReferenceBase { + + private final @NotNull Translation translation; + private final @NotNull KeyPathConverter converter; + + protected PsiKeyReference( + @NotNull KeyPathConverter converter, @NotNull Translation translation, @NotNull PsiElement element) { + + super(element, true); + this.translation = translation; + this.converter = converter; + } + + public @NotNull String getKey() { + return converter.toString(translation.getKey()); + } + + @Override + public @Nullable PsiElement resolve() { + return new TranslationReference(); + } + + public class TranslationReference extends FakePsiElement implements SyntheticElement { + @Override + public PsiElement getParent() { + return myElement; + } + + @Override + public void navigate(boolean requestFocus) { + new EditDialog(getProject(), translation).showAndHandle(); + } + + @Override + public String getPresentableText() { + return getKey(); + } + + @Override + public String getName() { + return getKey(); + } + + @Override + public @Nullable TextRange getTextRange() { + TextRange rangeInElement = getRangeInElement(); + TextRange elementRange = myElement.getTextRange(); + return elementRange != null ? rangeInElement.shiftRight(elementRange.getStartOffset()) : rangeInElement; + } + } +} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/dialog/AddDialog.java b/src/main/java/de/marhali/easyi18n/dialog/AddDialog.java index dd886b0..84164e9 100644 --- a/src/main/java/de/marhali/easyi18n/dialog/AddDialog.java +++ b/src/main/java/de/marhali/easyi18n/dialog/AddDialog.java @@ -3,112 +3,65 @@ package de.marhali.easyi18n.dialog; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogBuilder; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.ui.components.JBTextField; -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.model.*; +import de.marhali.easyi18n.model.action.TranslationCreate; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.model.action.TranslationUpdate; +import de.marhali.easyi18n.settings.ProjectSettingsService; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.border.EtchedBorder; -import java.awt.*; -import java.util.HashMap; -import java.util.Map; -import java.util.ResourceBundle; /** - * Create translation dialog. + * Dialog to create a new translation with all associated locale values. + * Supports optional prefill technique for translation key or locale value. * @author marhali */ -public class AddDialog { +public class AddDialog extends TranslationDialog { - private final @NotNull Project project; - private final @NotNull KeyPathConverter converter; - - private @NotNull KeyPath preKey; - - private JBTextField keyTextField; - private Map valueTextFields; - - public AddDialog(@NotNull Project project, @Nullable KeyPath preKey) { - this(project); - this.preKey = preKey == null ? new KeyPath() : preKey; + /** + * Constructs a new create dialog with prefilled fields + * @param project Opened project + * @param prefillKey Prefill translation key + * @param prefillLocale Prefill preview locale value + */ + public AddDialog(@NotNull Project project, @Nullable KeyPath prefillKey, @Nullable String prefillLocale) { + super(project, new Translation(prefillKey != null ? prefillKey : new KeyPath(), + prefillLocale != null + ? new TranslationValue(ProjectSettingsService.get(project).getState().getPreviewLocale(), prefillLocale) + : null) + ); } + /** + * Constructs a new create dialog without prefilled fields. + * @param project Opened project + */ public AddDialog(@NotNull Project project) { - this.project = project; - this.converter = new KeyPathConverter(project); - this.preKey = new KeyPath(); + this(project, new KeyPath(), ""); } - public void showAndHandle() { - int code = prepare().show(); - - if(code == DialogWrapper.OK_EXIT_CODE) { - saveTranslation(); - } - } - - private void saveTranslation() { - Translation translation = new Translation(); - - valueTextFields.forEach((k, v) -> { - if(!v.getText().isEmpty()) { - translation.put(k, v.getText()); - } - }); - - KeyedTranslation keyedTranslation = new KeyedTranslation(converter.split(keyTextField.getText()), translation); - TranslationCreate creation = new TranslationCreate(keyedTranslation); - InstanceManager.get(project).processUpdate(creation); - } - - private DialogBuilder prepare() { - JPanel rootPanel = new JPanel(); - rootPanel.setLayout(new BoxLayout(rootPanel, BoxLayout.PAGE_AXIS)); - - JPanel keyPanel = new JPanel(new GridLayout(0, 1, 2, 2)); - JBLabel keyLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("translation.key")); - keyTextField = new JBTextField(this.converter.concat(this.preKey)); - keyLabel.setLabelFor(keyTextField); - keyPanel.add(keyLabel); - keyPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); - keyPanel.add(keyTextField); - rootPanel.add(keyPanel); - - if(!this.preKey.isEmpty()) { // Add delimiter if pre key is defined - keyTextField.setText(keyTextField.getText() + KeyPath.DELIMITER); - } - - JPanel valuePanel = new JPanel(new GridLayout(0, 1, 2, 2)); - valueTextFields = new HashMap<>(); - - for(String locale : InstanceManager.get(project).store().getData().getLocales()) { - JBLabel localeLabel = new JBLabel(locale); - JBTextField localeText = new JBTextField(); - localeLabel.setLabelFor(localeText); - - valuePanel.add(localeLabel); - valuePanel.add(localeText); - valueTextFields.put(locale, localeText); - } - - JBScrollPane valuePane = new JBScrollPane(valuePanel); - valuePane.setBorder(BorderFactory.createTitledBorder(new EtchedBorder(), - ResourceBundle.getBundle("messages").getString("translation.locales"))); - rootPanel.add(valuePane); + @Override + protected @NotNull DialogBuilder configure(@NotNull JComponent centerPanel) { DialogBuilder builder = new DialogBuilder(); - builder.setTitle(ResourceBundle.getBundle("messages").getString("action.add")); + builder.setTitle(bundle.getString("action.add")); builder.removeAllActions(); builder.addOkAction(); builder.addCancelAction(); - builder.setCenterPanel(rootPanel); - + builder.setCenterPanel(centerPanel); return builder; } -} \ No newline at end of file + + @Override + protected @Nullable TranslationUpdate handleExit(int exitCode) { + if(exitCode == DialogWrapper.OK_EXIT_CODE) { + return new TranslationCreate(getState()); + } + return null; + } +} diff --git a/src/main/java/de/marhali/easyi18n/dialog/EditDialog.java b/src/main/java/de/marhali/easyi18n/dialog/EditDialog.java index cc47e27..6c9662c 100644 --- a/src/main/java/de/marhali/easyi18n/dialog/EditDialog.java +++ b/src/main/java/de/marhali/easyi18n/dialog/EditDialog.java @@ -3,102 +3,53 @@ package de.marhali.easyi18n.dialog; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogBuilder; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.ui.components.JBTextField; -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.model.*; import de.marhali.easyi18n.dialog.descriptor.DeleteActionDescriptor; +import de.marhali.easyi18n.model.action.TranslationDelete; +import de.marhali.easyi18n.model.action.TranslationUpdate; +import de.marhali.easyi18n.model.Translation; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.border.EtchedBorder; -import java.awt.*; -import java.util.HashMap; -import java.util.Map; -import java.util.ResourceBundle; /** - * Edit translation dialog. + * Dialog to edit or delete an existing translation. * @author marhali */ -public class EditDialog { +public class EditDialog extends TranslationDialog { - private final Project project; - private final KeyPathConverter converter; - - private final KeyedTranslation origin; - - private JBTextField keyTextField; - private Map valueTextFields; - - public EditDialog(Project project, KeyedTranslation origin) { - this.project = project; - this.converter = new KeyPathConverter(project); - this.origin = origin; + /** + * Constructs a new edit dialog with the provided translation + * @param project Opened project + * @param origin Translation to edit + */ + public EditDialog(@NotNull Project project, @NotNull Translation origin) { + super(project, origin); } - public void showAndHandle() { - int code = prepare().show(); - - if(code == DialogWrapper.OK_EXIT_CODE) { // Edit - InstanceManager.get(project).processUpdate(new TranslationUpdate(origin, getChanges())); - } else if(code == DeleteActionDescriptor.EXIT_CODE) { // Delete - InstanceManager.get(project).processUpdate(new TranslationDelete(origin)); - } - } - - private KeyedTranslation getChanges() { - Translation translation = new Translation(); - - valueTextFields.forEach((k, v) -> { - if(!v.getText().isEmpty()) { - translation.put(k, v.getText()); - } - }); - - return new KeyedTranslation(converter.split(keyTextField.getText()), translation); - } - - private DialogBuilder prepare() { - JPanel rootPanel = new JPanel(); - rootPanel.setLayout(new BoxLayout(rootPanel, BoxLayout.PAGE_AXIS)); - - JPanel keyPanel = new JPanel(new GridLayout(0, 1, 2,2)); - JBLabel keyLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("translation.key")); - keyTextField = new JBTextField(this.converter.concat(this.origin.getKey())); - keyLabel.setLabelFor(keyTextField); - keyPanel.add(keyLabel); - keyPanel.add(keyTextField); - keyPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); - rootPanel.add(keyPanel); - - JPanel valuePanel = new JPanel(new GridLayout(0, 1, 2, 2)); - valueTextFields = new HashMap<>(); - - for(String locale : InstanceManager.get(project).store().getData().getLocales()) { - JBLabel localeLabel = new JBLabel(locale); - JBTextField localeText = new JBTextField(this.origin.getTranslation().get(locale)); - localeLabel.setLabelFor(localeText); - - valuePanel.add(localeLabel); - valuePanel.add(localeText); - valueTextFields.put(locale, localeText); - } - - JBScrollPane valuePane = new JBScrollPane(valuePanel); - valuePane.setBorder(BorderFactory.createTitledBorder(new EtchedBorder(), - ResourceBundle.getBundle("messages").getString("translation.locales"))); - rootPanel.add(valuePane); - + @Override + protected @NotNull DialogBuilder configure(@NotNull JComponent centerPanel) { DialogBuilder builder = new DialogBuilder(); - builder.setTitle(ResourceBundle.getBundle("messages").getString("action.edit")); + builder.setTitle(bundle.getString("action.edit")); builder.removeAllActions(); builder.addCancelAction(); builder.addActionDescriptor(new DeleteActionDescriptor()); builder.addOkAction(); - builder.setCenterPanel(rootPanel); - + builder.setCenterPanel(centerPanel); return builder; } + + @Override + protected @Nullable TranslationUpdate handleExit(int exitCode) { + switch (exitCode) { + case DialogWrapper.OK_EXIT_CODE: + return new TranslationUpdate(origin, getState()); + case DeleteActionDescriptor.EXIT_CODE: + return new TranslationDelete(origin); + default: + return null; + } + } } \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/dialog/SettingsDialog.java b/src/main/java/de/marhali/easyi18n/dialog/SettingsDialog.java deleted file mode 100644 index b45ca2f..0000000 --- a/src/main/java/de/marhali/easyi18n/dialog/SettingsDialog.java +++ /dev/null @@ -1,173 +0,0 @@ -package de.marhali.easyi18n.dialog; - -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.openapi.ui.DialogBuilder; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.TextFieldWithBrowseButton; -import com.intellij.ui.components.JBCheckBox; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBPanel; -import com.intellij.ui.components.JBTextField; - -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.io.parser.ArrayMapper; -import de.marhali.easyi18n.model.FolderStrategyType; -import de.marhali.easyi18n.model.SettingsState; -import de.marhali.easyi18n.io.parser.ParserStrategyType; -import de.marhali.easyi18n.service.SettingsService; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.util.ResourceBundle; - -/** - * Plugin configuration dialog. - * @author marhali - */ -public class SettingsDialog { - - private final Project project; - - private TextFieldWithBrowseButton pathText; - private ComboBox folderStrategyComboBox; - private ComboBox parserStrategyComboBox; - private JBTextField filePatternText; - private JBTextField previewLocaleText; - private JBTextField pathPrefixText; - private JBCheckBox sortKeysCheckbox; - private JBCheckBox nestedKeysCheckbox; - private JBCheckBox codeAssistanceCheckbox; - - public SettingsDialog(Project project) { - this.project = project; - } - - public void showAndHandle() { - SettingsState state = SettingsService.getInstance(project).getState(); - - if(prepare(state).show() == DialogWrapper.OK_EXIT_CODE) { // Save changes - state.setLocalesPath(pathText.getText()); - state.setFolderStrategy(FolderStrategyType.fromIndex(folderStrategyComboBox.getSelectedIndex())); - state.setParserStrategy(ParserStrategyType.fromIndex(parserStrategyComboBox.getSelectedIndex())); - state.setFilePattern(filePatternText.getText()); - state.setPreviewLocale(previewLocaleText.getText()); - state.setPathPrefix(pathPrefixText.getText()); - state.setSortKeys(sortKeysCheckbox.isSelected()); - state.setNestedKeys(nestedKeysCheckbox.isSelected()); - state.setCodeAssistance(codeAssistanceCheckbox.isSelected()); - - // Reload instance - InstanceManager manager = InstanceManager.get(project); - manager.store().loadFromPersistenceLayer((success) -> - manager.bus().propagate().onUpdateData(manager.store().getData())); - } - } - - private DialogBuilder prepare(SettingsState state) { - ResourceBundle bundle = ResourceBundle.getBundle("messages"); - JPanel rootPanel = new JPanel(new GridLayout(0, 1, 2, 2)); - - /* path */ - JBLabel pathLabel = new JBLabel(bundle.getString("settings.path.text")); - pathText = new TextFieldWithBrowseButton(new JTextField(state.getLocalesPath())); - - pathLabel.setLabelFor(pathText); - pathText.addBrowseFolderListener(bundle.getString("settings.path.title"), null, project, new FileChooserDescriptor( - false, true, false, false, false, false)); - - rootPanel.add(pathLabel); - rootPanel.add(pathText); - - JBLabel strategyLabel = new JBLabel(bundle.getString("settings.strategy.title")); - rootPanel.add(strategyLabel); - - JPanel strategyPanel = new JBPanel<>(new GridBagLayout()); - rootPanel.add(strategyPanel); - GridBagConstraints constraints = new GridBagConstraints(); - - /* folder strategy */ - folderStrategyComboBox = new ComboBox<>(bundle.getString("settings.strategy.folder").split(ArrayMapper.SPLITERATOR_REGEX)); - folderStrategyComboBox.setSelectedIndex(state.getFolderStrategy().toIndex()); - folderStrategyComboBox.setToolTipText(bundle.getString("settings.strategy.folder.tooltip")); - folderStrategyComboBox.setMinimumAndPreferredWidth(256); - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.gridx = 0; - constraints.gridy = 0; - strategyPanel.add(folderStrategyComboBox, constraints); - - /* parser strategy */ - parserStrategyComboBox = new ComboBox<>(bundle.getString("settings.strategy.parser").split(ArrayMapper.SPLITERATOR_REGEX)); - parserStrategyComboBox.setSelectedIndex(state.getParserStrategy().toIndex()); - parserStrategyComboBox.setToolTipText(bundle.getString("settings.strategy.parser.tooltip")); - parserStrategyComboBox.addItemListener(handleParserChange()); - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.gridx = 1; - constraints.gridy = 0; - strategyPanel.add(parserStrategyComboBox, constraints); - - /* file pattern strategy */ - filePatternText = new JBTextField(state.getFilePattern()); - filePatternText.setToolTipText(bundle.getString("settings.strategy.file-pattern.tooltip")); - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.gridx = 2; - constraints.gridy = 0; - constraints.weightx = 1; - strategyPanel.add(filePatternText, constraints); - - /* preview locale */ - JBLabel previewLocaleLabel = new JBLabel(bundle.getString("settings.preview")); - previewLocaleText = new JBTextField(state.getPreviewLocale()); - previewLocaleLabel.setLabelFor(previewLocaleText); - - rootPanel.add(previewLocaleLabel); - rootPanel.add(previewLocaleText); - - /* path prefix */ - JBLabel pathPrefixLabel = new JBLabel(bundle.getString("settings.path.prefix")); - pathPrefixText = new JBTextField(state.getPathPrefix()); - - rootPanel.add(pathPrefixLabel); - rootPanel.add(pathPrefixText); - - /* sort keys */ - sortKeysCheckbox = new JBCheckBox(bundle.getString("settings.keys.sort")); - sortKeysCheckbox.setSelected(state.isSortKeys()); - - rootPanel.add(sortKeysCheckbox); - - /* nested keys */ - nestedKeysCheckbox = new JBCheckBox(bundle.getString("settings.keys.nested")); - nestedKeysCheckbox.setSelected(state.isNestedKeys()); - - rootPanel.add(nestedKeysCheckbox); - - /* code assistance */ - codeAssistanceCheckbox = new JBCheckBox(bundle.getString("settings.editor.assistance")); - codeAssistanceCheckbox.setSelected(state.isCodeAssistance()); - - rootPanel.add(codeAssistanceCheckbox); - - DialogBuilder builder = new DialogBuilder(); - builder.setTitle(bundle.getString("action.settings")); - builder.removeAllActions(); - builder.addCancelAction(); - builder.addOkAction(); - builder.setCenterPanel(rootPanel); - - return builder; - } - - private ItemListener handleParserChange() { - return e -> { - if(e.getStateChange() == ItemEvent.SELECTED) { - // Automatically suggest file pattern option on parser change - ParserStrategyType newStrategy = ParserStrategyType.fromIndex(parserStrategyComboBox.getSelectedIndex()); - filePatternText.setText(newStrategy.getExampleFilePattern()); - } - }; - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/dialog/TranslationDialog.java b/src/main/java/de/marhali/easyi18n/dialog/TranslationDialog.java new file mode 100644 index 0000000..306e5ba --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/dialog/TranslationDialog.java @@ -0,0 +1,148 @@ +package de.marhali.easyi18n.dialog; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogBuilder; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.Consumer; +import com.intellij.util.ui.FormBuilder; + +import de.marhali.easyi18n.InstanceManager; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.model.action.TranslationUpdate; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; +import de.marhali.easyi18n.util.KeyPathConverter; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.EtchedBorder; +import java.awt.*; +import java.util.*; + +/** + * Base for add and edit translation dialogs. + * @author marhali + */ +abstract class TranslationDialog { + + protected static final ResourceBundle bundle = ResourceBundle.getBundle("messages"); + + protected final @NotNull Project project; + protected final @NotNull ProjectSettings settings; + protected final @NotNull KeyPathConverter converter; + protected final @NotNull Translation origin; + + protected final JTextField keyField; + protected final Map localeValueFields; + + private final Set> callbacks; + + /** + * Constructs a new translation dialog. + * @param project Opened project + * @param origin Prefill translation + */ + protected TranslationDialog(@NotNull Project project, @NotNull Translation origin) { + this.project = project; + this.settings = ProjectSettingsService.get(project).getState(); + this.converter = new KeyPathConverter(settings); + this.origin = origin; + + this.callbacks = new HashSet<>(); + + // Fields + TranslationValue value = origin.getValue(); + + this.keyField = new JBTextField(converter.toString(origin.getKey())); + this.localeValueFields = new HashMap<>(); + + for(String locale : InstanceManager.get(project).store().getData().getLocales()) { + localeValueFields.put(locale, new JBTextField(value != null ? value.get(locale) : null)); + } + } + + /** + * Registers a callback that is called on dialog close with the final state. + * If the user aborts the dialog no callback is called. + * @param callback Callback to register + */ + public void registerCallback(Consumer callback) { + callbacks.add(callback); + } + + /** + * Implementation needs to configure the dialog. E.g. title, actions, ... + * The implementation needs to set the provided centerPanel as the view panel. + * @param centerPanel GUI to set on the dialog builder + * @return configured dialog builder + */ + protected abstract @NotNull DialogBuilder configure(@NotNull JComponent centerPanel); + + /** + * Implementation needs to handle exit + * @param exitCode See {@link com.intellij.openapi.ui.DialogWrapper} for exit codes + * @return update conclusion, null if aborted + */ + protected abstract @Nullable TranslationUpdate handleExit(int exitCode); + + /** + * Opens the translation modal and applies the appropriate logic on modal close. + * Internally, the {@link #handleExit(int)} method will be called to determine finalization logic. + */ + public void showAndHandle() { + int exitCode = createDialog().show(); + TranslationUpdate update = handleExit(exitCode); + + if(update != null) { + InstanceManager.get(project).processUpdate(update); + callbacks.forEach(callback -> callback.consume(update)); + } + } + + /** + * Retrieve current modal state. + * @return Translation + */ + protected @NotNull Translation getState() { + KeyPath key = converter.fromString(keyField.getText()); + + TranslationValue value = new TranslationValue(); + + for(Map.Entry entry : localeValueFields.entrySet()) { + value.put(entry.getKey(), entry.getValue().getText()); + } + + return new Translation(key, value); + } + + private DialogBuilder createDialog() { + JPanel panel = FormBuilder.createFormBuilder() + .addLabeledComponent(bundle.getString("translation.key"), keyField, true) + .addComponent(createLocalesPanel(), 12) + .getPanel(); + + panel.setMinimumSize(new Dimension(200, 150)); + + return configure(panel); + } + + private JComponent createLocalesPanel() { + FormBuilder builder = FormBuilder.createFormBuilder(); + + for(Map.Entry localeEntry : localeValueFields.entrySet()) { + builder.addLabeledComponent(localeEntry.getKey(), localeEntry.getValue(), 6, true); + } + + JScrollPane scrollPane = new JBScrollPane(builder.getPanel()); + + scrollPane.setBorder(BorderFactory.createTitledBorder( + new EtchedBorder(), bundle.getString("translation.locales"))); + + return scrollPane; + } +} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/editor/KeyAnnotator.java b/src/main/java/de/marhali/easyi18n/editor/KeyAnnotator.java deleted file mode 100644 index 919a97a..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/KeyAnnotator.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.marhali.easyi18n.editor; - -import com.intellij.lang.annotation.AnnotationHolder; -import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.openapi.project.Project; - -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.KeyPathConverter; -import de.marhali.easyi18n.model.SettingsState; -import de.marhali.easyi18n.model.TranslationNode; -import de.marhali.easyi18n.service.SettingsService; - -import org.jetbrains.annotations.NotNull; - -/** - * Superclass for managing key annotations. - * @author marhali - */ -public class KeyAnnotator { - - /** - * Adds annotations for i18n keys with content preview for preferred locale. - * @param key I18n key extracted by psi element - * @param project Project instance - * @param holder Annotation holder - */ - protected void annotate(@NotNull String key, @NotNull Project project, @NotNull AnnotationHolder holder) { - // Do not annotate keys if service is disabled - if(!SettingsService.getInstance(project).getState().isCodeAssistance()) { - return; - } - - SettingsState state = SettingsService.getInstance(project).getState(); - String pathPrefix = state.getPathPrefix(); - String previewLocale = state.getPreviewLocale(); - - KeyPathConverter converter = new KeyPathConverter(project); - - String searchKey = key.length() >= pathPrefix.length() - ? key.substring(pathPrefix.length()) - : key; - - if(searchKey.startsWith(KeyPath.DELIMITER)) { - searchKey = searchKey.substring(KeyPath.DELIMITER.length()); - } - - TranslationNode node = InstanceManager.get(project).store().getData().getNode(converter.split(searchKey)); - - 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(); - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java b/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java deleted file mode 100644 index c866428..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/KeyCompletionProvider.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.marhali.easyi18n.editor; - -import com.intellij.codeInsight.completion.*; -import com.intellij.codeInsight.lookup.*; -import com.intellij.icons.AllIcons; -import com.intellij.openapi.project.*; -import com.intellij.util.*; - -import de.marhali.easyi18n.DataStore; -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.Translation; -import de.marhali.easyi18n.service.*; - -import org.jetbrains.annotations.*; - -import java.util.*; - -/** - * I18n translation key completion provider. - * @author marhali - */ -public class KeyCompletionProvider extends CompletionProvider { - - @Override - protected void addCompletions(@NotNull CompletionParameters parameters, - @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { - - Project project = parameters.getOriginalFile().getProject(); - - // Do not annotate keys if service is disabled - if(!SettingsService.getInstance(project).getState().isCodeAssistance()) { - return; - } - - DataStore store = InstanceManager.get(project).store(); - - String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale(); - String pathPrefix = SettingsService.getInstance(project).getState().getPathPrefix(); - - if(pathPrefix.length() > 0 && !pathPrefix.endsWith(KeyPath.DELIMITER)) { - pathPrefix += KeyPath.DELIMITER; - } - - Set fullKeys = store.getData().getFullKeys(); - - for(KeyPath currentKey : fullKeys) { - result.addElement(createElement( - pathPrefix, - currentKey, - previewLocale, - Objects.requireNonNull(store.getData().getTranslation(currentKey)) - )); - } - } - - private LookupElement createElement(String prefix, KeyPath path, String locale, Translation translation) { - return LookupElementBuilder.create(prefix + path.toSimpleString()) - .withIcon(AllIcons.Actions.PreserveCaseHover) - .appendTailText(" I18n(" + locale + ": " + translation.get(locale) + ")", true); - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/editor/KeyReference.java b/src/main/java/de/marhali/easyi18n/editor/KeyReference.java deleted file mode 100644 index d848bc1..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/KeyReference.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.marhali.easyi18n.editor; - -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; -import com.intellij.psi.impl.FakePsiElement; - -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.dialog.AddDialog; -import de.marhali.easyi18n.dialog.EditDialog; -import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.KeyPathConverter; -import de.marhali.easyi18n.model.KeyedTranslation; -import de.marhali.easyi18n.model.Translation; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Go to declaration reference for i18n keys. - * @author marhali - */ -public class KeyReference extends PsiReferenceBase { - - @Nullable private final String myKey; - - public KeyReference(@NotNull final PsiElement element) { - this(element, (String)null); - } - - public KeyReference(@NotNull final PsiElement element, @Nullable final String myKey) { - super(element, true); - this.myKey = myKey; - } - - public KeyReference(@NotNull final PsiElement element, @NotNull TextRange textRange) { - this(element, textRange, null); - } - - public KeyReference(@NotNull PsiElement element, TextRange textRange, @Nullable String myKey) { - super(element, textRange, true); - this.myKey = myKey; - } - - @Override - public @Nullable PsiElement resolve() { - return new TranslationKey(); - } - - public String getKey() { - return myKey != null ? myKey : getValue(); - } - - class TranslationKey extends FakePsiElement implements SyntheticElement { - @Override - public PsiElement getParent() { - return myElement; - } - - @Override - public void navigate(boolean requestFocus) { - KeyPathConverter converter = new KeyPathConverter(getProject()); - KeyPath path = converter.split(getKey()); - Translation translation = InstanceManager.get(getProject()).store().getData().getTranslation(path); - - if(translation != null) { - new EditDialog(getProject(), new KeyedTranslation(path, translation)).showAndHandle(); - } else { - new AddDialog(getProject(), path).showAndHandle(); - } - } - - @Override - public String getPresentableText() { - return getKey(); - } - - @Override - public String getName() { - return getKey(); - } - - @Override - public @Nullable TextRange getTextRange() { - final TextRange rangeInElement = getRangeInElement(); - final TextRange elementRange = myElement.getTextRange(); - return elementRange != null ? rangeInElement.shiftRight(elementRange.getStartOffset()) : rangeInElement; - } - } - - public static boolean isReferencable(String value) { - return value.matches("^[^\\s:/]+$"); - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/editor/generic/GenericFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/editor/generic/GenericFoldingBuilder.java deleted file mode 100644 index cbda752..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/generic/GenericFoldingBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -package de.marhali.easyi18n.editor.generic; - -import com.intellij.lang.ASTNode; -import com.intellij.lang.folding.FoldingBuilderEx; -import com.intellij.lang.folding.FoldingDescriptor; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiLiteralValue; -import com.intellij.psi.util.PsiTreeUtil; - -import de.marhali.easyi18n.DataStore; -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.model.KeyPathConverter; -import de.marhali.easyi18n.model.Translation; -import de.marhali.easyi18n.service.SettingsService; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Translation key folding with actual value based on i18n instance. - * @author marhali - */ -public class GenericFoldingBuilder extends FoldingBuilderEx { - - @Override - public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) { - Collection literalValues = PsiTreeUtil.findChildrenOfType(root, PsiLiteralValue.class); - List descriptors = new ArrayList<>(); - - if(!SettingsService.getInstance(root.getProject()).getState().isCodeAssistance()) { - return FoldingDescriptor.EMPTY; - } - - DataStore store = InstanceManager.get(root.getProject()).store(); - KeyPathConverter converter = new KeyPathConverter(root.getProject()); - - for(final PsiLiteralValue literalValue : literalValues) { - String value = literalValue.getValue() instanceof String ? (String) literalValue.getValue() : null; - - // Undefined string literal or not a translation - if(value == null || store.getData().getTranslation(converter.split(value)) == null) { - continue; - } - - descriptors.add(new FoldingDescriptor(literalValue.getNode(), - new TextRange(literalValue.getTextRange().getStartOffset() + 1, - literalValue.getTextRange().getEndOffset() - 1))); - } - - return descriptors.toArray(new FoldingDescriptor[0]); - } - - @Nullable - @Override - public String getPlaceholderText(@NotNull ASTNode node) { - PsiLiteralValue literalValue = node.getPsi(PsiLiteralValue.class); - String value = literalValue.getValue() instanceof String ? (String) literalValue.getValue() : null; - - if(value == null) { - return null; - } - - DataStore store = InstanceManager.get(literalValue.getProject()).store(); - KeyPathConverter converter = new KeyPathConverter(literalValue.getProject()); - - Translation translation = store.getData().getTranslation(converter.split(value)); - - if(translation == null) { - return null; - } - - String previewLocale = SettingsService.getInstance(literalValue.getProject()).getState().getPreviewLocale(); - - return translation.get(previewLocale); - } - - @Override - public boolean isCollapsedByDefault(@NotNull ASTNode node) { - return true; - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyAnnotator.java b/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyAnnotator.java deleted file mode 100644 index 8de8149..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyAnnotator.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.marhali.easyi18n.editor.generic; - -import com.intellij.lang.annotation.AnnotationHolder; -import com.intellij.lang.annotation.Annotator; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiLiteralValue; - -import de.marhali.easyi18n.editor.KeyAnnotator; - -import org.jetbrains.annotations.NotNull; - -/** - * Translation key annotator for generic languages which support {@link PsiLiteralValue}. - * @author marhali - */ -public class GenericKeyAnnotator extends KeyAnnotator implements Annotator { - - @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; - } - - annotate(value, element.getProject(), holder); - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java b/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java deleted file mode 100644 index a8ed832..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyCompletionContributor.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.marhali.easyi18n.editor.generic; - -import com.intellij.codeInsight.completion.CompletionContributor; -import com.intellij.codeInsight.completion.CompletionType; -import com.intellij.patterns.*; -import com.intellij.psi.*; -import com.intellij.psi.xml.*; -import de.marhali.easyi18n.editor.KeyCompletionProvider; - -/** - * Translation key completion for generic languages which support {@link PsiLiteralValue}. - * @author marhali - */ -public class GenericKeyCompletionContributor extends CompletionContributor { - - public GenericKeyCompletionContributor() { - extend(CompletionType.BASIC, PlatformPatterns.psiElement(PlainTextTokenTypes.PLAIN_TEXT), - new KeyCompletionProvider()); - extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(XmlElement.class), - new KeyCompletionProvider()); - extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralValue.class), - new KeyCompletionProvider()); - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyReferenceContributor.java deleted file mode 100644 index 5127178..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/generic/GenericKeyReferenceContributor.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.marhali.easyi18n.editor.generic; - -import com.intellij.patterns.PlatformPatterns; -import com.intellij.psi.*; -import com.intellij.util.ProcessingContext; - -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.editor.KeyReference; -import de.marhali.easyi18n.model.KeyPathConverter; -import de.marhali.easyi18n.service.SettingsService; - -import org.jetbrains.annotations.NotNull; - -/** - * Generic translation key reference contributor. - * @author marhali - */ -public class GenericKeyReferenceContributor extends PsiReferenceContributor { - @Override - public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { - registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralValue.class), getProvider()); - } - - private PsiReferenceProvider getProvider() { - return new PsiReferenceProvider() { - @Override - public PsiReference @NotNull [] getReferencesByElement( - @NotNull PsiElement element, @NotNull ProcessingContext context) { - - PsiLiteralValue literalValue = (PsiLiteralValue) element; - String value = literalValue.getValue() instanceof String ? (String) literalValue.getValue() : null; - - if(value == null) { - return PsiReference.EMPTY_ARRAY; - } - - // Do not reference keys if service is disabled - if(!SettingsService.getInstance(element.getProject()).getState().isCodeAssistance()) { - return PsiReference.EMPTY_ARRAY; - } - - KeyPathConverter converter = new KeyPathConverter(element.getProject()); - - if(InstanceManager.get(element.getProject()).store().getData().getTranslation(converter.split(value)) == null) { - if(!KeyReference.isReferencable(value)) { // Creation policy - return PsiReference.EMPTY_ARRAY; - } - } - - return new PsiReference[] { new KeyReference(element, value) }; - } - }; - } -} diff --git a/src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyAnnotator.java b/src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyAnnotator.java deleted file mode 100644 index 4e9cfe3..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyAnnotator.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.marhali.easyi18n.editor.kotlin; - -import com.intellij.lang.annotation.AnnotationHolder; -import com.intellij.lang.annotation.Annotator; -import com.intellij.psi.PsiElement; - -import de.marhali.easyi18n.editor.KeyAnnotator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry; - -/** - * Kotlin specific translation key annotator - * @author marhali - */ -public class KotlinKeyAnnotator extends KeyAnnotator implements Annotator { - - @Override - public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { - if(!(element instanceof KtLiteralStringTemplateEntry)) { - return; - } - - String value = element.getText(); - - if(value == null) { - return; - } - - annotate(value, element.getProject(), holder); - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyReferenceContributor.java deleted file mode 100644 index bbb9203..0000000 --- a/src/main/java/de/marhali/easyi18n/editor/kotlin/KotlinKeyReferenceContributor.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.marhali.easyi18n.editor.kotlin; - -import com.intellij.patterns.PlatformPatterns; -import com.intellij.psi.*; - -import com.intellij.util.ProcessingContext; - -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.editor.KeyReference; -import de.marhali.easyi18n.model.KeyPathConverter; -import de.marhali.easyi18n.service.SettingsService; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry; -import org.jetbrains.kotlin.psi.KtStringTemplateExpression; - -/** - * Kotlin translation key reference contributor. - * @author marhali - */ -public class KotlinKeyReferenceContributor extends PsiReferenceContributor { - - @Override - public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { - registrar.registerReferenceProvider(PlatformPatterns.psiElement().inside(KtStringTemplateExpression.class), getProvider()); - } - - private PsiReferenceProvider getProvider() { - return new PsiReferenceProvider() { - @Override - public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { - String value = null; - - for (PsiElement child : element.getChildren()) { - if(child instanceof KtLiteralStringTemplateEntry) { - value = child.getText(); - } - } - - if(value == null) { - return PsiReference.EMPTY_ARRAY; - } - - // Do not reference keys if service is disabled - if(!SettingsService.getInstance(element.getProject()).getState().isCodeAssistance()) { - return PsiReference.EMPTY_ARRAY; - } - - KeyPathConverter converter = new KeyPathConverter(element.getProject()); - - if(InstanceManager.get(element.getProject()).store().getData().getNode(converter.split(value)) == null) { - return PsiReference.EMPTY_ARRAY; - } - - return new PsiReference[] { new KeyReference(element, value) }; - } - }; - } -} diff --git a/src/main/java/de/marhali/easyi18n/io/IOHandler.java b/src/main/java/de/marhali/easyi18n/io/IOHandler.java index cebe902..dfdf6ab 100644 --- a/src/main/java/de/marhali/easyi18n/io/IOHandler.java +++ b/src/main/java/de/marhali/easyi18n/io/IOHandler.java @@ -9,6 +9,7 @@ import de.marhali.easyi18n.io.parser.ParserStrategy; import de.marhali.easyi18n.io.parser.ParserStrategyType; import de.marhali.easyi18n.model.*; +import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -21,23 +22,23 @@ import java.util.List; */ public class IOHandler { - private final @NotNull SettingsState settings; + private final @NotNull ProjectSettings settings; private final @NotNull FolderStrategy folderStrategy; private final @NotNull ParserStrategyType parserStrategyType; private final @NotNull ParserStrategy parserStrategy; - public IOHandler(@NotNull SettingsState settings) throws Exception { + public IOHandler(@NotNull ProjectSettings settings) throws Exception { this.settings = settings; this.folderStrategy = settings.getFolderStrategy().getStrategy() - .getDeclaredConstructor(SettingsState.class).newInstance(settings); + .getDeclaredConstructor(ProjectSettings.class).newInstance(settings); this.parserStrategyType = settings.getParserStrategy(); this.parserStrategy = parserStrategyType.getStrategy() - .getDeclaredConstructor(SettingsState.class).newInstance(settings); + .getDeclaredConstructor(ProjectSettings.class).newInstance(settings); } /** @@ -47,7 +48,7 @@ public class IOHandler { * @throws IOException Could not read translation data */ public @NotNull TranslationData read() throws IOException { - String localesPath = this.settings.getLocalesPath(); + String localesPath = this.settings.getLocalesDirectory(); if(localesPath == null || localesPath.isEmpty()) { throw new EmptyLocalesDirException("Locales path must not be empty"); @@ -59,7 +60,7 @@ public class IOHandler { throw new IllegalArgumentException("Specified locales path is invalid (" + localesPath + ")"); } - TranslationData data = new TranslationData(this.settings.isSortKeys()); + TranslationData data = new TranslationData(this.settings.isSorting()); List translationFiles = this.folderStrategy.analyzeFolderStructure(localesDirectory); for(TranslationFile file : translationFiles) { @@ -80,7 +81,7 @@ public class IOHandler { * @throws IOException Write action failed */ public void write(@NotNull TranslationData data) throws IOException { - String localesPath = this.settings.getLocalesPath(); + String localesPath = this.settings.getLocalesDirectory(); if(localesPath == null || localesPath.isEmpty()) { throw new EmptyLocalesDirException("Locales path must not be empty"); diff --git a/src/main/java/de/marhali/easyi18n/io/folder/FolderStrategy.java b/src/main/java/de/marhali/easyi18n/io/folder/FolderStrategy.java index dbdd316..5e95367 100644 --- a/src/main/java/de/marhali/easyi18n/io/folder/FolderStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/folder/FolderStrategy.java @@ -4,10 +4,10 @@ import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategyType; -import de.marhali.easyi18n.model.SettingsState; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; +import de.marhali.easyi18n.settings.ProjectSettings; import org.apache.commons.io.FilenameUtils; import org.jetbrains.annotations.NotNull; @@ -22,9 +22,9 @@ import java.util.Objects; */ public abstract class FolderStrategy { - protected final @NotNull SettingsState settings; + protected final @NotNull ProjectSettings settings; - public FolderStrategy(@NotNull SettingsState settings) { + public FolderStrategy(@NotNull ProjectSettings settings) { this.settings = settings; } diff --git a/src/main/java/de/marhali/easyi18n/model/FolderStrategyType.java b/src/main/java/de/marhali/easyi18n/io/folder/FolderStrategyType.java similarity index 52% rename from src/main/java/de/marhali/easyi18n/model/FolderStrategyType.java rename to src/main/java/de/marhali/easyi18n/io/folder/FolderStrategyType.java index 14e81f3..047a563 100644 --- a/src/main/java/de/marhali/easyi18n/model/FolderStrategyType.java +++ b/src/main/java/de/marhali/easyi18n/io/folder/FolderStrategyType.java @@ -1,23 +1,24 @@ -package de.marhali.easyi18n.model; - -import de.marhali.easyi18n.io.folder.FolderStrategy; -import de.marhali.easyi18n.io.folder.ModularLocaleFolderStrategy; -import de.marhali.easyi18n.io.folder.ModularNamespaceFolderStrategy; -import de.marhali.easyi18n.io.folder.SingleFolderStrategy; +package de.marhali.easyi18n.io.folder; /** * Represents all supported folder strategies. * @author marhali */ public enum FolderStrategyType { - SINGLE(SingleFolderStrategy.class), - MODULARIZED_LOCALE(ModularLocaleFolderStrategy.class), - MODULARIZED_NAMESPACE(ModularNamespaceFolderStrategy.class); + SINGLE(SingleFolderStrategy.class, false), + MODULARIZED_LOCALE(ModularLocaleFolderStrategy.class, true), + MODULARIZED_NAMESPACE(ModularNamespaceFolderStrategy.class, true); private final Class strategy; + private final boolean namespaceMode; - FolderStrategyType(Class strategy) { + /** + * @param strategy Strategy implementation + * @param namespaceMode Does this strategy use namespaces? + */ + FolderStrategyType(Class strategy, boolean namespaceMode) { this.strategy = strategy; + this.namespaceMode = namespaceMode; } public Class getStrategy() { @@ -38,6 +39,10 @@ public enum FolderStrategyType { throw new NullPointerException(); } + public boolean isNamespaceMode() { + return namespaceMode; + } + public static FolderStrategyType fromIndex(int index) { return values()[index]; } diff --git a/src/main/java/de/marhali/easyi18n/io/folder/ModularLocaleFolderStrategy.java b/src/main/java/de/marhali/easyi18n/io/folder/ModularLocaleFolderStrategy.java index e626827..651c9ea 100644 --- a/src/main/java/de/marhali/easyi18n/io/folder/ModularLocaleFolderStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/folder/ModularLocaleFolderStrategy.java @@ -3,9 +3,9 @@ package de.marhali.easyi18n.io.folder; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategyType; -import de.marhali.easyi18n.model.SettingsState; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; +import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; @@ -20,7 +20,7 @@ import java.util.List; */ public class ModularLocaleFolderStrategy extends FolderStrategy { - public ModularLocaleFolderStrategy(@NotNull SettingsState settings) { + public ModularLocaleFolderStrategy(@NotNull ProjectSettings settings) { super(settings); } diff --git a/src/main/java/de/marhali/easyi18n/io/folder/ModularNamespaceFolderStrategy.java b/src/main/java/de/marhali/easyi18n/io/folder/ModularNamespaceFolderStrategy.java index 95b6fcd..fe8ffae 100644 --- a/src/main/java/de/marhali/easyi18n/io/folder/ModularNamespaceFolderStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/folder/ModularNamespaceFolderStrategy.java @@ -3,9 +3,9 @@ package de.marhali.easyi18n.io.folder; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategyType; -import de.marhali.easyi18n.model.SettingsState; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; +import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; @@ -20,7 +20,7 @@ import java.util.List; */ public class ModularNamespaceFolderStrategy extends FolderStrategy { - public ModularNamespaceFolderStrategy(@NotNull SettingsState settings) { + public ModularNamespaceFolderStrategy(@NotNull ProjectSettings settings) { super(settings); } diff --git a/src/main/java/de/marhali/easyi18n/io/folder/SingleFolderStrategy.java b/src/main/java/de/marhali/easyi18n/io/folder/SingleFolderStrategy.java index 42260a6..4ac6371 100644 --- a/src/main/java/de/marhali/easyi18n/io/folder/SingleFolderStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/folder/SingleFolderStrategy.java @@ -3,9 +3,9 @@ package de.marhali.easyi18n.io.folder; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategyType; -import de.marhali.easyi18n.model.SettingsState; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; +import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; @@ -22,7 +22,7 @@ import java.util.List; */ public class SingleFolderStrategy extends FolderStrategy { - public SingleFolderStrategy(@NotNull SettingsState settings) { + public SingleFolderStrategy(@NotNull ProjectSettings settings) { super(settings); } diff --git a/src/main/java/de/marhali/easyi18n/io/parser/ParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/ParserStrategy.java index 1678569..b94c0b3 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/ParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/ParserStrategy.java @@ -2,6 +2,8 @@ package de.marhali.easyi18n.io.parser; import de.marhali.easyi18n.model.*; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -12,9 +14,9 @@ import java.util.Objects; */ public abstract class ParserStrategy { - protected final @NotNull SettingsState settings; + protected final @NotNull ProjectSettings settings; - public ParserStrategy(@NotNull SettingsState settings) { + public ParserStrategy(@NotNull ProjectSettings settings) { this.settings = settings; } @@ -45,10 +47,10 @@ public abstract class ParserStrategy { if(file.getNamespace() != null) { String moduleName = file.getNamespace(); - TranslationNode moduleNode = data.getNode(KeyPath.of(moduleName)); + TranslationNode moduleNode = data.getNode(new KeyPath(moduleName)); if(moduleNode == null) { - moduleNode = new TranslationNode(this.settings.isSortKeys()); + moduleNode = new TranslationNode(this.settings.isSorting()); data.getRootNode().setChildren(moduleName, moduleNode); } @@ -68,7 +70,7 @@ public abstract class ParserStrategy { TranslationNode targetNode = data.getRootNode(); if(file.getNamespace() != null) { - targetNode = data.getNode(KeyPath.of(file.getNamespace())); + targetNode = data.getNode(new KeyPath(file.getNamespace())); } return Objects.requireNonNull(targetNode); diff --git a/src/main/java/de/marhali/easyi18n/io/parser/json/JsonMapper.java b/src/main/java/de/marhali/easyi18n/io/parser/json/JsonMapper.java index bd4c1bf..328eae7 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/json/JsonMapper.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/json/JsonMapper.java @@ -4,8 +4,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; -import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.TranslationNode; +import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.util.StringUtil; import org.apache.commons.lang.StringEscapeUtils; @@ -30,7 +30,7 @@ public class JsonMapper { // Nested element - run recursively read(locale, value.getAsJsonObject(), childNode); } else { - Translation translation = childNode.getValue(); + TranslationValue translation = childNode.getValue(); String content = entry.getValue().isJsonArray() ? JsonArrayMapper.read(value.getAsJsonArray()) @@ -55,7 +55,7 @@ public class JsonMapper { json.add(key, childJson); } } else { - Translation translation = childNode.getValue(); + TranslationValue translation = childNode.getValue(); String content = translation.get(locale); if(content != null) { diff --git a/src/main/java/de/marhali/easyi18n/io/parser/json/JsonParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/json/JsonParserStrategy.java index a765547..f47ac76 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/json/JsonParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/json/JsonParserStrategy.java @@ -3,14 +3,15 @@ package de.marhali.easyi18n.io.parser.json; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; + import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategy; import de.marhali.easyi18n.model.*; +import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; -import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.Objects; @@ -23,7 +24,7 @@ public class JsonParserStrategy extends ParserStrategy { private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); - public JsonParserStrategy(@NotNull SettingsState settings) { + public JsonParserStrategy(@NotNull ProjectSettings settings) { super(settings); } diff --git a/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5Mapper.java b/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5Mapper.java index 80b36ae..dc6bdfe 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5Mapper.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5Mapper.java @@ -1,12 +1,12 @@ package de.marhali.easyi18n.io.parser.json5; -import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.TranslationNode; +import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.util.StringUtil; - import de.marhali.json5.Json5Element; import de.marhali.json5.Json5Object; import de.marhali.json5.Json5Primitive; + import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.math.NumberUtils; @@ -28,7 +28,7 @@ public class Json5Mapper { // Nested element - run recursively read(locale, value.getAsJson5Object(), childNode); } else { - Translation translation = childNode.getValue(); + TranslationValue translation = childNode.getValue(); String content = value.isJson5Array() ? Json5ArrayMapper.read(value.getAsJson5Array()) @@ -54,7 +54,7 @@ public class Json5Mapper { } } else { - Translation translation = childNode.getValue(); + TranslationValue translation = childNode.getValue(); String content = translation.get(locale); if(content != null) { if(Json5ArrayMapper.isArray(content)) { diff --git a/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5ParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5ParserStrategy.java index cb9c64b..97965b9 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5ParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5ParserStrategy.java @@ -3,10 +3,10 @@ package de.marhali.easyi18n.io.parser.json5; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategy; -import de.marhali.easyi18n.model.SettingsState; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; import de.marhali.easyi18n.model.TranslationNode; +import de.marhali.easyi18n.settings.ProjectSettings; import de.marhali.json5.Json5; import de.marhali.json5.Json5Element; import de.marhali.json5.Json5Object; @@ -26,7 +26,7 @@ public class Json5ParserStrategy extends ParserStrategy { private static final Json5 JSON5 = Json5.builder(builder -> builder.allowInvalidSurrogate().trailingComma().indentFactor(4).build()); - public Json5ParserStrategy(@NotNull SettingsState settings) { + public Json5ParserStrategy(@NotNull ProjectSettings settings) { super(settings); } diff --git a/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesMapper.java b/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesMapper.java index 0ae62b2..2cac6ae 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesMapper.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesMapper.java @@ -1,8 +1,9 @@ package de.marhali.easyi18n.io.parser.properties; -import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.util.KeyPathConverter; import de.marhali.easyi18n.util.StringUtil; import org.apache.commons.lang.StringEscapeUtils; @@ -16,15 +17,17 @@ import java.util.Map; */ public class PropertiesMapper { - public static void read(String locale, SortableProperties properties, TranslationData data) { + public static void read(String locale, SortableProperties properties, + TranslationData data, KeyPathConverter converter) { + for(Map.Entry entry : properties.entrySet()) { - KeyPath key = new KeyPath(String.valueOf(entry.getKey())); + KeyPath key = converter.fromString(String.valueOf(entry.getKey())); Object value = entry.getValue(); - Translation translation = data.getTranslation(key); + TranslationValue translation = data.getTranslation(key); if(translation == null) { - translation = new Translation(); + translation = new TranslationValue(); } String content = value instanceof String[] @@ -36,12 +39,14 @@ public class PropertiesMapper { } } - public static void write(String locale, SortableProperties properties, TranslationData data) { - for(KeyPath key : data.getFullKeys()) { - Translation translation = data.getTranslation(key); + public static void write(String locale, SortableProperties properties, + TranslationData data, KeyPathConverter converter) { - if(translation != null && translation.containsKey(locale)) { - String simpleKey = key.toSimpleString(); + for(KeyPath key : data.getFullKeys()) { + TranslationValue translation = data.getTranslation(key); + + if(translation != null && translation.containsLocale(locale)) { + String simpleKey = converter.toString(key); String content = translation.get(locale); if(PropertiesArrayMapper.isArray(content)) { diff --git a/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesParserStrategy.java index 35a45db..22e7722 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesParserStrategy.java @@ -3,10 +3,11 @@ package de.marhali.easyi18n.io.parser.properties; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategy; -import de.marhali.easyi18n.model.SettingsState; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; import de.marhali.easyi18n.model.TranslationNode; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.util.KeyPathConverter; import org.jetbrains.annotations.NotNull; @@ -20,8 +21,11 @@ import java.io.StringWriter; */ public class PropertiesParserStrategy extends ParserStrategy { - public PropertiesParserStrategy(@NotNull SettingsState settings) { + private final @NotNull KeyPathConverter converter; + + public PropertiesParserStrategy(@NotNull ProjectSettings settings) { super(settings); + this.converter = new KeyPathConverter(settings); } @Override @@ -33,9 +37,9 @@ public class PropertiesParserStrategy extends ParserStrategy { TranslationData targetData = new TranslationData(data.getLocales(), targetNode); try(Reader reader = new InputStreamReader(vf.getInputStream(), vf.getCharset())) { - SortableProperties input = new SortableProperties(this.settings.isSortKeys()); + SortableProperties input = new SortableProperties(this.settings.isSorting()); input.load(reader); - PropertiesMapper.read(file.getLocale(), input, targetData); + PropertiesMapper.read(file.getLocale(), input, targetData, converter); } } @@ -44,8 +48,8 @@ public class PropertiesParserStrategy extends ParserStrategy { TranslationNode targetNode = super.getTargetNode(data, file); TranslationData targetData = new TranslationData(data.getLocales(), targetNode); - SortableProperties output = new SortableProperties(this.settings.isSortKeys()); - PropertiesMapper.write(file.getLocale(), output, targetData); + SortableProperties output = new SortableProperties(this.settings.isSorting()); + PropertiesMapper.write(file.getLocale(), output, targetData, converter); try(StringWriter writer = new StringWriter()) { output.store(writer, null); diff --git a/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlMapper.java b/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlMapper.java index 0ceb5ac..09e92da 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlMapper.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlMapper.java @@ -1,7 +1,7 @@ package de.marhali.easyi18n.io.parser.yaml; -import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.TranslationNode; +import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.util.StringUtil; import org.apache.commons.lang.StringEscapeUtils; @@ -28,7 +28,7 @@ public class YamlMapper { // Nested element - run recursively read(locale, (MapSection) value, childNode); } else { - Translation translation = childNode.getValue(); + TranslationValue translation = childNode.getValue(); String content = value instanceof ListSection ? YamlArrayMapper.read((ListSection) value) @@ -53,7 +53,7 @@ public class YamlMapper { section.setInScope(key, childSection); } } else { - Translation translation = childNode.getValue(); + TranslationValue translation = childNode.getValue(); String content = translation.get(locale); if(content != null) { diff --git a/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlParserStrategy.java index dddf5ec..5ea78ad 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlParserStrategy.java @@ -3,10 +3,10 @@ package de.marhali.easyi18n.io.parser.yaml; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategy; -import de.marhali.easyi18n.model.SettingsState; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; import de.marhali.easyi18n.model.TranslationNode; +import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; @@ -22,7 +22,7 @@ import java.io.Reader; */ public class YamlParserStrategy extends ParserStrategy { - public YamlParserStrategy(@NotNull SettingsState settings) { + public YamlParserStrategy(@NotNull ProjectSettings settings) { super(settings); } diff --git a/src/main/java/de/marhali/easyi18n/model/KeyPath.java b/src/main/java/de/marhali/easyi18n/model/KeyPath.java index 600a66a..0ad3089 100644 --- a/src/main/java/de/marhali/easyi18n/model/KeyPath.java +++ b/src/main/java/de/marhali/easyi18n/model/KeyPath.java @@ -1,58 +1,37 @@ package de.marhali.easyi18n.model; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.regex.Pattern; /** - * Represents a full translation key with all sections. - * Implementations can use single section or variable section length variants. - * The respective layer (io, presentation) is responsible for using the correct mapping mechanism. + * Represents the absolute key path for a desired translation. + * The key could be based one or many sections. + * Classes implementing this structure need to take care on how to layer translations paths. * @author marhali */ public class KeyPath extends ArrayList { - public static final String DELIMITER = "."; + public KeyPath() {} - public static KeyPath of(@NotNull String... path) { - return new KeyPath(List.of(path)); + public KeyPath(@Nullable String... path) { + super.addAll(List.of(path)); } - public KeyPath() { - super(); + public KeyPath(@NotNull List path) { + super(path); } - public KeyPath(@NotNull KeyPath path, String... pathToAppend) { + public KeyPath(@NotNull KeyPath path, @Nullable String... pathToAppend) { this(path); - this.addAll(List.of(pathToAppend)); + super.addAll(List.of(pathToAppend)); } - public KeyPath(@NotNull Collection c) { - super(c); + @Override + public String toString() { + // Just a simple array view (e.g. [first, second]) - use KeyPathConverter to properly convert a key path + return super.toString(); } - - public KeyPath(@NotNull String simplePath) { - this(List.of(simplePath.split(Pattern.quote(DELIMITER)))); - } - - /** - * Note: Use {@link KeyPathConverter} if you want to keep hierarchy. - * @return simple path representation by adding delimiter between the secton nodes - */ - public String toSimpleString() { - StringBuilder builder = new StringBuilder(); - - for(String section : this) { - if(builder.length() > 0) { - builder.append(DELIMITER); - } - - builder.append(section); - } - - return builder.toString(); - } -} \ No newline at end of file +} diff --git a/src/main/java/de/marhali/easyi18n/model/KeyPathConverter.java b/src/main/java/de/marhali/easyi18n/model/KeyPathConverter.java deleted file mode 100644 index c5b0ecd..0000000 --- a/src/main/java/de/marhali/easyi18n/model/KeyPathConverter.java +++ /dev/null @@ -1,69 +0,0 @@ -package de.marhali.easyi18n.model; - -import com.intellij.openapi.project.Project; - -import de.marhali.easyi18n.service.SettingsService; - -import org.jetbrains.annotations.NotNull; - -import java.util.regex.Pattern; - -/** - * Responsible for mapping {@link KeyPath} into single string and backwards. - * If nesting is enabled the delimiter within a section is escaped otherwise the delimiter between the key sections. - * @author marhali - */ -public class KeyPathConverter { - - private final boolean nestKeys; - - public KeyPathConverter(boolean nestKeys) { - this.nestKeys = nestKeys; - } - - public KeyPathConverter(@NotNull Project project) { - this(SettingsService.getInstance(project).getState().isNestedKeys()); - } - - public @NotNull String concat(@NotNull KeyPath path) { - StringBuilder builder = new StringBuilder(); - - for(String section : path) { - if(builder.length() > 0) { - if(!this.nestKeys) { - builder.append("\\\\"); - } - - builder.append(KeyPath.DELIMITER); - } - - if(this.nestKeys) { - builder.append(section.replace(KeyPath.DELIMITER, "\\\\" + KeyPath.DELIMITER)); - } else { - builder.append(section); - } - } - - return builder.toString(); - } - - public @NotNull KeyPath split(@NotNull String concatPath) { - String[] sections = concatPath.split(this.nestKeys ? - "(? { - public Translation() { - super(); +public class Translation { + + private final @NotNull KeyPath key; + private @Nullable TranslationValue value; + + /** + * Constructs a new translation instance. + * @param key Absolute key path + * @param value Values to set - nullable to indicate removal + */ + public Translation(@NotNull KeyPath key, @Nullable TranslationValue value) { + this.key = key; + this.value = value; } - public Translation(String locale, String content) { - this(); - super.put(locale, content); + /** + * @return Absolute key path + */ + public @NotNull KeyPath getKey() { + return key; } - public Translation add(String locale, String content) { - super.put(locale, content); - return this; + /** + * @return values - nullable to indicate removal + */ + public @Nullable TranslationValue getValue() { + return value; + } + + /** + * @param value Values to set - nullable to indicate removal + */ + public void setValue(@Nullable TranslationValue value) { + this.value = value; } @Override public String toString() { - return super.toString(); + return "Translation{" + + "key=" + key + + ", value=" + value + + '}'; } } \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/model/TranslationData.java b/src/main/java/de/marhali/easyi18n/model/TranslationData.java index 087048b..33a2946 100644 --- a/src/main/java/de/marhali/easyi18n/model/TranslationData.java +++ b/src/main/java/de/marhali/easyi18n/model/TranslationData.java @@ -93,7 +93,7 @@ public class TranslationData { * @param fullPath Absolute translation key path * @return Found translation. Can be null if path is empty or is not a leaf element */ - public @Nullable Translation getTranslation(@NotNull KeyPath fullPath) { + public @Nullable TranslationValue getTranslation(@NotNull KeyPath fullPath) { TranslationNode node = this.getNode(fullPath); if(node == null || !node.isLeaf()) { @@ -109,7 +109,7 @@ public class TranslationData { * @param fullPath Absolute translation key path * @param translation Translation to set. Can be null to delete the corresponding node */ - public void setTranslation(@NotNull KeyPath fullPath, @Nullable Translation translation) { + public void setTranslation(@NotNull KeyPath fullPath, @Nullable TranslationValue translation) { if(fullPath.isEmpty()) { throw new IllegalArgumentException("Key path cannot be empty"); } diff --git a/src/main/java/de/marhali/easyi18n/model/TranslationNode.java b/src/main/java/de/marhali/easyi18n/model/TranslationNode.java index d85e678..b2ca570 100644 --- a/src/main/java/de/marhali/easyi18n/model/TranslationNode.java +++ b/src/main/java/de/marhali/easyi18n/model/TranslationNode.java @@ -27,7 +27,7 @@ public class TranslationNode { private Map children; @NotNull - private Translation value; + private TranslationValue value; public TranslationNode(boolean sort) { this(sort ? new TreeMap<>() : new LinkedHashMap<>()); @@ -40,7 +40,7 @@ public class TranslationNode { public TranslationNode(@NotNull Map children) { this.parent = null; this.children = children; - this.value = new Translation(); + this.value = new TranslationValue(); } /** @@ -62,11 +62,11 @@ public class TranslationNode { this.parent = parent; } - public @NotNull Translation getValue() { + public @NotNull TranslationValue getValue() { return value; } - public void setValue(@NotNull Translation value) { + public void setValue(@NotNull TranslationValue value) { this.children.clear(); this.value = value; } @@ -93,7 +93,7 @@ public class TranslationNode { } } - public void setChildren(@NotNull String key, @NotNull Translation translation) { + public void setChildren(@NotNull String key, @NotNull TranslationValue translation) { this.setChildren(key).setValue(translation); } diff --git a/src/main/java/de/marhali/easyi18n/model/TranslationValue.java b/src/main/java/de/marhali/easyi18n/model/TranslationValue.java new file mode 100644 index 0000000..6ca10bd --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/model/TranslationValue.java @@ -0,0 +1,70 @@ +package de.marhali.easyi18n.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Represents the set values behind a specific translation. + * @author marhali + */ +public class TranslationValue { + + private @NotNull Map localeValues; + + public TranslationValue() { + this.localeValues = new HashMap<>(); + } + + public TranslationValue(@NotNull String locale, @NotNull String content) { + this(); + localeValues.put(locale, content); + } + + public Set> getEntries() { + return this.localeValues.entrySet(); + } + + public Collection getLocaleContents() { + return this.localeValues.values(); + } + + public void setLocaleValues(@NotNull Map localeValues) { + this.localeValues = localeValues; + } + + public @Nullable String get(@NotNull String locale) { + return this.localeValues.get(locale); + } + + public void put(@NotNull String locale, @NotNull String content) { + this.localeValues.put(locale, content); + } + + public void remove(@NotNull String locale) { + this.localeValues.remove(locale); + } + + public boolean containsLocale(@NotNull String locale) { + return this.localeValues.containsKey(locale); + } + + public int size() { + return this.localeValues.size(); + } + + public void clear() { + this.localeValues.clear(); + } + + @Override + public String toString() { + return "TranslationValue{" + + "localeValues=" + localeValues + + '}'; + } +} diff --git a/src/main/java/de/marhali/easyi18n/model/TranslationCreate.java b/src/main/java/de/marhali/easyi18n/model/action/TranslationCreate.java similarity index 59% rename from src/main/java/de/marhali/easyi18n/model/TranslationCreate.java rename to src/main/java/de/marhali/easyi18n/model/action/TranslationCreate.java index 9c955a9..4edd8f5 100644 --- a/src/main/java/de/marhali/easyi18n/model/TranslationCreate.java +++ b/src/main/java/de/marhali/easyi18n/model/action/TranslationCreate.java @@ -1,4 +1,6 @@ -package de.marhali.easyi18n.model; +package de.marhali.easyi18n.model.action; + +import de.marhali.easyi18n.model.Translation; import org.jetbrains.annotations.NotNull; @@ -7,7 +9,7 @@ import org.jetbrains.annotations.NotNull; * @author marhali */ public class TranslationCreate extends TranslationUpdate { - public TranslationCreate(@NotNull KeyedTranslation translation) { + public TranslationCreate(@NotNull Translation translation) { super(null, translation); } } \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/model/TranslationDelete.java b/src/main/java/de/marhali/easyi18n/model/action/TranslationDelete.java similarity index 60% rename from src/main/java/de/marhali/easyi18n/model/TranslationDelete.java rename to src/main/java/de/marhali/easyi18n/model/action/TranslationDelete.java index 763cb16..6812e86 100644 --- a/src/main/java/de/marhali/easyi18n/model/TranslationDelete.java +++ b/src/main/java/de/marhali/easyi18n/model/action/TranslationDelete.java @@ -1,4 +1,6 @@ -package de.marhali.easyi18n.model; +package de.marhali.easyi18n.model.action; + +import de.marhali.easyi18n.model.Translation; import org.jetbrains.annotations.NotNull; @@ -7,7 +9,7 @@ import org.jetbrains.annotations.NotNull; * @author marhali */ public class TranslationDelete extends TranslationUpdate { - public TranslationDelete(@NotNull KeyedTranslation translation) { + public TranslationDelete(@NotNull Translation translation) { super(translation, null); } } \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/model/TranslationUpdate.java b/src/main/java/de/marhali/easyi18n/model/action/TranslationUpdate.java similarity index 69% rename from src/main/java/de/marhali/easyi18n/model/TranslationUpdate.java rename to src/main/java/de/marhali/easyi18n/model/action/TranslationUpdate.java index 6b9dac8..b1a03b3 100644 --- a/src/main/java/de/marhali/easyi18n/model/TranslationUpdate.java +++ b/src/main/java/de/marhali/easyi18n/model/action/TranslationUpdate.java @@ -1,5 +1,6 @@ -package de.marhali.easyi18n.model; +package de.marhali.easyi18n.model.action; +import de.marhali.easyi18n.model.Translation; import org.jetbrains.annotations.Nullable; /** @@ -10,19 +11,19 @@ import org.jetbrains.annotations.Nullable; */ public class TranslationUpdate { - private final @Nullable KeyedTranslation origin; - private final @Nullable KeyedTranslation change; + private final @Nullable Translation origin; + private final @Nullable Translation change; - public TranslationUpdate(@Nullable KeyedTranslation origin, @Nullable KeyedTranslation change) { + public TranslationUpdate(@Nullable Translation origin, @Nullable Translation change) { this.origin = origin; this.change = change; } - public @Nullable KeyedTranslation getOrigin() { + public @Nullable Translation getOrigin() { return origin; } - public @Nullable KeyedTranslation getChange() { + public @Nullable Translation getChange() { return change; } diff --git a/src/main/java/de/marhali/easyi18n/service/SettingsService.java b/src/main/java/de/marhali/easyi18n/service/SettingsService.java deleted file mode 100644 index 59f81ad..0000000 --- a/src/main/java/de/marhali/easyi18n/service/SettingsService.java +++ /dev/null @@ -1,37 +0,0 @@ -package de.marhali.easyi18n.service; - -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.State; -import com.intellij.openapi.project.Project; - -import de.marhali.easyi18n.model.SettingsState; - -import org.jetbrains.annotations.NotNull; - -/** - * Persistent settings storage at project level. - * @author marhali - */ -@State(name = "EasyI18nSettings") -public class SettingsService implements PersistentStateComponent { - - public static SettingsService getInstance(Project project) { - return project.getService(SettingsService.class); - } - - private SettingsState state; - - public SettingsService() { - this.state = new SettingsState(); - } - - @Override - public @NotNull SettingsState getState() { - return state; - } - - @Override - public void loadState(@NotNull SettingsState state) { - this.state = state; - } -} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettings.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettings.java new file mode 100644 index 0000000..e4c94a6 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettings.java @@ -0,0 +1,35 @@ +package de.marhali.easyi18n.settings; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.io.folder.FolderStrategyType; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * API to access the project-specific configuration for this plugin. + * @author marhaliu + */ +public interface ProjectSettings { + // Resource Configuration + @Nullable String getLocalesDirectory(); + @NotNull FolderStrategyType getFolderStrategy(); + @NotNull ParserStrategyType getParserStrategy(); + @NotNull String getFilePattern(); + + boolean isSorting(); + + // Editor Configuration + @Nullable String getNamespaceDelimiter(); + @NotNull String getSectionDelimiter(); + @Nullable String getContextDelimiter(); + @Nullable String getPluralDelimiter(); + @Nullable String getDefaultNamespace(); + @NotNull String getPreviewLocale(); + + boolean isNestedKeys(); + boolean isAssistance(); + + // Experimental Configuration + boolean isAlwaysFold(); +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponent.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponent.java new file mode 100644 index 0000000..98b1dee --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponent.java @@ -0,0 +1,228 @@ +package de.marhali.easyi18n.settings; + +import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.ui.JBColor; +import com.intellij.ui.TitledSeparator; +import com.intellij.ui.components.*; +import com.intellij.ui.components.fields.ExtendableTextField; +import com.intellij.util.ui.FormBuilder; + +import de.marhali.easyi18n.io.parser.ArrayMapper; +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.settings.presets.Preset; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ResourceBundle; + +/** + * Configuration panel with all possible options for this plugin. + * @author marhali + */ +public class ProjectSettingsComponent extends ProjectSettingsComponentState { + + private final Project project; + private final ResourceBundle bundle; + private final JPanel mainPanel; + + // Data fields are provided by the underlying state class + + public ProjectSettingsComponent(Project project) { + this.project = project; + this.bundle = ResourceBundle.getBundle("messages"); + + this.mainPanel = FormBuilder.createFormBuilder() + .addComponent(new JBLabel(bundle.getString("settings.hint.text"))) + .addComponent(new ActionLink(bundle.getString("settings.hint.action"), + (ActionListener) (var) -> BrowserUtil.browse("https://github.com/marhali/easy-i18n"))) + .addVerticalGap(24) + .addLabeledComponent(bundle.getString("settings.preset.title"), constructPresetField(), 1, false) + .addVerticalGap(12) + .addComponent(new TitledSeparator(bundle.getString("settings.resource.title"))) + .addLabeledComponent(bundle.getString("settings.resource.path.title"), constructLocalesDirectoryField(), 1, false) + .addLabeledComponent(bundle.getString("settings.resource.strategy"), constructFileStrategyPanel(), 1, false) + .addVerticalGap(12) + .addComponent(constructSortingField()) + .addVerticalGap(24) + .addComponent(new TitledSeparator(bundle.getString("settings.editor.title"))) + .addLabeledComponent(bundle.getString("settings.editor.key.title"), constructKeyStrategyPanel(), 1, false) + .addLabeledComponent(bundle.getString("settings.editor.default-namespace.title"), constructDefaultNamespaceField(), 1, false) + .addLabeledComponent(bundle.getString("settings.editor.preview.title"), constructPreviewLocaleField(), 1, false) + .addVerticalGap(12) + .addComponent(constructNestedKeysField()) + .addComponent(constructAssistanceField()) + .addVerticalGap(24) + .addComponent(new TitledSeparator(bundle.getString("settings.experimental.title"))) + .addComponent(constructAlwaysFoldField()) + .addComponentFillVertically(new JPanel(), 0) + .getPanel(); + } + + private JComponent constructPresetField() { + preset = new ComboBox<>(Preset.values()); + preset.setToolTipText(bundle.getString("settings.preset.tooltip")); + preset.setMinimumAndPreferredWidth(196); + preset.addActionListener(e -> setState(preset.getItem().config())); // Listen to selection change + return preset; + } + + private JComponent constructLocalesDirectoryField() { + localesDirectory = new TextFieldWithBrowseButton(); + localesDirectory.setToolTipText(bundle.getString("settings.resource.path.tooltip")); + localesDirectory.addBrowseFolderListener(bundle.getString("settings.resource.path.window"), + bundle.getString("settings.resource.path.tooltip"), project, + new FileChooserDescriptor(false, true, + false, false, false, false)); + + // Listen to value change + localesDirectory.getTextField().getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + validateLocalesDirectory(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + validateLocalesDirectory(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + validateLocalesDirectory(); + } + }); + + validateLocalesDirectory(); + return localesDirectory; + } + + private void validateLocalesDirectory() { + // Paint red border to indicate missing value + localesDirectory.setBorder(localesDirectory.getText().isEmpty() + ? BorderFactory.createLineBorder(JBColor.red) : null); + } + + private JPanel constructFileStrategyPanel() { + JPanel panel = new JBPanel<>(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + + /* folder strategy */ + folderStrategy = new ComboBox<>(bundle.getString("settings.resource.folder.items").split(ArrayMapper.SPLITERATOR_REGEX)); + folderStrategy.setToolTipText(bundle.getString("settings.resource.folder.tooltip")); + folderStrategy.setMinimumAndPreferredWidth(256); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 0; + constraints.gridy = 0; + panel.add(folderStrategy, constraints); + + /* parser strategy */ + parserStrategy = new ComboBox<>(bundle.getString("settings.resource.parser.items").split(ArrayMapper.SPLITERATOR_REGEX)); + parserStrategy.setToolTipText(bundle.getString("settings.resource.parser.tooltip")); + parserStrategy.addItemListener(handleParserChange()); + parserStrategy.setMinimumAndPreferredWidth(128); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 1; + constraints.gridy = 0; + panel.add(parserStrategy, constraints); + + /* file pattern strategy */ + filePattern = new JBTextField(); + filePattern.setToolTipText(bundle.getString("settings.resource.file-pattern.tooltip")); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 2; + constraints.gridy = 0; + constraints.weightx = 1; + panel.add(filePattern, constraints); + + return panel; + } + + private JComponent constructSortingField() { + sorting = new JBCheckBox(bundle.getString("settings.resource.sorting.title")); + sorting.setToolTipText(bundle.getString("settings.resource.sorting.tooltip")); + return sorting; + } + + private JPanel constructKeyStrategyPanel() { + JPanel panel = new JBPanel<>(new FlowLayout(FlowLayout.LEFT)); + + panel.add(new JBLabel(bundle.getString("settings.editor.key.namespace.title"))); + panel.add(namespaceDelimiter = createDelimiterField(bundle.getString("settings.editor.key.namespace.tooltip"))); + panel.add(new JBLabel(bundle.getString("settings.editor.key.section.title"))); + panel.add(sectionDelimiter = createDelimiterField(bundle.getString("settings.editor.key.section.tooltip"))); + panel.add(createBoldLabel(bundle.getString("settings.editor.key.leaf.title"))); + panel.add(contextDelimiter = createDelimiterField(bundle.getString("settings.editor.key.context.tooltip"))); + panel.add(createBoldLabel(bundle.getString("settings.editor.key.context.title"))); + panel.add(pluralDelimiter = createDelimiterField(bundle.getString("settings.editor.key.plural.tooltip"))); + panel.add(createBoldLabel(bundle.getString("settings.editor.key.plural.title"))); + + return panel; + } + + private JLabel createBoldLabel(String title) { + JBLabel label = new JBLabel(title); + Font font = label.getFont(); + label.setFont(font.deriveFont(font.getStyle() | Font.BOLD)); + return label; + } + + private JTextField createDelimiterField(String tooltip) { + JBTextField field = new JBTextField(); + field.setHorizontalAlignment(JTextField.CENTER); + field.setToolTipText(tooltip); + return field; + } + + private JComponent constructDefaultNamespaceField() { + defaultNamespace = new ExtendableTextField(20); + defaultNamespace.setToolTipText(bundle.getString("settings.editor.default-namespace.tooltip")); + return defaultNamespace; + } + + private JComponent constructPreviewLocaleField() { + previewLocale = new ExtendableTextField(12); + previewLocale.setToolTipText(bundle.getString("settings.editor.preview.tooltip")); + return previewLocale; + } + + private JComponent constructNestedKeysField() { + nestedKeys = new JBCheckBox(bundle.getString("settings.editor.key.nesting.title")); + nestedKeys.setToolTipText(bundle.getString("settings.editor.key.nesting.tooltip")); + return nestedKeys; + } + + private JComponent constructAssistanceField() { + assistance = new JBCheckBox(bundle.getString("settings.editor.assistance.title")); + assistance.setToolTipText(bundle.getString("settings.editor.assistance.tooltip")); + return assistance; + } + + private JComponent constructAlwaysFoldField() { + alwaysFold = new JBCheckBox(bundle.getString("settings.experimental.always-fold.title")); + alwaysFold.setToolTipText(bundle.getString("settings.experimental.always-fold.tooltip")); + return alwaysFold; + } + + private ItemListener handleParserChange() { + return e -> { + if(e.getStateChange() == ItemEvent.SELECTED) { + // Automatically suggest file pattern option on parser change + ParserStrategyType newStrategy = ParserStrategyType.fromIndex(parserStrategy.getSelectedIndex()); + filePattern.setText(newStrategy.getExampleFilePattern()); + } + }; + } + + public JPanel getMainPanel() { + return mainPanel; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponentState.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponentState.java new file mode 100644 index 0000000..bf682e8 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponentState.java @@ -0,0 +1,89 @@ +package de.marhali.easyi18n.settings; + +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.io.folder.FolderStrategyType; +import de.marhali.easyi18n.settings.presets.Preset; + +import javax.swing.*; + +/** + * Mandatory for state management for the project settings component. + * @author marhali + */ +public class ProjectSettingsComponentState { + + protected ComboBox preset; + + // Resource Configuration + protected TextFieldWithBrowseButton localesDirectory; + protected ComboBox folderStrategy; + protected ComboBox parserStrategy; + protected JTextField filePattern; + + protected JCheckBox sorting; + + // Editor configuration + protected JTextField namespaceDelimiter; + protected JTextField sectionDelimiter; + protected JTextField contextDelimiter; + protected JTextField pluralDelimiter; + protected JTextField defaultNamespace; + protected JTextField previewLocale; + + protected JCheckBox nestedKeys; + protected JCheckBox assistance; + + // Experimental configuration + protected JCheckBox alwaysFold; + + protected ProjectSettingsState getState() { + // Every field needs to provide its state + ProjectSettingsState state = new ProjectSettingsState(); + + state.setLocalesDirectory(localesDirectory.getText()); + state.setFolderStrategy(FolderStrategyType.fromIndex(folderStrategy.getSelectedIndex())); + state.setParserStrategy(ParserStrategyType.fromIndex(parserStrategy.getSelectedIndex())); + state.setFilePattern(filePattern.getText()); + + state.setSorting(sorting.isSelected()); + + state.setNamespaceDelimiter(namespaceDelimiter.getText()); + state.setSectionDelimiter(sectionDelimiter.getText()); + state.setContextDelimiter(contextDelimiter.getText()); + state.setPluralDelimiter(pluralDelimiter.getText()); + state.setDefaultNamespace(defaultNamespace.getText()); + state.setPreviewLocale(previewLocale.getText()); + + state.setNestedKeys(nestedKeys.isSelected()); + state.setAssistance(assistance.isSelected()); + + state.setAlwaysFold(alwaysFold.isSelected()); + + return state; + } + + protected void setState(ProjectSettings state) { + // Update every field with the new state + localesDirectory.setText(state.getLocalesDirectory()); + folderStrategy.setSelectedIndex(state.getFolderStrategy().toIndex()); + parserStrategy.setSelectedIndex((state.getParserStrategy().toIndex())); + filePattern.setText(state.getFilePattern()); + + sorting.setSelected(state.isSorting()); + + namespaceDelimiter.setText(state.getNamespaceDelimiter()); + sectionDelimiter.setText(state.getSectionDelimiter()); + contextDelimiter.setText(state.getContextDelimiter()); + pluralDelimiter.setText(state.getPluralDelimiter()); + defaultNamespace.setText(state.getDefaultNamespace()); + previewLocale.setText(state.getPreviewLocale()); + + nestedKeys.setSelected(state.isNestedKeys()); + assistance.setSelected(state.isAssistance()); + + alwaysFold.setSelected(state.isAlwaysFold()); + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsConfigurable.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsConfigurable.java new file mode 100644 index 0000000..dcb07b6 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsConfigurable.java @@ -0,0 +1,59 @@ +package de.marhali.easyi18n.settings; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.project.Project; + +import de.marhali.easyi18n.InstanceManager; + +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** + * IDE settings panel for this plugin + * @author marhali + */ +public class ProjectSettingsConfigurable implements Configurable { + + private final Project project; + + private ProjectSettingsComponent component; + + public ProjectSettingsConfigurable(Project project) { + this.project = project; + } + + @Override + public String getDisplayName() { + return "Easy I18n"; + } + + @Override + public @Nullable JComponent createComponent() { + component = new ProjectSettingsComponent(project); + component.setState(ProjectSettingsService.get(project).getState()); + return component.getMainPanel(); + } + + @Override + public boolean isModified() { + ProjectSettingsState originState = ProjectSettingsService.get(project).getState(); + return !originState.equals(component.getState()); + } + + @Override + public void apply() { + ProjectSettingsService.get(project).setState(component.getState()); + InstanceManager.get(project).reload(); + } + + @Override + public void reset() { + component.setState(ProjectSettingsService.get(project).getState()); + } + + @Override + public void disposeUIResources() { + component = null; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsService.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsService.java new file mode 100644 index 0000000..1cc0538 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsService.java @@ -0,0 +1,47 @@ +package de.marhali.easyi18n.settings; + +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.project.Project; + +import org.jetbrains.annotations.NotNull; + +/** + * Persistent storage for project-specific settings. + * @author marhali + */ +@State( + name = "ProjectSettingsService", + storages = @Storage("easy-i18n.xml") +) +public class ProjectSettingsService implements PersistentStateComponent { + + public static @NotNull ProjectSettingsService get(@NotNull Project project) { + return project.getService(ProjectSettingsService.class); + } + + private ProjectSettingsState state; + + public ProjectSettingsService() { + this.state = new ProjectSettingsState(); + } + + /** + * Sets the provided configuration and invalidates the merged state. + * @param state New configuration + */ + protected void setState(@NotNull ProjectSettingsState state) { + this.state = state; + } + + @Override + public @NotNull ProjectSettingsState getState() { + return state; + } + + @Override + public void loadState(@NotNull ProjectSettingsState state) { + this.state = state; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsState.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsState.java new file mode 100644 index 0000000..29bdd44 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsState.java @@ -0,0 +1,168 @@ +package de.marhali.easyi18n.settings; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.io.folder.FolderStrategyType; +import de.marhali.easyi18n.settings.presets.DefaultPreset; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the project-specific configuration of this plugin. + * @author marhali + */ +public class ProjectSettingsState implements ProjectSettings { + + private static final ProjectSettings defaults = new DefaultPreset(); + + // Resource Configuration + private String localesDirectory; + private FolderStrategyType folderStrategy; + private ParserStrategyType parserStrategy; + private String filePattern; + + private Boolean sorting; + + // Editor configuration + private String namespaceDelimiter; + private String sectionDelimiter; + private String contextDelimiter; + private String pluralDelimiter; + private String defaultNamespace; + private String previewLocale; + + private Boolean nestedKeys; + private Boolean assistance; + + // Experimental configuration + private Boolean alwaysFold; + + public ProjectSettingsState() {} + + @Override + public @Nullable String getLocalesDirectory() { + return localesDirectory != null ? localesDirectory : defaults.getLocalesDirectory(); + } + + @Override + public @NotNull FolderStrategyType getFolderStrategy() { + return folderStrategy != null ? folderStrategy : defaults.getFolderStrategy(); + } + + @Override + public @NotNull ParserStrategyType getParserStrategy() { + return parserStrategy != null ? parserStrategy : defaults.getParserStrategy(); + } + + @Override + public @NotNull String getFilePattern() { + return filePattern != null ? filePattern : defaults.getFilePattern(); + } + + @Override + public boolean isSorting() { + return sorting != null ? sorting : defaults.isSorting(); + } + + @Override + public @Nullable String getNamespaceDelimiter() { + return namespaceDelimiter != null ? namespaceDelimiter : defaults.getNamespaceDelimiter(); + } + + @Override + public @NotNull String getSectionDelimiter() { + return sectionDelimiter != null ? sectionDelimiter : defaults.getSectionDelimiter(); + } + + @Override + public @Nullable String getContextDelimiter() { + return contextDelimiter != null ? contextDelimiter : defaults.getContextDelimiter(); + } + + @Override + public @Nullable String getPluralDelimiter() { + return pluralDelimiter != null ? pluralDelimiter : defaults.getPluralDelimiter(); + } + + @Nullable + @Override + public String getDefaultNamespace() { + return defaultNamespace; + } + + @Override + public @NotNull String getPreviewLocale() { + return previewLocale != null ? previewLocale : defaults.getPreviewLocale(); + } + + @Override + public boolean isNestedKeys() { + return nestedKeys != null ? nestedKeys : defaults.isNestedKeys(); + } + + @Override + public boolean isAssistance() { + return assistance != null ? assistance : defaults.isAssistance(); + } + + @Override + public boolean isAlwaysFold() { + return alwaysFold != null ? alwaysFold : defaults.isAlwaysFold(); + } + + public void setLocalesDirectory(String localesDirectory) { + this.localesDirectory = localesDirectory; + } + + public void setFolderStrategy(FolderStrategyType folderStrategy) { + this.folderStrategy = folderStrategy; + } + + public void setParserStrategy(ParserStrategyType parserStrategy) { + this.parserStrategy = parserStrategy; + } + + public void setFilePattern(String filePattern) { + this.filePattern = filePattern; + } + + public void setSorting(Boolean sorting) { + this.sorting = sorting; + } + + public void setNamespaceDelimiter(String namespaceDelimiter) { + this.namespaceDelimiter = namespaceDelimiter; + } + + public void setSectionDelimiter(String sectionDelimiter) { + this.sectionDelimiter = sectionDelimiter; + } + + public void setContextDelimiter(String contextDelimiter) { + this.contextDelimiter = contextDelimiter; + } + + public void setPluralDelimiter(String pluralDelimiter) { + this.pluralDelimiter = pluralDelimiter; + } + + public void setDefaultNamespace(String defaultNamespace) { + this.defaultNamespace = defaultNamespace; + } + + public void setPreviewLocale(String previewLocale) { + this.previewLocale = previewLocale; + } + + public void setNestedKeys(Boolean nestedKeys) { + this.nestedKeys = nestedKeys; + } + + public void setAssistance(Boolean assistance) { + this.assistance = assistance; + } + + public void setAlwaysFold(Boolean alwaysFold) { + this.alwaysFold = alwaysFold; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/presets/DefaultPreset.java b/src/main/java/de/marhali/easyi18n/settings/presets/DefaultPreset.java new file mode 100644 index 0000000..63b7b4e --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/presets/DefaultPreset.java @@ -0,0 +1,84 @@ +package de.marhali.easyi18n.settings.presets; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.io.folder.FolderStrategyType; +import de.marhali.easyi18n.settings.ProjectSettings; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Default preset. Used if none has been defined. + * @author marhali + */ +public class DefaultPreset implements ProjectSettings { + @Override + public String getLocalesDirectory() { + return null; + } + + @Override + public @NotNull FolderStrategyType getFolderStrategy() { + return FolderStrategyType.SINGLE; + } + + @Override + public @NotNull ParserStrategyType getParserStrategy() { + return ParserStrategyType.JSON; + } + + @Override + public @NotNull String getFilePattern() { + return "*.*"; + } + + @Override + public boolean isSorting() { + return true; + } + + @Override + public String getNamespaceDelimiter() { + return ":"; + } + + @Override + public @NotNull String getSectionDelimiter() { + return "."; + } + + @Override + public String getContextDelimiter() { + return "_"; + } + + @Override + public String getPluralDelimiter() { + return "_"; + } + + @Override + public @Nullable String getDefaultNamespace() { + return null; + } + + @Override + public @NotNull String getPreviewLocale() { + return "en"; + } + + @Override + public boolean isNestedKeys() { + return true; + } + + @Override + public boolean isAssistance() { + return true; + } + + @Override + public boolean isAlwaysFold() { + return false; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/presets/Preset.java b/src/main/java/de/marhali/easyi18n/settings/presets/Preset.java new file mode 100644 index 0000000..7a7155a --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/presets/Preset.java @@ -0,0 +1,33 @@ +package de.marhali.easyi18n.settings.presets; + +import de.marhali.easyi18n.settings.ProjectSettings; + +/** + * Enumeration of all available configuration presets. + * Every preset needs to be registered here to be properly recognized. + * @author marhali + */ +public enum Preset { + DEFAULT(DefaultPreset.class), + VUE_I18N(VueI18nPreset.class), + REACT_I18NEXT(ReactI18NextPreset.class); + + private final Class clazz; + + Preset(Class clazz) { + this.clazz = clazz; + } + + public ProjectSettings config() { + try { + return this.clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return super.name().toLowerCase(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/presets/ReactI18NextPreset.java b/src/main/java/de/marhali/easyi18n/settings/presets/ReactI18NextPreset.java new file mode 100644 index 0000000..cc55f1d --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/presets/ReactI18NextPreset.java @@ -0,0 +1,84 @@ +package de.marhali.easyi18n.settings.presets; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.io.folder.FolderStrategyType; +import de.marhali.easyi18n.settings.ProjectSettings; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Preset for React - i18n-next + * @author marhali + */ +public class ReactI18NextPreset implements ProjectSettings { + @Override + public @Nullable String getLocalesDirectory() { + return null; + } + + @Override + public @NotNull FolderStrategyType getFolderStrategy() { + return FolderStrategyType.MODULARIZED_NAMESPACE; + } + + @Override + public @NotNull ParserStrategyType getParserStrategy() { + return ParserStrategyType.JSON; + } + + @Override + public @NotNull String getFilePattern() { + return "*.json"; + } + + @Override + public boolean isSorting() { + return true; + } + + @Override + public @Nullable String getNamespaceDelimiter() { + return ":"; + } + + @Override + public @NotNull String getSectionDelimiter() { + return "."; + } + + @Override + public @Nullable String getContextDelimiter() { + return "_"; + } + + @Override + public @Nullable String getPluralDelimiter() { + return "_"; + } + + @Override + public @Nullable String getDefaultNamespace() { + return "common"; + } + + @Override + public @NotNull String getPreviewLocale() { + return "en"; + } + + @Override + public boolean isNestedKeys() { + return false; + } + + @Override + public boolean isAssistance() { + return true; + } + + @Override + public boolean isAlwaysFold() { + return false; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/presets/VueI18nPreset.java b/src/main/java/de/marhali/easyi18n/settings/presets/VueI18nPreset.java new file mode 100644 index 0000000..01927d8 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/presets/VueI18nPreset.java @@ -0,0 +1,83 @@ +package de.marhali.easyi18n.settings.presets; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.io.folder.FolderStrategyType; +import de.marhali.easyi18n.settings.ProjectSettings; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Preset for Vue.js - vue-i18n + * @author marhali + */ +public class VueI18nPreset implements ProjectSettings { + @Override + public @Nullable String getLocalesDirectory() { + return null; + } + + @Override + public @NotNull FolderStrategyType getFolderStrategy() { + return FolderStrategyType.SINGLE; + } + + @Override + public @NotNull ParserStrategyType getParserStrategy() { + return ParserStrategyType.JSON; + } + + @Override + public @NotNull String getFilePattern() { + return "*.json"; + } + + @Override + public boolean isSorting() { + return true; + } + + @Override + public @Nullable String getNamespaceDelimiter() { + return null; + } + + @Override + public @NotNull String getSectionDelimiter() { + return "."; + } + + @Override + public @Nullable String getContextDelimiter() { + return null; + } + + @Override + public @Nullable String getPluralDelimiter() { + return null; + } + + @Override + public @Nullable String getDefaultNamespace() { + return null; + } + + @Override + public @NotNull String getPreviewLocale() { + return "en"; + } + + @Override + public boolean isNestedKeys() { + return true; + } + + @Override + public boolean isAssistance() { + return true; + } + + @Override + public boolean isAlwaysFold() { + return false; + } +} diff --git a/src/main/java/de/marhali/easyi18n/tabs/TableView.java b/src/main/java/de/marhali/easyi18n/tabs/TableView.java index 02ad3b7..51b65de 100644 --- a/src/main/java/de/marhali/easyi18n/tabs/TableView.java +++ b/src/main/java/de/marhali/easyi18n/tabs/TableView.java @@ -5,14 +5,19 @@ import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.table.JBTable; import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.listener.ReturnKeyListener; -import de.marhali.easyi18n.model.*; import de.marhali.easyi18n.dialog.EditDialog; +import de.marhali.easyi18n.listener.ReturnKeyListener; import de.marhali.easyi18n.listener.DeleteKeyListener; import de.marhali.easyi18n.listener.PopupClickListener; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.action.TranslationDelete; import de.marhali.easyi18n.model.bus.BusListener; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.renderer.TableRenderer; import de.marhali.easyi18n.tabs.mapper.TableModelMapper; +import de.marhali.easyi18n.util.KeyPathConverter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -56,20 +61,20 @@ public class TableView implements BusListener { return; } - KeyPath fullPath = this.converter.split(String.valueOf(this.table.getValueAt(row, 0))); - Translation translation = InstanceManager.get(project).store().getData().getTranslation(fullPath); + KeyPath fullPath = this.converter.fromString(String.valueOf(this.table.getValueAt(row, 0))); + TranslationValue value = InstanceManager.get(project).store().getData().getTranslation(fullPath); - if (translation != null) { - new EditDialog(project, new KeyedTranslation(fullPath, translation)).showAndHandle(); + if (value != null) { + new EditDialog(project, new Translation(fullPath, value)).showAndHandle(); } } private void deleteSelectedRows() { for (int selectedRow : table.getSelectedRows()) { - KeyPath fullPath = this.converter.split(String.valueOf(table.getValueAt(selectedRow, 0))); + KeyPath fullPath = this.converter.fromString(String.valueOf(table.getValueAt(selectedRow, 0))); InstanceManager.get(project).processUpdate( - new TranslationDelete(new KeyedTranslation(fullPath, null)) + new TranslationDelete(new Translation(fullPath, null)) ); } } @@ -84,7 +89,7 @@ public class TableView implements BusListener { @Override public void onFocusKey(@NotNull KeyPath key) { - String concatKey = this.converter.concat(key); + String concatKey = this.converter.toString(key); int row = -1; for (int i = 0; i < table.getRowCount(); i++) { diff --git a/src/main/java/de/marhali/easyi18n/tabs/TreeView.java b/src/main/java/de/marhali/easyi18n/tabs/TreeView.java index dff10ea..7db7850 100644 --- a/src/main/java/de/marhali/easyi18n/tabs/TreeView.java +++ b/src/main/java/de/marhali/easyi18n/tabs/TreeView.java @@ -9,16 +9,20 @@ import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.treeStructure.Tree; import de.marhali.easyi18n.InstanceManager; +import de.marhali.easyi18n.dialog.EditDialog; import de.marhali.easyi18n.listener.ReturnKeyListener; -import de.marhali.easyi18n.model.*; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.action.TranslationDelete; import de.marhali.easyi18n.model.bus.BusListener; import de.marhali.easyi18n.action.treeview.CollapseTreeViewAction; import de.marhali.easyi18n.action.treeview.ExpandTreeViewAction; -import de.marhali.easyi18n.dialog.EditDialog; import de.marhali.easyi18n.listener.DeleteKeyListener; import de.marhali.easyi18n.listener.PopupClickListener; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.renderer.TreeRenderer; -import de.marhali.easyi18n.service.SettingsService; +import de.marhali.easyi18n.settings.ProjectSettingsService; import de.marhali.easyi18n.tabs.mapper.TreeModelMapper; import de.marhali.easyi18n.util.TreeUtil; @@ -28,6 +32,8 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; +import java.util.ArrayList; +import java.util.List; import java.util.ResourceBundle; /** @@ -80,7 +86,21 @@ public class TreeView implements BusListener { @Override public void onUpdateData(@NotNull TranslationData data) { - tree.setModel(this.currentMapper = new TreeModelMapper(data, SettingsService.getInstance(project).getState())); + List expanded = getExpandedRows(); + tree.setModel(this.currentMapper = new TreeModelMapper(data, ProjectSettingsService.get(project).getState())); + expanded.forEach(tree::expandRow); + } + + private List getExpandedRows() { + List expanded = new ArrayList<>(); + + for(int i = 0; i < tree.getRowCount(); i++) { + if(tree.isExpanded(i)) { + expanded.add(i); + } + } + + return expanded; } @Override @@ -127,13 +147,13 @@ public class TreeView implements BusListener { } KeyPath fullPath = TreeUtil.getFullPath(path); - Translation translation = InstanceManager.get(project).store().getData().getTranslation(fullPath); + TranslationValue value = InstanceManager.get(project).store().getData().getTranslation(fullPath); - if (translation == null) { + if (value == null) { return; } - new EditDialog(project, new KeyedTranslation(fullPath, translation)).showAndHandle(); + new EditDialog(project, new Translation(fullPath, value)).showAndHandle(); } private void deleteSelectedNodes() { @@ -147,7 +167,7 @@ public class TreeView implements BusListener { KeyPath fullPath = TreeUtil.getFullPath(path); InstanceManager.get(project).processUpdate( - new TranslationDelete(new KeyedTranslation(fullPath, null)) + new TranslationDelete(new Translation(fullPath, null)) ); } } diff --git a/src/main/java/de/marhali/easyi18n/tabs/mapper/TableModelMapper.java b/src/main/java/de/marhali/easyi18n/tabs/mapper/TableModelMapper.java index 18137c2..62760cd 100644 --- a/src/main/java/de/marhali/easyi18n/tabs/mapper/TableModelMapper.java +++ b/src/main/java/de/marhali/easyi18n/tabs/mapper/TableModelMapper.java @@ -1,8 +1,13 @@ package de.marhali.easyi18n.tabs.mapper; -import de.marhali.easyi18n.model.*; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.action.TranslationUpdate; import de.marhali.easyi18n.model.bus.FilterMissingTranslationsListener; import de.marhali.easyi18n.model.bus.SearchQueryListener; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.util.KeyPathConverter; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -50,10 +55,10 @@ public class TableModelMapper implements TableModel, SearchQueryListener, Filter List matches = new ArrayList<>(); for(KeyPath key : this.data.getFullKeys()) { - if(this.converter.concat(key).toLowerCase().contains(query)) { + if(this.converter.toString(key).toLowerCase().contains(query)) { matches.add(key); } else { - for(String content : this.data.getTranslation(key).values()) { + for(String content : this.data.getTranslation(key).getLocaleContents()) { if(content.toLowerCase().contains(query)) { matches.add(key); } @@ -74,7 +79,7 @@ public class TableModelMapper implements TableModel, SearchQueryListener, Filter List matches = new ArrayList<>(); for(KeyPath key : this.data.getFullKeys()) { - if(this.data.getTranslation(key).values().size() != this.locales.size()) { + if(this.data.getTranslation(key).getLocaleContents().size() != this.locales.size()) { matches.add(key); } } @@ -117,25 +122,25 @@ public class TableModelMapper implements TableModel, SearchQueryListener, Filter KeyPath key = this.fullKeys.get(rowIndex); if(columnIndex == 0) { // Keys - return this.converter.concat(key); + return this.converter.toString(key); } String locale = this.locales.get(columnIndex - 1); - Translation translation = this.data.getTranslation(key); + TranslationValue value = this.data.getTranslation(key); - return translation == null ? null : translation.get(locale); + return value == null ? null : value.get(locale); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { KeyPath key = this.fullKeys.get(rowIndex); - Translation translation = this.data.getTranslation(key); + TranslationValue translation = this.data.getTranslation(key); if(translation == null) { // Unknown cell return; } - KeyPath newKey = columnIndex == 0 ? this.converter.split(String.valueOf(aValue)) : key; + KeyPath newKey = columnIndex == 0 ? this.converter.fromString(String.valueOf(aValue)) : key; // Translation content update if(columnIndex > 0) { @@ -146,8 +151,8 @@ public class TableModelMapper implements TableModel, SearchQueryListener, Filter } } - TranslationUpdate update = new TranslationUpdate(new KeyedTranslation(key, translation), - new KeyedTranslation(newKey, translation)); + TranslationUpdate update = new TranslationUpdate(new Translation(key, translation), + new Translation(newKey, translation)); this.updater.accept(update); } diff --git a/src/main/java/de/marhali/easyi18n/tabs/mapper/TreeModelMapper.java b/src/main/java/de/marhali/easyi18n/tabs/mapper/TreeModelMapper.java index e7468cb..18ff29c 100644 --- a/src/main/java/de/marhali/easyi18n/tabs/mapper/TreeModelMapper.java +++ b/src/main/java/de/marhali/easyi18n/tabs/mapper/TreeModelMapper.java @@ -3,9 +3,14 @@ package de.marhali.easyi18n.tabs.mapper; import com.intellij.ide.projectView.PresentationData; import com.intellij.ui.JBColor; -import de.marhali.easyi18n.model.*; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.TranslationNode; import de.marhali.easyi18n.model.bus.FilterMissingTranslationsListener; import de.marhali.easyi18n.model.bus.SearchQueryListener; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.util.KeyPathConverter; import de.marhali.easyi18n.util.UiUtil; import org.jetbrains.annotations.NotNull; @@ -24,13 +29,13 @@ public class TreeModelMapper extends DefaultTreeModel implements SearchQueryList private final TranslationData data; private final KeyPathConverter converter; - private final SettingsState state; + private final ProjectSettings state; - public TreeModelMapper(TranslationData data, SettingsState state) { + public TreeModelMapper(TranslationData data, ProjectSettings state) { super(null); this.data = data; - this.converter = new KeyPathConverter(state.isNestedKeys()); + this.converter = new KeyPathConverter(state); this.state = state; DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); @@ -41,7 +46,7 @@ public class TreeModelMapper extends DefaultTreeModel implements SearchQueryList @Override public void onSearchQuery(@Nullable String query) { DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); - TranslationData shadow = new TranslationData(this.state.isSortKeys()); + TranslationData shadow = new TranslationData(this.state.isSorting()); if(query == null) { // Reset this.generateNodes(rootNode, this.data.getRootNode()); @@ -52,15 +57,15 @@ public class TreeModelMapper extends DefaultTreeModel implements SearchQueryList query = query.toLowerCase(); for(KeyPath currentKey : this.data.getFullKeys()) { - Translation translation = this.data.getTranslation(currentKey); - String loweredKey = this.converter.concat(currentKey).toLowerCase(); + TranslationValue translation = this.data.getTranslation(currentKey); + String loweredKey = this.converter.toString(currentKey).toLowerCase(); if(query.contains(loweredKey) || loweredKey.contains(query)) { shadow.setTranslation(currentKey, translation); continue; } - for(String currentContent : translation.values()) { + for(String currentContent : translation.getLocaleContents()) { if(currentContent.toLowerCase().contains(query)) { shadow.setTranslation(currentKey, translation); break; @@ -75,7 +80,7 @@ public class TreeModelMapper extends DefaultTreeModel implements SearchQueryList @Override public void onFilterMissingTranslations(boolean filter) { DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); - TranslationData shadow = new TranslationData(this.state.isSortKeys()); + TranslationData shadow = new TranslationData(this.state.isSorting()); if(!filter) { // Reset this.generateNodes(rootNode, this.data.getRootNode()); @@ -84,9 +89,9 @@ public class TreeModelMapper extends DefaultTreeModel implements SearchQueryList } for(KeyPath currentKey : this.data.getFullKeys()) { - Translation translation = this.data.getTranslation(currentKey); + TranslationValue translation = this.data.getTranslation(currentKey); - if(translation.values().size() != this.data.getLocales().size()) { + if(translation.getLocaleContents().size() != this.data.getLocales().size()) { shadow.setTranslation(currentKey, translation); } } @@ -124,7 +129,7 @@ public class TreeModelMapper extends DefaultTreeModel implements SearchQueryList } else { String previewLocale = this.state.getPreviewLocale(); String sub = "(" + previewLocale + ": " + childTranslationNode.getValue().get(previewLocale) + ")"; - String tooltip = UiUtil.generateHtmlTooltip(childTranslationNode.getValue()); + String tooltip = UiUtil.generateHtmlTooltip(childTranslationNode.getValue().getEntries()); PresentationData data = new PresentationData(key, sub, null, null); data.setTooltip(tooltip); diff --git a/src/main/java/de/marhali/easyi18n/util/KeyPathConverter.java b/src/main/java/de/marhali/easyi18n/util/KeyPathConverter.java new file mode 100644 index 0000000..3307d6c --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/util/KeyPathConverter.java @@ -0,0 +1,159 @@ +package de.marhali.easyi18n.util; + +import com.intellij.openapi.project.Project; + +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Stateful utility to transform absolute translation keys into their character literal representation and backwards. + * @author marhali + */ +public class KeyPathConverter { + + private final ProjectSettings settings; + + /** + * Constructs a new converter instance + * @param settings Delimiter configuration + */ + public KeyPathConverter(@NotNull ProjectSettings settings) { + this.settings = settings; + } + + /** + * @see #KeyPathConverter(ProjectSettings) + * @param project Opened project + */ + public KeyPathConverter(@NotNull Project project) { + this(ProjectSettingsService.get(project).getState()); + } + + /** + * Transform to character literal representation + * @param path Absolute key path + * @return Character literal + */ + public @NotNull String toString(@NotNull KeyPath path) { + StringBuilder builder = new StringBuilder(); + + for(int i = 0; i < path.size(); i++) { + if(i > 0) { // Delimiters + if(i == 1 && settings.getFolderStrategy().isNamespaceMode() && settings.getNamespaceDelimiter() != null) { + builder.append(quoteDelimiter(settings.getNamespaceDelimiter())); + } else { + builder.append(quoteDelimiter(settings.getSectionDelimiter())); + } + } + + // Section content + builder.append(quoteSection(path.get(i))); + } + + return builder.toString(); + } + + /** + * Splits provided character literal into key path sections. + * If namespace mode is activated and none was provided, the default namespace will be added. + * @return Layered key path sections + */ + public @NotNull KeyPath fromString(@NotNull String literalPath) { + KeyPath path = new KeyPath(); + + int i = 0; + for(String section : literalPath.split(getSplitRegex())) { + + // Missing namespace + if(i == 0 && settings.getFolderStrategy().isNamespaceMode() && hasDefaultNamespace()) { + String namespaceDelim = (settings.isNestedKeys() ? "" : "\\") + settings.getNamespaceDelimiter(); + if(section.length() == literalPath.length() || !literalPath.substring(section.length()).startsWith(namespaceDelim)) { + path.add(settings.getDefaultNamespace()); + } + } + + path.add(unquoteSection(section)); + + i++; + } + + return path; + } + + @Override + public String toString() { + return "KeyPathConverter{" + + "settings=" + settings + + '}'; + } + + /* + * INTERNAL METHODS + */ + + private boolean hasDefaultNamespace() { + return settings.getDefaultNamespace() != null && !settings.getDefaultNamespace().isEmpty(); + } + + private String getSplitRegex() { + return settings.isNestedKeys() + ? ("(? messages) { + public static String generateHtmlTooltip(Set> messages) { StringBuilder builder = new StringBuilder(); builder.append(""); - for(Map.Entry entry : messages.entrySet()) { + for(Map.Entry entry : messages) { builder.append(""); builder.append(entry.getKey()).append(":"); builder.append(" "); diff --git a/src/main/resources/META-INF/de.marhali.easyi18n-java.xml b/src/main/resources/META-INF/de.marhali.easyi18n-java.xml index 4bf69d7..e172b2b 100644 --- a/src/main/resources/META-INF/de.marhali.easyi18n-java.xml +++ b/src/main/resources/META-INF/de.marhali.easyi18n-java.xml @@ -1,5 +1,27 @@ - + + de.marhali.easyi18n.assistance.intention.JavaTranslationIntention + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/de.marhali.easyi18n-javascript.xml b/src/main/resources/META-INF/de.marhali.easyi18n-javascript.xml index 132273b..8c2dbf0 100644 --- a/src/main/resources/META-INF/de.marhali.easyi18n-javascript.xml +++ b/src/main/resources/META-INF/de.marhali.easyi18n-javascript.xml @@ -1,6 +1,45 @@ - - + + + + + + + + + + + + + + + + + de.marhali.easyi18n.assistance.intention.JsTranslationIntention + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/de.marhali.easyi18n-kotlin.xml b/src/main/resources/META-INF/de.marhali.easyi18n-kotlin.xml index fe81e94..7e5c8c4 100644 --- a/src/main/resources/META-INF/de.marhali.easyi18n-kotlin.xml +++ b/src/main/resources/META-INF/de.marhali.easyi18n-kotlin.xml @@ -1,11 +1,27 @@ - + + de.marhali.easyi18n.assistance.intention.KtTranslationIntention + - + - + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/de.marhali.easyi18n-php.xml b/src/main/resources/META-INF/de.marhali.easyi18n-php.xml new file mode 100644 index 0000000..6b47b4e --- /dev/null +++ b/src/main/resources/META-INF/de.marhali.easyi18n-php.xml @@ -0,0 +1,27 @@ + + + + de.marhali.easyi18n.assistance.intention.PhpTranslationIntention + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/de.marhali.easyi18n-vue.xml b/src/main/resources/META-INF/de.marhali.easyi18n-vue.xml index 68c3fa0..79ed051 100644 --- a/src/main/resources/META-INF/de.marhali.easyi18n-vue.xml +++ b/src/main/resources/META-INF/de.marhali.easyi18n-vue.xml @@ -1,6 +1,15 @@ - + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 4ff888e..4db7d2f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -13,6 +13,7 @@ JavaScript com.intellij.java org.jetbrains.plugins.vue + com.jetbrains.php - + - + - + - + - - - - - + \ No newline at end of file diff --git a/src/main/resources/META-INF/toolWindowIcon.svg b/src/main/resources/META-INF/toolWindowIcon.svg deleted file mode 100644 index a768feb..0000000 --- a/src/main/resources/META-INF/toolWindowIcon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - Layer 1 - - - - - \ No newline at end of file diff --git a/src/main/resources/icons/translate13.svg b/src/main/resources/icons/translate13.svg new file mode 100644 index 0000000..fbe7f69 --- /dev/null +++ b/src/main/resources/icons/translate13.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/translate13_dark.svg b/src/main/resources/icons/translate13_dark.svg new file mode 100644 index 0000000..c43d969 --- /dev/null +++ b/src/main/resources/icons/translate13_dark.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index ed38b92..3df89be 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1,7 +1,8 @@ -view.tree.title=TreeView +documentation=EasyI18n Translation +view.tree.title=Tree View view.tree.collapse=Collapse Tree view.tree.expand=Expand Tree -view.table.title=TableView +view.table.title=Table View view.empty=No translations found. Click on settings to specify a locales directory or reload action.add=Add Translation action.edit=Edit Translation @@ -10,21 +11,51 @@ action.reload=Reload From Disk action.settings=Settings action.search=Search... action.delete=Delete +action.extract=Extract translation translation.key=Key translation.locales=Locales -settings.path.title=Locales Directory -settings.path.text=Locales directory -settings.strategy.title=Translation file structure -settings.strategy.folder=Single Directory;Modularized: Locale / Namespace;Modularized: Namespace / Locale -settings.strategy.folder.tooltip=What is the folder structure of your translation files? -settings.strategy.parser=JSON;JSON5;YAML;YML;Properties;ARB -settings.strategy.parser.tooltip=Which file parser should be used to process your translation files? -settings.strategy.file-pattern.tooltip=Defines a wildcard matcher to filter relevant translation files. For example *.json, *.???. -settings.preview=Preview locale -settings.path.prefix=Path prefix -settings.keys.sort=Sort translation keys alphabetically -settings.keys.nested=Escape delimiter character within a section layer. -settings.editor.assistance=I18n key completion, annotation and reference inside editor +# Settings +settings.hint.text=Project-specific configuration. For an easy start, you can use one of the existing presets. +settings.hint.action=Fore more information, see the documentation +settings.preset.title=Preset +settings.preset.tooltip=Choose a configuration template that best fits your project. After that you can make further changes. +# Resource Configuration +settings.resource.title=Resource Configuration +settings.resource.path.window=Locales Directory +settings.resource.path.title=Locales directory +settings.resource.path.tooltip=Define the folder which contains all translation files. For nested folders, use the top folder. +settings.resource.strategy=File structure +settings.resource.folder.items=Single Directory;Modularized: Locale / Namespace;Modularized: Namespace / Locale +settings.resource.folder.tooltip=What is the folder structure of your translation files? +settings.resource.parser.items=JSON;JSON5;YAML;YML;Properties;ARB +settings.resource.parser.tooltip=Which file parser should be used to process your translation files? +settings.resource.file-pattern.tooltip=Defines a wildcard matcher to filter relevant translation files. For example *.json, *.??? or *.*. +settings.resource.sorting.title=Sort translation keys alphabetically +settings.resource.sorting.tooltip=Sorts all translation keys alphabetically. If disabled, the original key-order in the files is kept. +# Editor Configuration +settings.editor.title=Editor Configuration +settings.editor.key.title=Key delimiters +settings.editor.key.namespace.title=[namespace] +settings.editor.key.namespace.tooltip=Sets the separator used between namespace and key path. +settings.editor.key.section.title=[section] +settings.editor.key.section.tooltip=Sets the separator used between section nodes of the key path. +settings.editor.key.leaf.title=[leaf +settings.editor.key.context.title=context +settings.editor.key.context.tooltip=Sets the separator used to define context-specific variants of a translation. +settings.editor.key.plural.title=pluralization] +settings.editor.key.plural.tooltip=Sets the separator used to define different pluralization's of a translation. +settings.editor.default-namespace.title=Default namespace +settings.editor.default-namespace.tooltip=Specifies the namespace to use by default if none is specified in a translation key. The field can be left blank to ignore this feature. +settings.editor.preview.title=Preview locale +settings.editor.preview.tooltip=Defines the language to be displayed in editor previews. +settings.editor.key.nesting.title=Escape section delimiter within a section layer +settings.editor.key.nesting.tooltip=Escapes the section delimiter within a section to properly reconstruct nested key sections. Disable this feature if nested key sections are the exception rather than the rule in your translation file. +settings.editor.assistance.title=Editor code assistance for translations +settings.editor.assistance.tooltip=Activates editor support to reference, auto-complete and folding of existing translations. +# Experimental configuration +settings.experimental.title=Experimental Configuration +settings.experimental.always-fold.title=Always fold translation keys +settings.experimental.always-fold.tooltip=Forces the editor to always display the value behind a translation key. The value cannot be unfolded when this function is active. error.io=An error occurred while processing translation files. \n\ Config: {0} => {1} ({2}) \n\ Path: {3} \n\ diff --git a/src/main/resources/messages_de.properties b/src/main/resources/messages_de.properties deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/java/de/marhali/easyi18n/KeyPathConverterTest.java b/src/test/java/de/marhali/easyi18n/KeyPathConverterTest.java index 9b18a32..d61ea47 100644 --- a/src/test/java/de/marhali/easyi18n/KeyPathConverterTest.java +++ b/src/test/java/de/marhali/easyi18n/KeyPathConverterTest.java @@ -1,8 +1,13 @@ package de.marhali.easyi18n; +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.io.folder.FolderStrategyType; import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.KeyPathConverter; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.util.KeyPathConverter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Assert; import org.junit.Test; @@ -12,42 +17,151 @@ import org.junit.Test; */ public class KeyPathConverterTest { - private final KeyPathConverter deepMapper = new KeyPathConverter(true); - private final KeyPathConverter flatMapper = new KeyPathConverter(false); - @Test - public void testNestedConcat() { - Assert.assertEquals("first\\\\.section.second.third", - deepMapper.concat(KeyPath.of("first.section", "second", "third"))); + public void noNamespaceDelimiter() { + KeyPathConverter converter = getConverter(FolderStrategyType.MODULARIZED_NAMESPACE, null, ".", null, true); - Assert.assertEquals("first.second.third", - deepMapper.concat(KeyPath.of("first", "second", "third"))); + Assert.assertEquals(new KeyPath("username"), converter.fromString("username")); + Assert.assertEquals(new KeyPath("username:nested"), converter.fromString("username:nested")); + Assert.assertEquals(new KeyPath("username:nested", "leaf"), converter.fromString("username:nested.leaf")); } @Test - public void testNestedSplit() { - Assert.assertEquals(KeyPath.of("first.section", "second", "third"), - deepMapper.split("first\\\\.section.second.third")); + public void emptyDefaultNamespace() { + KeyPathConverter converter = getConverter(FolderStrategyType.MODULARIZED_NAMESPACE, ":", ".", null, true); - Assert.assertEquals(KeyPath.of("first", "second", "third"), - deepMapper.split("first.second.third")); + Assert.assertEquals(new KeyPath("username"), converter.fromString("username")); + Assert.assertEquals(new KeyPath("username", "nested"), converter.fromString("username:nested")); + Assert.assertEquals(new KeyPath("username", "nested", "leaf"), converter.fromString("username:nested.leaf")); } @Test - public void testNonNestedConcat() { - Assert.assertEquals("flat.map\\\\.deeper", - flatMapper.concat(KeyPath.of("flat.map", "deeper"))); + public void nonNestedSingle() { + KeyPathConverter converter = getConverter(FolderStrategyType.SINGLE, null, ".", null, false); - Assert.assertEquals("flat.map.keys", - flatMapper.concat(KeyPath.of("flat.map.keys"))); + Assert.assertEquals("username", converter.toString(new KeyPath("username"))); + Assert.assertEquals("username\\.nested.section", converter.toString(new KeyPath("username", "nested.section"))); + Assert.assertEquals("username.normal.nested", converter.toString(new KeyPath("username.normal.nested"))); + + Assert.assertEquals(new KeyPath("username"), converter.fromString("username")); + Assert.assertEquals(new KeyPath("username", "nested.section"), converter.fromString("username\\.nested.section")); + Assert.assertEquals(new KeyPath("username.normal.nested"), converter.fromString("username.normal.nested")); } @Test - public void testNonNestedSplit() { - Assert.assertEquals(KeyPath.of("flat.keys.with", "deep.section"), - flatMapper.split("flat.keys.with\\\\.deep.section")); + public void nonNestedNamespace() { + KeyPathConverter converter = getConverter(FolderStrategyType.MODULARIZED_NAMESPACE, ":", ".", "common", false); - Assert.assertEquals(KeyPath.of("flat.keys.only"), - flatMapper.split("flat.keys.only")); + Assert.assertEquals("username", converter.toString(new KeyPath("username"))); + Assert.assertEquals("username.title\\:concat.leaf\\.node", converter.toString(new KeyPath("username.title", "concat.leaf", "node"))); + + Assert.assertEquals(new KeyPath("common", "username"), converter.fromString("username")); + Assert.assertEquals(new KeyPath("username.title", "concat", "leaf.node"), converter.fromString("username.title\\:concat\\.leaf.node")); } -} \ No newline at end of file + + @Test + public void single() { + KeyPathConverter converter = getConverter(FolderStrategyType.SINGLE,null, ".", null, true); + + Assert.assertEquals("username", converter.toString(new KeyPath("username"))); + Assert.assertEquals("username.title", converter.toString(new KeyPath("username", "title"))); + Assert.assertEquals("username.nested\\.section", converter.toString(new KeyPath("username", "nested.section"))); + Assert.assertEquals("username.deep.nested", converter.toString(new KeyPath("username", "deep", "nested"))); + + Assert.assertEquals(new KeyPath("username"), converter.fromString("username")); + Assert.assertEquals(new KeyPath("username", "title"), converter.fromString("username.title")); + Assert.assertEquals(new KeyPath("username", "nested.section"), converter.fromString("username.nested\\.section")); + Assert.assertEquals(new KeyPath("username", "deep", "nested"), converter.fromString("username.deep.nested")); + } + + @Test + public void namespace() { + KeyPathConverter converter = getConverter(FolderStrategyType.MODULARIZED_NAMESPACE, ":", ".", "common", true); + + Assert.assertEquals("common", converter.toString(new KeyPath("common"))); + Assert.assertEquals("common:username", converter.toString(new KeyPath("common", "username"))); + Assert.assertEquals("nested\\:common:username", converter.toString(new KeyPath("nested:common", "username"))); + Assert.assertEquals("common:username.nested\\.section", converter.toString(new KeyPath("common", "username", "nested.section"))); + Assert.assertEquals("common:username.deep.nested", converter.toString(new KeyPath("common", "username", "deep", "nested"))); + + Assert.assertEquals(new KeyPath("common", "key"), converter.fromString("key")); + Assert.assertEquals(new KeyPath("common", "common:username", "title"), converter.fromString("common\\:username.title")); + Assert.assertEquals(new KeyPath("user", "title"), converter.fromString("user:title")); + Assert.assertEquals(new KeyPath("user:complex", "deep.nested", "value"), converter.fromString("user\\:complex:deep\\.nested.value")); + } + + private KeyPathConverter getConverter(FolderStrategyType strategy, String namespaceDelim, + String sectionDelim, String defaultNs, boolean nestKeys) { + return new KeyPathConverter(new ProjectSettings() { + @Override + public @Nullable String getLocalesDirectory() { + return null; + } + + @Override + public @NotNull FolderStrategyType getFolderStrategy() { + return strategy; + } + + @Override + public @NotNull ParserStrategyType getParserStrategy() { + return null; + } + + @Override + public @NotNull String getFilePattern() { + return null; + } + + @Override + public boolean isSorting() { + return false; + } + + @Override + public @Nullable String getNamespaceDelimiter() { + return namespaceDelim; + } + + @Override + public @NotNull String getSectionDelimiter() { + return sectionDelim; + } + + @Override + public @Nullable String getContextDelimiter() { + return null; + } + + @Override + public @Nullable String getPluralDelimiter() { + return null; + } + + @Override + public @Nullable String getDefaultNamespace() { + return defaultNs; + } + + @Override + public @NotNull String getPreviewLocale() { + return null; + } + + @Override + public boolean isNestedKeys() { + return nestKeys; + } + + @Override + public boolean isAssistance() { + return false; + } + + @Override + public boolean isAlwaysFold() { + return false; + } + }); + } +} diff --git a/src/test/java/de/marhali/easyi18n/TranslationDataTest.java b/src/test/java/de/marhali/easyi18n/TranslationDataTest.java index 4e788f0..24f4c0f 100644 --- a/src/test/java/de/marhali/easyi18n/TranslationDataTest.java +++ b/src/test/java/de/marhali/easyi18n/TranslationDataTest.java @@ -1,10 +1,10 @@ package de.marhali.easyi18n; -import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationNode; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.TranslationValue; import org.junit.Assert; import org.junit.Test; @@ -17,27 +17,27 @@ import java.util.*; public class TranslationDataTest { private final int numOfTranslations = 14; - private final Translation translation = new Translation("en", "test"); + private final TranslationValue translation = new TranslationValue("en", "test"); private void addTranslations(TranslationData data) { - data.setTranslation(KeyPath.of("zulu"), translation); - data.setTranslation(KeyPath.of("gamma"), translation); + data.setTranslation(new KeyPath("zulu"), translation); + data.setTranslation(new KeyPath("gamma"), translation); - data.setTranslation(KeyPath.of("foxtrot.super.long.key"), translation); - data.setTranslation(KeyPath.of("foxtrot", "super", "long", "key"), translation); + data.setTranslation(new KeyPath("foxtrot.super.long.key"), translation); + data.setTranslation(new KeyPath("foxtrot", "super", "long", "key"), translation); - data.setTranslation(KeyPath.of("charlie.b", "sub"), translation); - data.setTranslation(KeyPath.of("charlie.a", "sub"), translation); + data.setTranslation(new KeyPath("charlie.b", "sub"), translation); + data.setTranslation(new KeyPath("charlie.a", "sub"), translation); - data.setTranslation(KeyPath.of("bravo.b"), translation); - data.setTranslation(KeyPath.of("bravo.c"), translation); - data.setTranslation(KeyPath.of("bravo.a"), translation); - data.setTranslation(KeyPath.of("bravo.d"), translation); + data.setTranslation(new KeyPath("bravo.b"), translation); + data.setTranslation(new KeyPath("bravo.c"), translation); + data.setTranslation(new KeyPath("bravo.a"), translation); + data.setTranslation(new KeyPath("bravo.d"), translation); - data.setTranslation(KeyPath.of("bravo", "b"), translation); - data.setTranslation(KeyPath.of("bravo", "c"), translation); - data.setTranslation(KeyPath.of("bravo", "a"), translation); - data.setTranslation(KeyPath.of("bravo", "d"), translation); + data.setTranslation(new KeyPath("bravo", "b"), translation); + data.setTranslation(new KeyPath("bravo", "c"), translation); + data.setTranslation(new KeyPath("bravo", "a"), translation); + data.setTranslation(new KeyPath("bravo", "d"), translation); } @Test @@ -46,13 +46,13 @@ public class TranslationDataTest { this.addTranslations(data); Set expectation = new LinkedHashSet<>(Arrays.asList( - KeyPath.of("bravo", "a"), KeyPath.of("bravo", "b"), KeyPath.of("bravo", "c"), KeyPath.of("bravo", "d"), - KeyPath.of("bravo.a"), KeyPath.of("bravo.b"), KeyPath.of("bravo.c"), KeyPath.of("bravo.d"), - KeyPath.of("charlie.a", "sub"), KeyPath.of("charlie.b", "sub"), - KeyPath.of("foxtrot", "super", "long", "key"), - KeyPath.of("foxtrot.super.long.key"), - KeyPath.of("gamma"), - KeyPath.of("zulu") + new KeyPath("bravo", "a"), new KeyPath("bravo", "b"), new KeyPath("bravo", "c"), new KeyPath("bravo", "d"), + new KeyPath("bravo.a"), new KeyPath("bravo.b"), new KeyPath("bravo.c"), new KeyPath("bravo.d"), + new KeyPath("charlie.a", "sub"), new KeyPath("charlie.b", "sub"), + new KeyPath("foxtrot", "super", "long", "key"), + new KeyPath("foxtrot.super.long.key"), + new KeyPath("gamma"), + new KeyPath("zulu") )); Assert.assertEquals(data.getFullKeys(), expectation); @@ -65,13 +65,13 @@ public class TranslationDataTest { this.addTranslations(data); Set expectation = new LinkedHashSet<>(Arrays.asList( - KeyPath.of("zulu"), - KeyPath.of("gamma"), - KeyPath.of("foxtrot.super.long.key"), - KeyPath.of("foxtrot", "super", "long", "key"), - KeyPath.of("charlie.b", "sub"), KeyPath.of("charlie.a", "sub"), - KeyPath.of("bravo.b"), KeyPath.of("bravo.c"), KeyPath.of("bravo.a"), KeyPath.of("bravo.d"), - KeyPath.of("bravo", "b"), KeyPath.of("bravo", "c"), KeyPath.of("bravo", "a"), KeyPath.of("bravo", "d") + new KeyPath("zulu"), + new KeyPath("gamma"), + new KeyPath("foxtrot.super.long.key"), + new KeyPath("foxtrot", "super", "long", "key"), + new KeyPath("charlie.b", "sub"), new KeyPath("charlie.a", "sub"), + new KeyPath("bravo.b"), new KeyPath("bravo.c"), new KeyPath("bravo.a"), new KeyPath("bravo.d"), + new KeyPath("bravo", "b"), new KeyPath("bravo", "c"), new KeyPath("bravo", "a"), new KeyPath("bravo", "d") )); Assert.assertEquals(data.getFullKeys(), expectation); @@ -82,35 +82,35 @@ public class TranslationDataTest { public void testDelete() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("alpha"), translation); - data.setTranslation(KeyPath.of("nested.alpha"), translation); - data.setTranslation(KeyPath.of("nested.long.bravo"), translation); + data.setTranslation(new KeyPath("alpha"), translation); + data.setTranslation(new KeyPath("nested.alpha"), translation); + data.setTranslation(new KeyPath("nested.long.bravo"), translation); - data.setTranslation(KeyPath.of("beta"), translation); - data.setTranslation(KeyPath.of("nested", "alpha"), translation); - data.setTranslation(KeyPath.of("nested", "long", "bravo"), translation); + data.setTranslation(new KeyPath("beta"), translation); + data.setTranslation(new KeyPath("nested", "alpha"), translation); + data.setTranslation(new KeyPath("nested", "long", "bravo"), translation); Assert.assertEquals(data.getFullKeys().size(), 6); - data.setTranslation(KeyPath.of("alpha"), null); - data.setTranslation(KeyPath.of("nested.alpha"), null); - data.setTranslation(KeyPath.of("nested.long.bravo"), null); + data.setTranslation(new KeyPath("alpha"), null); + data.setTranslation(new KeyPath("nested.alpha"), null); + data.setTranslation(new KeyPath("nested.long.bravo"), null); Assert.assertEquals(data.getFullKeys().size(), 3); - data.setTranslation(KeyPath.of("beta"), null); - data.setTranslation(KeyPath.of("nested", "alpha"), null); - data.setTranslation(KeyPath.of("nested", "long", "bravo"), null); + data.setTranslation(new KeyPath("beta"), null); + data.setTranslation(new KeyPath("nested", "alpha"), null); + data.setTranslation(new KeyPath("nested", "long", "bravo"), null); Assert.assertEquals(data.getFullKeys().size(), 0); - Assert.assertNull(data.getTranslation(KeyPath.of("alpha"))); - Assert.assertNull(data.getTranslation(KeyPath.of("nested.alpha"))); - Assert.assertNull(data.getTranslation(KeyPath.of("nested.long.bravo"))); + Assert.assertNull(data.getTranslation(new KeyPath("alpha"))); + Assert.assertNull(data.getTranslation(new KeyPath("nested.alpha"))); + Assert.assertNull(data.getTranslation(new KeyPath("nested.long.bravo"))); - Assert.assertNull(data.getTranslation(KeyPath.of("beta"))); - Assert.assertNull(data.getTranslation(KeyPath.of("nested", "alpha"))); - Assert.assertNull(data.getTranslation(KeyPath.of("nested", "long", "bravo"))); + Assert.assertNull(data.getTranslation(new KeyPath("beta"))); + Assert.assertNull(data.getTranslation(new KeyPath("nested", "alpha"))); + Assert.assertNull(data.getTranslation(new KeyPath("nested", "long", "bravo"))); } @Test @@ -118,10 +118,10 @@ public class TranslationDataTest { TranslationData data = new TranslationData(true); this.addTranslations(data); - data.setTranslation(KeyPath.of("foxtrot.super.long.key"), null); - data.setTranslation(KeyPath.of("foxtrot", "super", "long", "key"), null); + data.setTranslation(new KeyPath("foxtrot.super.long.key"), null); + data.setTranslation(new KeyPath("foxtrot", "super", "long", "key"), null); - Assert.assertNull(data.getTranslation(KeyPath.of("foxtrot.super.long.key"))); + Assert.assertNull(data.getTranslation(new KeyPath("foxtrot.super.long.key"))); Assert.assertNull(data.getRootNode().getChildren().get("foxtrot")); Assert.assertEquals(data.getFullKeys().size(), numOfTranslations - 2); } @@ -130,70 +130,70 @@ public class TranslationDataTest { public void testOverwrite() { TranslationData data = new TranslationData(true); - Translation before = new Translation("en", "before"); - Translation after = new Translation("en", "after"); + TranslationValue before = new TranslationValue("en", "before"); + TranslationValue after = new TranslationValue("en", "after"); - data.setTranslation(KeyPath.of("alpha"), before); - data.setTranslation(KeyPath.of("nested.alpha"), before); - data.setTranslation(KeyPath.of("nested.long.bravo"), before); - data.setTranslation(KeyPath.of("beta"), before); - data.setTranslation(KeyPath.of("nested", "alpha"), before); - data.setTranslation(KeyPath.of("nested", "long", "bravo"), before); + data.setTranslation(new KeyPath("alpha"), before); + data.setTranslation(new KeyPath("nested.alpha"), before); + data.setTranslation(new KeyPath("nested.long.bravo"), before); + data.setTranslation(new KeyPath("beta"), before); + data.setTranslation(new KeyPath("nested", "alpha"), before); + data.setTranslation(new KeyPath("nested", "long", "bravo"), before); - Assert.assertEquals(data.getTranslation(KeyPath.of("alpha")), before); - Assert.assertEquals(data.getTranslation(KeyPath.of("nested.alpha")), before); - Assert.assertEquals(data.getTranslation(KeyPath.of("nested.long.bravo")), before); - Assert.assertEquals(data.getTranslation(KeyPath.of("beta")), before); - Assert.assertEquals(data.getTranslation(KeyPath.of("nested", "alpha")), before); - Assert.assertEquals(data.getTranslation(KeyPath.of("nested", "long", "bravo")), before); + Assert.assertEquals(data.getTranslation(new KeyPath("alpha")), before); + Assert.assertEquals(data.getTranslation(new KeyPath("nested.alpha")), before); + Assert.assertEquals(data.getTranslation(new KeyPath("nested.long.bravo")), before); + Assert.assertEquals(data.getTranslation(new KeyPath("beta")), before); + Assert.assertEquals(data.getTranslation(new KeyPath("nested", "alpha")), before); + Assert.assertEquals(data.getTranslation(new KeyPath("nested", "long", "bravo")), before); - data.setTranslation(KeyPath.of("alpha"), after); - data.setTranslation(KeyPath.of("nested.alpha"), after); - data.setTranslation(KeyPath.of("nested.long.bravo"), after); - data.setTranslation(KeyPath.of("beta"), after); - data.setTranslation(KeyPath.of("nested", "alpha"), after); - data.setTranslation(KeyPath.of("nested", "long", "bravo"), after); + data.setTranslation(new KeyPath("alpha"), after); + data.setTranslation(new KeyPath("nested.alpha"), after); + data.setTranslation(new KeyPath("nested.long.bravo"), after); + data.setTranslation(new KeyPath("beta"), after); + data.setTranslation(new KeyPath("nested", "alpha"), after); + data.setTranslation(new KeyPath("nested", "long", "bravo"), after); - Assert.assertEquals(data.getTranslation(KeyPath.of("alpha")), after); - Assert.assertEquals(data.getTranslation(KeyPath.of("nested.alpha")), after); - Assert.assertEquals(data.getTranslation(KeyPath.of("nested.long.bravo")), after); - Assert.assertEquals(data.getTranslation(KeyPath.of("beta")), after); - Assert.assertEquals(data.getTranslation(KeyPath.of("nested", "alpha")), after); - Assert.assertEquals(data.getTranslation(KeyPath.of("nested", "long", "bravo")), after); + Assert.assertEquals(data.getTranslation(new KeyPath("alpha")), after); + Assert.assertEquals(data.getTranslation(new KeyPath("nested.alpha")), after); + Assert.assertEquals(data.getTranslation(new KeyPath("nested.long.bravo")), after); + Assert.assertEquals(data.getTranslation(new KeyPath("beta")), after); + Assert.assertEquals(data.getTranslation(new KeyPath("nested", "alpha")), after); + Assert.assertEquals(data.getTranslation(new KeyPath("nested", "long", "bravo")), after); } @Test public void testTransformRecursively() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("alpha.nested.key"), translation); - data.setTranslation(KeyPath.of("alpha.other"), translation); - data.setTranslation(KeyPath.of("bravo"), translation); - data.setTranslation(KeyPath.of("alpha", "nested", "key"), translation); - data.setTranslation(KeyPath.of("alpha", "other"), translation); - data.setTranslation(KeyPath.of("charlie"), translation); + data.setTranslation(new KeyPath("alpha.nested.key"), translation); + data.setTranslation(new KeyPath("alpha.other"), translation); + data.setTranslation(new KeyPath("bravo"), translation); + data.setTranslation(new KeyPath("alpha", "nested", "key"), translation); + data.setTranslation(new KeyPath("alpha", "other"), translation); + data.setTranslation(new KeyPath("charlie"), translation); Assert.assertEquals(6, data.getFullKeys().size()); - data.setTranslation(KeyPath.of("alpha.nested"), translation); - data.setTranslation(KeyPath.of("alpha.other.new"), translation); - data.setTranslation(KeyPath.of("bravo"), null); - data.setTranslation(KeyPath.of("alpha", "nested"), translation); - data.setTranslation(KeyPath.of("alpha", "other", "new"), translation); - data.setTranslation(KeyPath.of("charlie"), null); + data.setTranslation(new KeyPath("alpha.nested"), translation); + data.setTranslation(new KeyPath("alpha.other.new"), translation); + data.setTranslation(new KeyPath("bravo"), null); + data.setTranslation(new KeyPath("alpha", "nested"), translation); + data.setTranslation(new KeyPath("alpha", "other", "new"), translation); + data.setTranslation(new KeyPath("charlie"), null); Assert.assertEquals(6, data.getFullKeys().size()); - Assert.assertNotNull(data.getTranslation(KeyPath.of("alpha.nested.key"))); - Assert.assertNotNull(data.getTranslation(KeyPath.of("alpha.other"))); - Assert.assertNull(data.getTranslation(KeyPath.of("bravo"))); - Assert.assertEquals(data.getTranslation(KeyPath.of("alpha.nested")), translation); - Assert.assertEquals(data.getTranslation(KeyPath.of("alpha.other.new")), translation); + Assert.assertNotNull(data.getTranslation(new KeyPath("alpha.nested.key"))); + Assert.assertNotNull(data.getTranslation(new KeyPath("alpha.other"))); + Assert.assertNull(data.getTranslation(new KeyPath("bravo"))); + Assert.assertEquals(data.getTranslation(new KeyPath("alpha.nested")), translation); + Assert.assertEquals(data.getTranslation(new KeyPath("alpha.other.new")), translation); - Assert.assertNull(data.getTranslation(KeyPath.of("alpha", "nested", "key"))); - Assert.assertNull(data.getTranslation(KeyPath.of("alpha", "other"))); - Assert.assertNull(data.getTranslation(KeyPath.of("charlie"))); - Assert.assertEquals(data.getTranslation(KeyPath.of("alpha", "nested")), translation); - Assert.assertEquals(data.getTranslation(KeyPath.of("alpha", "other", "new")), translation); + Assert.assertNull(data.getTranslation(new KeyPath("alpha", "nested", "key"))); + Assert.assertNull(data.getTranslation(new KeyPath("alpha", "other"))); + Assert.assertNull(data.getTranslation(new KeyPath("charlie"))); + Assert.assertEquals(data.getTranslation(new KeyPath("alpha", "nested")), translation); + Assert.assertEquals(data.getTranslation(new KeyPath("alpha", "other", "new")), translation); } } \ No newline at end of file diff --git a/src/test/java/de/marhali/easyi18n/mapper/AbstractMapperTest.java b/src/test/java/de/marhali/easyi18n/mapper/AbstractMapperTest.java index 9771660..c031e88 100644 --- a/src/test/java/de/marhali/easyi18n/mapper/AbstractMapperTest.java +++ b/src/test/java/de/marhali/easyi18n/mapper/AbstractMapperTest.java @@ -1,6 +1,6 @@ package de.marhali.easyi18n.mapper; -import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationValue; import org.junit.Test; @@ -39,7 +39,7 @@ public abstract class AbstractMapperTest { @Test public abstract void testNumbers(); - protected Translation create(String content) { - return new Translation("en", content); + protected TranslationValue create(String content) { + return new TranslationValue("en", content); } } \ No newline at end of file diff --git a/src/test/java/de/marhali/easyi18n/mapper/Json5MapperTest.java b/src/test/java/de/marhali/easyi18n/mapper/Json5MapperTest.java index befdc04..6be4c79 100644 --- a/src/test/java/de/marhali/easyi18n/mapper/Json5MapperTest.java +++ b/src/test/java/de/marhali/easyi18n/mapper/Json5MapperTest.java @@ -3,8 +3,8 @@ package de.marhali.easyi18n.mapper; import de.marhali.easyi18n.io.parser.json.JsonArrayMapper; import de.marhali.easyi18n.io.parser.json5.Json5ArrayMapper; import de.marhali.easyi18n.io.parser.json5.Json5Mapper; -import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.KeyPath; import de.marhali.json5.Json5Object; import de.marhali.json5.Json5Primitive; @@ -58,8 +58,8 @@ public class Json5MapperTest extends AbstractMapperTest { @Override public void testArrays() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("simple"), create(arraySimple)); - data.setTranslation(KeyPath.of("escaped"), create(arrayEscaped)); + data.setTranslation(new KeyPath("simple"), create(arraySimple)); + data.setTranslation(new KeyPath("escaped"), create(arrayEscaped)); Json5Object output = new Json5Object(); Json5Mapper.write("en", output, data.getRootNode()); @@ -72,14 +72,14 @@ public class Json5MapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); Json5Mapper.read("en", output, input.getRootNode()); - Assert.assertTrue(JsonArrayMapper.isArray(input.getTranslation(KeyPath.of("simple")).get("en"))); - Assert.assertTrue(JsonArrayMapper.isArray(input.getTranslation(KeyPath.of("escaped")).get("en"))); + Assert.assertTrue(JsonArrayMapper.isArray(input.getTranslation(new KeyPath("simple")).get("en"))); + Assert.assertTrue(JsonArrayMapper.isArray(input.getTranslation(new KeyPath("escaped")).get("en"))); } @Override public void testSpecialCharacters() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("chars"), create(specialCharacters)); + data.setTranslation(new KeyPath("chars"), create(specialCharacters)); Json5Object output = new Json5Object(); Json5Mapper.write("en", output, data.getRootNode()); @@ -90,13 +90,13 @@ public class Json5MapperTest extends AbstractMapperTest { Json5Mapper.read("en", output, input.getRootNode()); Assert.assertEquals(specialCharacters, - StringEscapeUtils.unescapeJava(input.getTranslation(KeyPath.of("chars")).get("en"))); + StringEscapeUtils.unescapeJava(input.getTranslation(new KeyPath("chars")).get("en"))); } @Override public void testNestedKeys() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("nested", "key", "section"), create("test")); + data.setTranslation(new KeyPath("nested", "key", "section"), create("test")); Json5Object output = new Json5Object(); Json5Mapper.write("en", output, data.getRootNode()); @@ -106,13 +106,13 @@ public class Json5MapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); Json5Mapper.read("en", output, input.getRootNode()); - Assert.assertEquals("test", input.getTranslation(KeyPath.of("nested", "key", "section")).get("en")); + Assert.assertEquals("test", input.getTranslation(new KeyPath("nested", "key", "section")).get("en")); } @Override public void testNonNestedKeys() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("long.key.with.many.sections"), create("test")); + data.setTranslation(new KeyPath("long.key.with.many.sections"), create("test")); Json5Object output = new Json5Object(); Json5Mapper.write("en", output, data.getRootNode()); @@ -122,13 +122,13 @@ public class Json5MapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); Json5Mapper.read("en", output, input.getRootNode()); - Assert.assertEquals("test", input.getTranslation(KeyPath.of("long.key.with.many.sections")).get("en")); + Assert.assertEquals("test", input.getTranslation(new KeyPath("long.key.with.many.sections")).get("en")); } @Override public void testLeadingSpace() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("space"), create(leadingSpace)); + data.setTranslation(new KeyPath("space"), create(leadingSpace)); Json5Object output = new Json5Object(); Json5Mapper.write("en", output, data.getRootNode()); @@ -138,13 +138,13 @@ public class Json5MapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); Json5Mapper.read("en", output, input.getRootNode()); - Assert.assertEquals(leadingSpace, input.getTranslation(KeyPath.of("space")).get("en")); + Assert.assertEquals(leadingSpace, input.getTranslation(new KeyPath("space")).get("en")); } @Override public void testNumbers() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("numbered"), create("15000")); + data.setTranslation(new KeyPath("numbered"), create("15000")); Json5Object output = new Json5Object(); Json5Mapper.write("en", output, data.getRootNode()); @@ -155,6 +155,6 @@ public class Json5MapperTest extends AbstractMapperTest { input.addProperty("numbered", 143.23); Json5Mapper.read("en", input, data.getRootNode()); - Assert.assertEquals("143.23", data.getTranslation(KeyPath.of("numbered")).get("en")); + Assert.assertEquals("143.23", data.getTranslation(new KeyPath("numbered")).get("en")); } } \ No newline at end of file diff --git a/src/test/java/de/marhali/easyi18n/mapper/JsonMapperTest.java b/src/test/java/de/marhali/easyi18n/mapper/JsonMapperTest.java index 78be880..7647671 100644 --- a/src/test/java/de/marhali/easyi18n/mapper/JsonMapperTest.java +++ b/src/test/java/de/marhali/easyi18n/mapper/JsonMapperTest.java @@ -6,9 +6,9 @@ import com.google.gson.JsonPrimitive; import de.marhali.easyi18n.io.parser.json.JsonArrayMapper; import de.marhali.easyi18n.io.parser.json.JsonMapper; -import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.KeyPath; import org.apache.commons.lang.StringEscapeUtils; import org.junit.Assert; @@ -59,8 +59,8 @@ public class JsonMapperTest extends AbstractMapperTest { @Override public void testArrays() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("simple"), create(arraySimple)); - data.setTranslation(KeyPath.of("escaped"), create(arrayEscaped)); + data.setTranslation(new KeyPath("simple"), create(arraySimple)); + data.setTranslation(new KeyPath("escaped"), create(arrayEscaped)); JsonObject output = new JsonObject(); JsonMapper.write("en", output, data.getRootNode()); @@ -73,14 +73,14 @@ public class JsonMapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); JsonMapper.read("en", output, input.getRootNode()); - Assert.assertTrue(JsonArrayMapper.isArray(input.getTranslation(KeyPath.of("simple")).get("en"))); - Assert.assertTrue(JsonArrayMapper.isArray(input.getTranslation(KeyPath.of("escaped")).get("en"))); + Assert.assertTrue(JsonArrayMapper.isArray(input.getTranslation(new KeyPath("simple")).get("en"))); + Assert.assertTrue(JsonArrayMapper.isArray(input.getTranslation(new KeyPath("escaped")).get("en"))); } @Override public void testSpecialCharacters() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("chars"), create(specialCharacters)); + data.setTranslation(new KeyPath("chars"), create(specialCharacters)); JsonObject output = new JsonObject(); JsonMapper.write("en", output, data.getRootNode()); @@ -91,13 +91,13 @@ public class JsonMapperTest extends AbstractMapperTest { JsonMapper.read("en", output, input.getRootNode()); Assert.assertEquals(specialCharacters, - StringEscapeUtils.unescapeJava(input.getTranslation(KeyPath.of("chars")).get("en"))); + StringEscapeUtils.unescapeJava(input.getTranslation(new KeyPath("chars")).get("en"))); } @Override public void testNestedKeys() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("nested", "key", "section"), create("test")); + data.setTranslation(new KeyPath("nested", "key", "section"), create("test")); JsonObject output = new JsonObject(); JsonMapper.write("en", output, data.getRootNode()); @@ -107,13 +107,13 @@ public class JsonMapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); JsonMapper.read("en", output, input.getRootNode()); - Assert.assertEquals("test", input.getTranslation(KeyPath.of("nested", "key", "section")).get("en")); + Assert.assertEquals("test", input.getTranslation(new KeyPath("nested", "key", "section")).get("en")); } @Override public void testNonNestedKeys() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("long.key.with.many.sections"), create("test")); + data.setTranslation(new KeyPath("long.key.with.many.sections"), create("test")); JsonObject output = new JsonObject(); JsonMapper.write("en", output, data.getRootNode()); @@ -123,13 +123,13 @@ public class JsonMapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); JsonMapper.read("en", output, input.getRootNode()); - Assert.assertEquals("test", input.getTranslation(KeyPath.of("long.key.with.many.sections")).get("en")); + Assert.assertEquals("test", input.getTranslation(new KeyPath("long.key.with.many.sections")).get("en")); } @Override public void testLeadingSpace() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("space"), create(leadingSpace)); + data.setTranslation(new KeyPath("space"), create(leadingSpace)); JsonObject output = new JsonObject(); JsonMapper.write("en", output, data.getRootNode()); @@ -139,13 +139,13 @@ public class JsonMapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); JsonMapper.read("en", output, input.getRootNode()); - Assert.assertEquals(leadingSpace, input.getTranslation(KeyPath.of("space")).get("en")); + Assert.assertEquals(leadingSpace, input.getTranslation(new KeyPath("space")).get("en")); } @Override public void testNumbers() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("numbered"), create("15000")); + data.setTranslation(new KeyPath("numbered"), create("15000")); JsonObject output = new JsonObject(); JsonMapper.write("en", output, data.getRootNode()); @@ -156,6 +156,6 @@ public class JsonMapperTest extends AbstractMapperTest { input.addProperty("numbered", 143.23); JsonMapper.read("en", input, data.getRootNode()); - Assert.assertEquals("143.23", data.getTranslation(KeyPath.of("numbered")).get("en")); + Assert.assertEquals("143.23", data.getTranslation(new KeyPath("numbered")).get("en")); } } \ No newline at end of file diff --git a/src/test/java/de/marhali/easyi18n/mapper/PropertiesMapperTest.java b/src/test/java/de/marhali/easyi18n/mapper/PropertiesMapperTest.java index 2643981..5667613 100644 --- a/src/test/java/de/marhali/easyi18n/mapper/PropertiesMapperTest.java +++ b/src/test/java/de/marhali/easyi18n/mapper/PropertiesMapperTest.java @@ -1,12 +1,18 @@ package de.marhali.easyi18n.mapper; +import de.marhali.easyi18n.io.folder.FolderStrategyType; +import de.marhali.easyi18n.io.parser.ParserStrategyType; import de.marhali.easyi18n.io.parser.properties.PropertiesArrayMapper; import de.marhali.easyi18n.io.parser.properties.PropertiesMapper; import de.marhali.easyi18n.io.parser.properties.SortableProperties; -import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.util.KeyPathConverter; import org.apache.commons.lang.StringEscapeUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Assert; import java.util.*; @@ -25,10 +31,10 @@ public class PropertiesMapperTest extends AbstractMapperTest { input.setProperty("bravo", "test"); TranslationData data = new TranslationData(false); - PropertiesMapper.read("en", input, data); + PropertiesMapper.read("en", input, data, converter(true)); SortableProperties output = new SortableProperties(false); - PropertiesMapper.write("en", output, data); + PropertiesMapper.write("en", output, data, converter(true)); List expect = Arrays.asList("zulu", "alpha", "bravo"); Assert.assertEquals(expect, new ArrayList<>(output.keySet())); @@ -42,10 +48,10 @@ public class PropertiesMapperTest extends AbstractMapperTest { input.setProperty("bravo", "test"); TranslationData data = new TranslationData(true); - PropertiesMapper.read("en", input, data); + PropertiesMapper.read("en", input, data, converter(true)); SortableProperties output = new SortableProperties(true); - PropertiesMapper.write("en", output, data); + PropertiesMapper.write("en", output, data, converter(true)); List expect = Arrays.asList("alpha", "bravo", "zulu"); Assert.assertEquals(expect, new ArrayList<>(output.keySet())); @@ -54,11 +60,11 @@ public class PropertiesMapperTest extends AbstractMapperTest { @Override public void testArrays() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("simple"), create(arraySimple)); - data.setTranslation(KeyPath.of("escaped"), create(arrayEscaped)); + data.setTranslation(new KeyPath("simple"), create(arraySimple)); + data.setTranslation(new KeyPath("escaped"), create(arrayEscaped)); SortableProperties output = new SortableProperties(true); - PropertiesMapper.write("en", output, data); + PropertiesMapper.write("en", output, data, converter(true)); Assert.assertTrue(output.get("simple") instanceof String[]); Assert.assertEquals(arraySimple, PropertiesArrayMapper.read((String[]) output.get("simple"))); @@ -66,93 +72,169 @@ public class PropertiesMapperTest extends AbstractMapperTest { Assert.assertEquals(arrayEscaped, StringEscapeUtils.unescapeJava(PropertiesArrayMapper.read((String[]) output.get("escaped")))); TranslationData input = new TranslationData(true); - PropertiesMapper.read("en", output, input); + PropertiesMapper.read("en", output, input, converter(true)); - Assert.assertTrue(PropertiesArrayMapper.isArray(input.getTranslation(KeyPath.of("simple")).get("en"))); - Assert.assertTrue(PropertiesArrayMapper.isArray(input.getTranslation(KeyPath.of("escaped")).get("en"))); + Assert.assertTrue(PropertiesArrayMapper.isArray(input.getTranslation(new KeyPath("simple")).get("en"))); + Assert.assertTrue(PropertiesArrayMapper.isArray(input.getTranslation(new KeyPath("escaped")).get("en"))); } @Override public void testSpecialCharacters() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("chars"), create(specialCharacters)); + data.setTranslation(new KeyPath("chars"), create(specialCharacters)); SortableProperties output = new SortableProperties(true); - PropertiesMapper.write("en", output, data); + PropertiesMapper.write("en", output, data, converter(true)); Assert.assertEquals(specialCharacters, output.get("chars")); TranslationData input = new TranslationData(true); - PropertiesMapper.read("en", output, input); + PropertiesMapper.read("en", output, input, converter(true)); - Assert.assertEquals(specialCharacters, StringEscapeUtils.unescapeJava(input.getTranslation(KeyPath.of("chars")).get("en"))); + Assert.assertEquals(specialCharacters, StringEscapeUtils.unescapeJava(input.getTranslation(new KeyPath("chars")).get("en"))); } @Override public void testNestedKeys() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("nested", "key", "sections"), create("test")); + data.setTranslation(new KeyPath("nested", "key", "sections"), create("test")); SortableProperties output = new SortableProperties(true); - PropertiesMapper.write("en", output, data); + PropertiesMapper.write("en", output, data, converter(true)); - Assert.assertEquals("test", output.get("nested.key.sections")); + Assert.assertEquals("test", output.get("nested:key.sections")); TranslationData input = new TranslationData(true); - PropertiesMapper.read("en", output, input); - - System.out.println(input); + PropertiesMapper.read("en", output, input, converter(true)); Assert.assertTrue(input.getRootNode().getChildren().containsKey("nested")); - Assert.assertEquals("test", input.getTranslation(KeyPath.of("nested", "key", "sections")).get("en")); + Assert.assertEquals("test", input.getTranslation(new KeyPath("nested", "key", "sections")).get("en")); } @Override public void testNonNestedKeys() { // Note: Key nesting is not supported in properties file. TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("long.key.with.many.sections"), create("test")); + data.setTranslation(new KeyPath("long.key.with.many.sections"), create("test")); SortableProperties output = new SortableProperties(true); - PropertiesMapper.write("en", output, data); + PropertiesMapper.write("en", output, data, converter(false)); Assert.assertNotNull(output.get("long.key.with.many.sections")); TranslationData input = new TranslationData(true); - PropertiesMapper.read("en", output, input); + PropertiesMapper.read("en", output, input, converter(false)); - Assert.assertEquals("test", input.getTranslation(KeyPath.of("long", "key", "with", "many", "sections")).get("en")); + Assert.assertEquals("test", input.getTranslation(new KeyPath("long.key.with.many.sections")).get("en")); } @Override public void testLeadingSpace() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("space"), create(leadingSpace)); + data.setTranslation(new KeyPath("space"), create(leadingSpace)); SortableProperties output = new SortableProperties(true); - PropertiesMapper.write("en", output, data); + PropertiesMapper.write("en", output, data, converter()); Assert.assertEquals(leadingSpace, output.get("space")); TranslationData input = new TranslationData(true); - PropertiesMapper.read("en", output, input); + PropertiesMapper.read("en", output, input, converter()); - Assert.assertEquals(leadingSpace, input.getTranslation(KeyPath.of("space")).get("en")); + Assert.assertEquals(leadingSpace, input.getTranslation(new KeyPath("space")).get("en")); } @Override public void testNumbers() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("numbered"), create("15000")); + data.setTranslation(new KeyPath("numbered"), create("15000")); SortableProperties output = new SortableProperties(true); - PropertiesMapper.write("en", output, data); + PropertiesMapper.write("en", output, data, converter()); Assert.assertEquals(15000, output.get("numbered")); SortableProperties input = new SortableProperties(true); input.put("numbered", 143.23); - PropertiesMapper.read("en", input, data); + PropertiesMapper.read("en", input, data, converter()); - Assert.assertEquals("143.23", data.getTranslation(KeyPath.of("numbered")).get("en")); + Assert.assertEquals("143.23", data.getTranslation(new KeyPath("numbered")).get("en")); + } + + private KeyPathConverter converter() { + return converter(true); + } + + private KeyPathConverter converter(boolean nestKeys) { + return new KeyPathConverter(new ProjectSettings() { + @Override + public @Nullable String getLocalesDirectory() { + return null; + } + + @Override + public @NotNull FolderStrategyType getFolderStrategy() { + return FolderStrategyType.MODULARIZED_NAMESPACE; + } + + @Override + public @NotNull ParserStrategyType getParserStrategy() { + return ParserStrategyType.PROPERTIES; + } + + @Override + public @NotNull String getFilePattern() { + return null; + } + + @Override + public boolean isSorting() { + return true; + } + + @Override + public @Nullable String getNamespaceDelimiter() { + return ":"; + } + + @Override + public @NotNull String getSectionDelimiter() { + return "."; + } + + @Override + public @Nullable String getContextDelimiter() { + return null; + } + + @Override + public @Nullable String getPluralDelimiter() { + return null; + } + + @Override + public @Nullable String getDefaultNamespace() { + return null; + } + + @Override + public @NotNull String getPreviewLocale() { + return null; + } + + @Override + public boolean isNestedKeys() { + return nestKeys; + } + + @Override + public boolean isAssistance() { + return false; + } + + @Override + public boolean isAlwaysFold() { + return false; + } + }); } } \ No newline at end of file diff --git a/src/test/java/de/marhali/easyi18n/mapper/YamlMapperTest.java b/src/test/java/de/marhali/easyi18n/mapper/YamlMapperTest.java index 2cb9d01..9eefe5c 100644 --- a/src/test/java/de/marhali/easyi18n/mapper/YamlMapperTest.java +++ b/src/test/java/de/marhali/easyi18n/mapper/YamlMapperTest.java @@ -2,8 +2,8 @@ package de.marhali.easyi18n.mapper; import de.marhali.easyi18n.io.parser.yaml.YamlArrayMapper; import de.marhali.easyi18n.io.parser.yaml.YamlMapper; -import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.KeyPath; import org.apache.commons.lang.StringEscapeUtils; import org.junit.Assert; @@ -58,8 +58,8 @@ public class YamlMapperTest extends AbstractMapperTest { @Override public void testArrays() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("simple"), create(arraySimple)); - data.setTranslation(KeyPath.of("escaped"), create(arrayEscaped)); + data.setTranslation(new KeyPath("simple"), create(arraySimple)); + data.setTranslation(new KeyPath("escaped"), create(arrayEscaped)); Section output = new MapSection(); YamlMapper.write("en", output, data.getRootNode()); @@ -72,14 +72,14 @@ public class YamlMapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); YamlMapper.read("en", output, input.getRootNode()); - Assert.assertTrue(YamlArrayMapper.isArray(input.getTranslation(KeyPath.of("simple")).get("en"))); - Assert.assertTrue(YamlArrayMapper.isArray(input.getTranslation(KeyPath.of("escaped")).get("en"))); + Assert.assertTrue(YamlArrayMapper.isArray(input.getTranslation(new KeyPath("simple")).get("en"))); + Assert.assertTrue(YamlArrayMapper.isArray(input.getTranslation(new KeyPath("escaped")).get("en"))); } @Override public void testSpecialCharacters() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("chars"), create(specialCharacters)); + data.setTranslation(new KeyPath("chars"), create(specialCharacters)); Section output = new MapSection(); YamlMapper.write("en", output, data.getRootNode()); @@ -90,13 +90,13 @@ public class YamlMapperTest extends AbstractMapperTest { YamlMapper.read("en", output, input.getRootNode()); Assert.assertEquals(specialCharacters, - StringEscapeUtils.unescapeJava(input.getTranslation(KeyPath.of("chars")).get("en"))); + StringEscapeUtils.unescapeJava(input.getTranslation(new KeyPath("chars")).get("en"))); } @Override public void testNestedKeys() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("nested", "key", "section"), create("test")); + data.setTranslation(new KeyPath("nested", "key", "section"), create("test")); Section output = new MapSection(); YamlMapper.write("en", output, data.getRootNode()); @@ -106,13 +106,13 @@ public class YamlMapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); YamlMapper.read("en", output, input.getRootNode()); - Assert.assertEquals("test", input.getTranslation(KeyPath.of("nested", "key", "section")).get("en")); + Assert.assertEquals("test", input.getTranslation(new KeyPath("nested", "key", "section")).get("en")); } @Override public void testNonNestedKeys() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("long.key.with.many.sections"), create("test")); + data.setTranslation(new KeyPath("long.key.with.many.sections"), create("test")); Section output = new MapSection(); YamlMapper.write("en", output, data.getRootNode()); @@ -122,13 +122,13 @@ public class YamlMapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); YamlMapper.read("en", output, input.getRootNode()); - Assert.assertEquals("test", input.getTranslation(KeyPath.of("long.key.with.many.sections")).get("en")); + Assert.assertEquals("test", input.getTranslation(new KeyPath("long.key.with.many.sections")).get("en")); } @Override public void testLeadingSpace() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("space"), create(leadingSpace)); + data.setTranslation(new KeyPath("space"), create(leadingSpace)); Section output = new MapSection(); YamlMapper.write("en", output, data.getRootNode()); @@ -138,13 +138,13 @@ public class YamlMapperTest extends AbstractMapperTest { TranslationData input = new TranslationData(true); YamlMapper.read("en", output, input.getRootNode()); - Assert.assertEquals(leadingSpace, input.getTranslation(KeyPath.of("space")).get("en")); + Assert.assertEquals(leadingSpace, input.getTranslation(new KeyPath("space")).get("en")); } @Override public void testNumbers() { TranslationData data = new TranslationData(true); - data.setTranslation(KeyPath.of("numbered"), create("15000")); + data.setTranslation(new KeyPath("numbered"), create("15000")); Section output = new MapSection(); YamlMapper.write("en", output, data.getRootNode()); @@ -155,6 +155,6 @@ public class YamlMapperTest extends AbstractMapperTest { input.set("numbered", 143.23); YamlMapper.read("en", input, data.getRootNode()); - Assert.assertEquals("143.23", data.getTranslation(KeyPath.of("numbered")).get("en")); + Assert.assertEquals("143.23", data.getTranslation(new KeyPath("numbered")).get("en")); } } \ No newline at end of file