consider subdirectories for modularized translation files

Resolves #124
This commit is contained in:
marhali 2022-05-29 15:35:34 +02:00
parent 8dc66e1250
commit c03d97f3e8
8 changed files with 105 additions and 34 deletions

View File

@ -7,6 +7,7 @@
- Duplicate translation values filter - Duplicate translation values filter
- Indicate translations with duplicated values yellow - Indicate translations with duplicated values yellow
- Multiple translation filters can be used together - Multiple translation filters can be used together
- Option to consider subdirectories for modularized translation files
### Changed ### Changed
- Reengineered how translation filters are applied internally - Reengineered how translation filters are applied internally

View File

@ -6,8 +6,8 @@ import com.intellij.openapi.vfs.VirtualFile;
import de.marhali.easyi18n.io.parser.ParserStrategyType; import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.TranslationFile; import de.marhali.easyi18n.model.TranslationFile;
import de.marhali.easyi18n.settings.ProjectSettings; import de.marhali.easyi18n.settings.ProjectSettings;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -71,4 +71,14 @@ public abstract class FolderStrategy {
return Objects.requireNonNull(vf); 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;
}
} }

View File

@ -3,8 +3,10 @@ package de.marhali.easyi18n.io.folder;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import de.marhali.easyi18n.io.parser.ParserStrategyType; import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.TranslationFile; import de.marhali.easyi18n.model.TranslationFile;
import de.marhali.easyi18n.model.TranslationNode;
import de.marhali.easyi18n.settings.ProjectSettings; import de.marhali.easyi18n.settings.ProjectSettings;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -12,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Modularized translation folder strategy by locale. * Modularized translation folder strategy by locale.
@ -31,12 +34,26 @@ public class ModularLocaleFolderStrategy extends FolderStrategy {
for(VirtualFile localeModuleDir : localesDirectory.getChildren()) { for(VirtualFile localeModuleDir : localesDirectory.getChildren()) {
if(localeModuleDir.isDirectory()) { if(localeModuleDir.isDirectory()) {
String locale = localeModuleDir.getNameWithoutExtension(); String locale = localeModuleDir.getNameWithoutExtension();
files.addAll(findNamespaceFiles(locale, new KeyPath(), localeModuleDir));
}
}
return files;
}
private List<TranslationFile> findNamespaceFiles(@NotNull String locale, @NotNull KeyPath ns, @NotNull VirtualFile dir) {
List<TranslationFile> 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;
}
for(VirtualFile namespaceFile : localeModuleDir.getChildren()) {
if(super.isFileRelevant(namespaceFile)) { if(super.isFileRelevant(namespaceFile)) {
files.add(new TranslationFile(namespaceFile, locale, namespaceFile.getNameWithoutExtension())); files.add(new TranslationFile(namespaceFile, locale, new KeyPath(ns, namespaceFile.getNameWithoutExtension())));
}
}
} }
} }
@ -51,12 +68,28 @@ public class ModularLocaleFolderStrategy extends FolderStrategy {
List<TranslationFile> files = new ArrayList<>(); List<TranslationFile> files = new ArrayList<>();
for(String locale : data.getLocales()) { for(String locale : data.getLocales()) {
for(String namespace : data.getRootNode().getChildren().keySet()) { files.addAll(this.createNamespaceFiles(localesPath, locale, new KeyPath(), type, data.getRootNode()));
VirtualFile vf = super.constructFile(localesPath + "/" + locale,
namespace + "." + type.getFileExtension());
files.add(new TranslationFile(vf, locale, namespace));
} }
return files;
}
private List<TranslationFile> createNamespaceFiles(
String localesPath, String locale, KeyPath path,
ParserStrategyType type, TranslationNode node) throws IOException {
List<TranslationFile> files = new ArrayList<>();
for(Map.Entry<String, TranslationNode> 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; return files;

View File

@ -3,8 +3,10 @@ package de.marhali.easyi18n.io.folder;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import de.marhali.easyi18n.io.parser.ParserStrategyType; import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.TranslationFile; import de.marhali.easyi18n.model.TranslationFile;
import de.marhali.easyi18n.model.TranslationNode;
import de.marhali.easyi18n.settings.ProjectSettings; import de.marhali.easyi18n.settings.ProjectSettings;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -12,6 +14,8 @@ import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* Modular translation folder strategy by namespace. * Modular translation folder strategy by namespace.
@ -26,17 +30,22 @@ public class ModularNamespaceFolderStrategy extends FolderStrategy {
@Override @Override
public @NotNull List<TranslationFile> analyzeFolderStructure(@NotNull VirtualFile localesDirectory) { public @NotNull List<TranslationFile> analyzeFolderStructure(@NotNull VirtualFile localesDirectory) {
return new ArrayList<>(findLocaleFiles(new KeyPath(), localesDirectory));
}
private List<TranslationFile> findLocaleFiles(KeyPath ns, VirtualFile dir) {
List<TranslationFile> files = new ArrayList<>(); List<TranslationFile> files = new ArrayList<>();
for(VirtualFile namespaceModuleDir : localesDirectory.getChildren()) { for (VirtualFile localeFile : dir.getChildren()) {
if(namespaceModuleDir.isDirectory()) { if(localeFile.isDirectory()) {
String namespace = namespaceModuleDir.getNameWithoutExtension(); if(settings.isIncludeSubDirs()) {
files.addAll(findLocaleFiles(new KeyPath(ns, localeFile.getName()), localeFile));
}
continue;
}
for(VirtualFile localeFile : namespaceModuleDir.getChildren()) {
if(super.isFileRelevant(localeFile)) { if(super.isFileRelevant(localeFile)) {
files.add(new TranslationFile(localeFile, localeFile.getNameWithoutExtension(), namespace)); files.add(new TranslationFile(localeFile, localeFile.getNameWithoutExtension(), ns));
}
}
} }
} }
@ -48,14 +57,24 @@ public class ModularNamespaceFolderStrategy extends FolderStrategy {
@NotNull String localesPath, @NotNull ParserStrategyType type, @NotNull String localesPath, @NotNull ParserStrategyType type,
@NotNull TranslationData data) throws IOException { @NotNull TranslationData data) throws IOException {
return new ArrayList<>(this.createLocaleFiles(
localesPath, data.getLocales(), new KeyPath(), type, data.getRootNode()));
}
private List<TranslationFile> createLocaleFiles(String localesPath, Set<String> locales, KeyPath path, ParserStrategyType type, TranslationNode node) throws IOException {
List<TranslationFile> files = new ArrayList<>(); List<TranslationFile> files = new ArrayList<>();
for(String namespace : data.getRootNode().getChildren().keySet()) { for (Map.Entry<String, TranslationNode> entry : node.getChildren().entrySet()) {
for(String locale : data.getLocales()) { String parentPath = localesPath + "/" + String.join("/", path);
VirtualFile vf = super.constructFile(localesPath + "/" + namespace,
locale + "." + type.getFileExtension());
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));
} }
} }

View File

@ -46,15 +46,7 @@ public abstract class ParserStrategy {
TranslationNode targetNode = data.getRootNode(); TranslationNode targetNode = data.getRootNode();
if(file.getNamespace() != null) { if(file.getNamespace() != null) {
String moduleName = file.getNamespace(); targetNode = data.getOrCreateNoe(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;
} }
return targetNode; return targetNode;

View File

@ -89,6 +89,20 @@ public class TranslationData {
return node; 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 * @param fullPath Absolute translation key path
* @return Found translation. Can be null if path is empty or is not a leaf element * @return Found translation. Can be null if path is empty or is not a leaf element

View File

@ -13,9 +13,9 @@ public class TranslationFile {
private final @NotNull VirtualFile virtualFile; private final @NotNull VirtualFile virtualFile;
private final @NotNull String locale; 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.virtualFile = virtualFile;
this.locale = locale; this.locale = locale;
this.namespace = namespace; this.namespace = namespace;
@ -29,7 +29,7 @@ public class TranslationFile {
return locale; return locale;
} }
public @Nullable String getNamespace() { public @Nullable KeyPath getNamespace() {
return namespace; return namespace;
} }

View File

@ -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.items=JSON;JSON5;YAML;YML;Properties;ARB
settings.resource.parser.tooltip=Which file parser should be used to process your translation files? 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.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.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. settings.resource.sorting.tooltip=Sorts all translation keys alphabetically. If disabled, the original key-order in the files is kept.
# Editor Configuration # Editor Configuration