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
- 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

View File

@ -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;
}
}

View File

@ -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));
}
}
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)) {
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<>();
for(String locale : data.getLocales()) {
for(String namespace : data.getRootNode().getChildren().keySet()) {
VirtualFile vf = super.constructFile(localesPath + "/" + locale,
namespace + "." + type.getFileExtension());
files.add(new TranslationFile(vf, locale, namespace));
files.addAll(this.createNamespaceFiles(localesPath, locale, new KeyPath(), type, data.getRootNode()));
}
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;

View File

@ -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<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<>();
for(VirtualFile namespaceModuleDir : localesDirectory.getChildren()) {
if(namespaceModuleDir.isDirectory()) {
String namespace = namespaceModuleDir.getNameWithoutExtension();
for (VirtualFile localeFile : dir.getChildren()) {
if(localeFile.isDirectory()) {
if(settings.isIncludeSubDirs()) {
files.addAll(findLocaleFiles(new KeyPath(ns, localeFile.getName()), localeFile));
}
continue;
}
for(VirtualFile localeFile : namespaceModuleDir.getChildren()) {
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 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<>();
for(String namespace : data.getRootNode().getChildren().keySet()) {
for(String locale : data.getLocales()) {
VirtualFile vf = super.constructFile(localesPath + "/" + namespace,
locale + "." + type.getFileExtension());
for (Map.Entry<String, TranslationNode> 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));
}
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}

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.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