From c03d97f3e8c1e1eb93a6c1de47ef399f1b4aa711 Mon Sep 17 00:00:00 2001 From: marhali Date: Sun, 29 May 2022 15:35:34 +0200 Subject: [PATCH] consider subdirectories for modularized translation files Resolves #124 --- CHANGELOG.md | 1 + .../easyi18n/io/folder/FolderStrategy.java | 12 ++++- .../folder/ModularLocaleFolderStrategy.java | 49 ++++++++++++++++--- .../ModularNamespaceFolderStrategy.java | 45 ++++++++++++----- .../easyi18n/io/parser/ParserStrategy.java | 10 +--- .../easyi18n/model/TranslationData.java | 14 ++++++ .../easyi18n/model/TranslationFile.java | 6 +-- src/main/resources/messages.properties | 2 + 8 files changed, 105 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e2923..a80a6dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Duplicate translation values filter - Indicate translations with duplicated values yellow - Multiple translation filters can be used together +- Option to consider subdirectories for modularized translation files ### Changed - Reengineered how translation filters are applied internally 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 5e95367..ad56024 100644 --- a/src/main/java/de/marhali/easyi18n/io/folder/FolderStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/folder/FolderStrategy.java @@ -6,8 +6,8 @@ import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategyType; 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; @@ -71,4 +71,14 @@ public abstract class FolderStrategy { return Objects.requireNonNull(vf); } + + /** + * Checks whether a given file or directory exists + * @param parent Parent path + * @param child File / Directory name + * @return true if file is existing otherwise false + */ + protected boolean exists(@NotNull String parent, @NotNull String child) { + return LocalFileSystem.getInstance().findFileByIoFile(new File(parent, child)) != null; + } } 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 651c9ea..1e0f025 100644 --- a/src/main/java/de/marhali/easyi18n/io/folder/ModularLocaleFolderStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/folder/ModularLocaleFolderStrategy.java @@ -3,8 +3,10 @@ package de.marhali.easyi18n.io.folder; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.model.KeyPath; 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; @@ -12,6 +14,7 @@ import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Modularized translation folder strategy by locale. @@ -31,12 +34,26 @@ public class ModularLocaleFolderStrategy extends FolderStrategy { for(VirtualFile localeModuleDir : localesDirectory.getChildren()) { if(localeModuleDir.isDirectory()) { String locale = localeModuleDir.getNameWithoutExtension(); + files.addAll(findNamespaceFiles(locale, new KeyPath(), localeModuleDir)); + } + } - for(VirtualFile namespaceFile : localeModuleDir.getChildren()) { - if(super.isFileRelevant(namespaceFile)) { - files.add(new TranslationFile(namespaceFile, locale, namespaceFile.getNameWithoutExtension())); - } + return files; + } + + private List findNamespaceFiles(@NotNull String locale, @NotNull KeyPath ns, @NotNull VirtualFile dir) { + List files = new ArrayList<>(); + + for(VirtualFile namespaceFile : dir.getChildren()) { + if(namespaceFile.isDirectory()) { + if(settings.isIncludeSubDirs()) { + files.addAll(findNamespaceFiles(locale, new KeyPath(ns, namespaceFile.getName()), namespaceFile)); } + continue; + } + + if(super.isFileRelevant(namespaceFile)) { + files.add(new TranslationFile(namespaceFile, locale, new KeyPath(ns, namespaceFile.getNameWithoutExtension()))); } } @@ -51,12 +68,28 @@ public class ModularLocaleFolderStrategy extends FolderStrategy { List files = new ArrayList<>(); for(String locale : data.getLocales()) { - for(String namespace : data.getRootNode().getChildren().keySet()) { - VirtualFile vf = super.constructFile(localesPath + "/" + locale, - namespace + "." + type.getFileExtension()); + files.addAll(this.createNamespaceFiles(localesPath, locale, new KeyPath(), type, data.getRootNode())); + } - files.add(new TranslationFile(vf, locale, namespace)); + return files; + } + + private List createNamespaceFiles( + String localesPath, String locale, KeyPath path, + ParserStrategyType type, TranslationNode node) throws IOException { + + List files = new ArrayList<>(); + + for(Map.Entry entry : node.getChildren().entrySet()) { + String parentPath = localesPath + "/" + locale + "/" + String.join("/", path); + + if(super.exists(parentPath, entry.getKey())) { // Is directory - includeSubDirs + files.addAll(createNamespaceFiles(localesPath, locale, new KeyPath(path, entry.getKey()), type, entry.getValue())); + continue; } + + VirtualFile vf = super.constructFile(parentPath, entry.getKey() + "." + type.getFileExtension()); + files.add(new TranslationFile(vf, locale, new KeyPath(path, entry.getKey()))); } return files; 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 fe8ffae..a7707b4 100644 --- a/src/main/java/de/marhali/easyi18n/io/folder/ModularNamespaceFolderStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/folder/ModularNamespaceFolderStrategy.java @@ -3,8 +3,10 @@ package de.marhali.easyi18n.io.folder; import com.intellij.openapi.vfs.VirtualFile; import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.model.KeyPath; 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; @@ -12,6 +14,8 @@ import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Modular translation folder strategy by namespace. @@ -26,17 +30,22 @@ public class ModularNamespaceFolderStrategy extends FolderStrategy { @Override public @NotNull List analyzeFolderStructure(@NotNull VirtualFile localesDirectory) { + return new ArrayList<>(findLocaleFiles(new KeyPath(), localesDirectory)); + } + + private List findLocaleFiles(KeyPath ns, VirtualFile dir) { List files = new ArrayList<>(); - for(VirtualFile namespaceModuleDir : localesDirectory.getChildren()) { - if(namespaceModuleDir.isDirectory()) { - String namespace = namespaceModuleDir.getNameWithoutExtension(); - - for(VirtualFile localeFile : namespaceModuleDir.getChildren()) { - if(super.isFileRelevant(localeFile)) { - files.add(new TranslationFile(localeFile, localeFile.getNameWithoutExtension(), namespace)); - } + for (VirtualFile localeFile : dir.getChildren()) { + if(localeFile.isDirectory()) { + if(settings.isIncludeSubDirs()) { + files.addAll(findLocaleFiles(new KeyPath(ns, localeFile.getName()), localeFile)); } + continue; + } + + if(super.isFileRelevant(localeFile)) { + files.add(new TranslationFile(localeFile, localeFile.getNameWithoutExtension(), ns)); } } @@ -48,14 +57,24 @@ public class ModularNamespaceFolderStrategy extends FolderStrategy { @NotNull String localesPath, @NotNull ParserStrategyType type, @NotNull TranslationData data) throws IOException { + return new ArrayList<>(this.createLocaleFiles( + localesPath, data.getLocales(), new KeyPath(), type, data.getRootNode())); + } + + private List createLocaleFiles(String localesPath, Set locales, KeyPath path, ParserStrategyType type, TranslationNode node) throws IOException { List files = new ArrayList<>(); - for(String namespace : data.getRootNode().getChildren().keySet()) { - for(String locale : data.getLocales()) { - VirtualFile vf = super.constructFile(localesPath + "/" + namespace, - locale + "." + type.getFileExtension()); + for (Map.Entry entry : node.getChildren().entrySet()) { + String parentPath = localesPath + "/" + String.join("/", path); - files.add(new TranslationFile(vf, locale, namespace)); + if(super.exists(parentPath, entry.getKey())) { // Is directory - includeSubDirs + files.addAll(createLocaleFiles(localesPath, locales, new KeyPath(path, entry.getKey()), type, entry.getValue())); + continue; + } + + for (String locale : locales) { + VirtualFile vf = super.constructFile(parentPath, locale + "." + type.getFileExtension()); + files.add(new TranslationFile(vf, locale, path)); } } 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 b94c0b3..5ded988 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/ParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/ParserStrategy.java @@ -46,15 +46,7 @@ public abstract class ParserStrategy { TranslationNode targetNode = data.getRootNode(); if(file.getNamespace() != null) { - String moduleName = file.getNamespace(); - TranslationNode moduleNode = data.getNode(new KeyPath(moduleName)); - - if(moduleNode == null) { - moduleNode = new TranslationNode(this.settings.isSorting()); - data.getRootNode().setChildren(moduleName, moduleNode); - } - - targetNode = moduleNode; + targetNode = data.getOrCreateNoe(file.getNamespace()); } return targetNode; diff --git a/src/main/java/de/marhali/easyi18n/model/TranslationData.java b/src/main/java/de/marhali/easyi18n/model/TranslationData.java index e3239bf..1ab0087 100644 --- a/src/main/java/de/marhali/easyi18n/model/TranslationData.java +++ b/src/main/java/de/marhali/easyi18n/model/TranslationData.java @@ -89,6 +89,20 @@ public class TranslationData { return node; } + public @NotNull TranslationNode getOrCreateNoe(@NotNull KeyPath fullPath) { + TranslationNode node = this.rootNode; + + if(fullPath.isEmpty()) { // Return root node if empty path was supplied + return node; + } + + for(String section : fullPath) { + node = node.getOrCreateChildren(section); + } + + return node; + } + /** * @param fullPath Absolute translation key path * @return Found translation. Can be null if path is empty or is not a leaf element diff --git a/src/main/java/de/marhali/easyi18n/model/TranslationFile.java b/src/main/java/de/marhali/easyi18n/model/TranslationFile.java index 50f8cb4..6432c0d 100644 --- a/src/main/java/de/marhali/easyi18n/model/TranslationFile.java +++ b/src/main/java/de/marhali/easyi18n/model/TranslationFile.java @@ -13,9 +13,9 @@ public class TranslationFile { private final @NotNull VirtualFile virtualFile; private final @NotNull String locale; - private final @Nullable String namespace; + private final @Nullable KeyPath namespace; - public TranslationFile(@NotNull VirtualFile virtualFile, @NotNull String locale, @Nullable String namespace) { + public TranslationFile(@NotNull VirtualFile virtualFile, @NotNull String locale, @Nullable KeyPath namespace) { this.virtualFile = virtualFile; this.locale = locale; this.namespace = namespace; @@ -29,7 +29,7 @@ public class TranslationFile { return locale; } - public @Nullable String getNamespace() { + public @Nullable KeyPath getNamespace() { return namespace; } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 93af07e..03cdc40 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -31,6 +31,8 @@ settings.resource.folder.tooltip=What is the folder structure of your translatio 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.nesting.title=Consider subdirectories for modularized translation files +settings.resource.nesting.tooltip=Validates directories within a module and passes them as a submodule. 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