From 21e71f53d51b2083dc64481a7e274eb591251cdb Mon Sep 17 00:00:00 2001 From: marhali Date: Sat, 28 May 2022 21:46:22 +0200 Subject: [PATCH] indicate translations with duplicated values yellow --- CHANGELOG.md | 1 + README.md | 2 +- .../de/marhali/easyi18n/FilteredDataBus.java | 64 ++------------- .../easyi18n/renderer/TableRenderer.java | 41 ---------- .../de/marhali/easyi18n/tabs/TableView.java | 2 +- .../de/marhali/easyi18n/tabs/TreeView.java | 2 +- .../easyi18n/tabs/mapper/TreeModelMapper.java | 29 ++++--- .../easyi18n/tabs/renderer/TableRenderer.java | 74 +++++++++++++++++ .../{ => tabs}/renderer/TreeRenderer.java | 2 +- .../easyi18n/util/TranslationUtil.java | 79 +++++++++++++++++++ 10 files changed, 181 insertions(+), 115 deletions(-) delete mode 100644 src/main/java/de/marhali/easyi18n/renderer/TableRenderer.java create mode 100644 src/main/java/de/marhali/easyi18n/tabs/renderer/TableRenderer.java rename src/main/java/de/marhali/easyi18n/{ => tabs}/renderer/TreeRenderer.java (95%) create mode 100644 src/main/java/de/marhali/easyi18n/util/TranslationUtil.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6314032..30e2923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## [Unreleased] ### Added - Duplicate translation values filter +- Indicate translations with duplicated values yellow - Multiple translation filters can be used together ### Changed diff --git a/README.md b/README.md index ad5057a..9bc606d 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ _For more examples, please refer to the [Examples Directory](https://github.com/ - [X] Define default namespace to use if none was provided - [X] Enhance editor code assistance - [ ] XML Support -- [ ] Mark duplicate translation values +- [X] Mark duplicate translation values - [ ] Python language assistance See the [open issues](https://github.com/marhali/easy-i18n/issues) for a full list of proposed features (and known issues). diff --git a/src/main/java/de/marhali/easyi18n/FilteredDataBus.java b/src/main/java/de/marhali/easyi18n/FilteredDataBus.java index 1607cb1..1bbc7c7 100644 --- a/src/main/java/de/marhali/easyi18n/FilteredDataBus.java +++ b/src/main/java/de/marhali/easyi18n/FilteredDataBus.java @@ -2,21 +2,17 @@ package de.marhali.easyi18n; import com.intellij.openapi.project.Project; -import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.TranslationData; -import de.marhali.easyi18n.model.TranslationNode; -import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.model.*; import de.marhali.easyi18n.model.bus.BusListener; import de.marhali.easyi18n.model.bus.ExpandAllListener; import de.marhali.easyi18n.model.bus.FilteredBusListener; import de.marhali.easyi18n.settings.ProjectSettingsService; import de.marhali.easyi18n.settings.ProjectSettingsState; -import de.marhali.easyi18n.util.KeyPathConverter; +import de.marhali.easyi18n.util.TranslationUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; @@ -109,21 +105,21 @@ public class FilteredDataBus implements BusListener { // filter incomplete translations if(filterIncomplete) { - if(!isIncomplete(value)) { + if(!TranslationUtil.isIncomplete(value, this.data)) { shadow.setTranslation(key, null); } } // filter duplicate values if(filterDuplicate) { - if(!isDuplicate(key, value)) { + if(!TranslationUtil.hasDuplicates(new Translation(key, value), this.data)) { shadow.setTranslation(key, null); } } // full-text-search if(searchQuery != null) { - if(!isSearched(key, value)) { + if(!TranslationUtil.isSearched(settings, new Translation(key, value), searchQuery)) { shadow.setTranslation(key, null); } } @@ -144,54 +140,4 @@ public class FilteredDataBus implements BusListener { private void fire(@NotNull Consumer li) { listener.forEach(li); } - - /** - * Filter translations with missing translation values for any locale - */ - private boolean isIncomplete(@NotNull TranslationValue value) { - return this.data.getLocales().size() != value.getLocaleContents().size(); - } - - /** - * Filter duplicate translation values - */ - private boolean isDuplicate(@NotNull KeyPath key, @NotNull TranslationValue value) { - Collection contents = value.getLocaleContents(); - - for (KeyPath currentKey : this.data.getFullKeys()) { - TranslationValue currentValue = this.data.getTranslation(currentKey); - assert currentValue != null; - - if(currentKey.equals(key)) { // Only consider other translations - continue; - } - - for (String currentContent : currentValue.getLocaleContents()) { - if(contents.contains(currentContent)) { - return true; - } - } - } - - return false; - } - - /** - * Filter by search query - */ - private boolean isSearched(@NotNull KeyPath key, @NotNull TranslationValue value) { - String concatKey = new KeyPathConverter(settings).toString(key).toLowerCase(); - - if(searchQuery.contains(concatKey) || concatKey.contains(searchQuery)) { - return true; - } - - for (String localeContent : value.getLocaleContents()) { - if(localeContent.toLowerCase().contains(searchQuery)) { - return true; - } - } - - return false; - } } \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/renderer/TableRenderer.java b/src/main/java/de/marhali/easyi18n/renderer/TableRenderer.java deleted file mode 100644 index cebe4c9..0000000 --- a/src/main/java/de/marhali/easyi18n/renderer/TableRenderer.java +++ /dev/null @@ -1,41 +0,0 @@ -package de.marhali.easyi18n.renderer; - -import com.intellij.ui.JBColor; - -import javax.swing.*; -import javax.swing.table.DefaultTableCellRenderer; -import java.awt.*; - -/** - * Similar to {@link DefaultTableCellRenderer} but will mark the first column red if any column is empty. - * @author marhali - */ -public class TableRenderer extends DefaultTableCellRenderer { - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - - if(column == 0 && missesValues(row, table)) { - component.setForeground(JBColor.RED); - } else { // Reset color - component.setForeground(null); - } - - return component; - } - - private boolean missesValues(int row, JTable table) { - int columns = table.getColumnCount(); - - for(int i = 1; i < columns; i++) { - Object value = table.getValueAt(row, i); - - if(value == null || value.toString().isEmpty()) { - return true; - } - } - - return false; - } -} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/tabs/TableView.java b/src/main/java/de/marhali/easyi18n/tabs/TableView.java index 658e65b..7fa16c3 100644 --- a/src/main/java/de/marhali/easyi18n/tabs/TableView.java +++ b/src/main/java/de/marhali/easyi18n/tabs/TableView.java @@ -15,7 +15,7 @@ import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.model.bus.FilteredBusListener; -import de.marhali.easyi18n.renderer.TableRenderer; +import de.marhali.easyi18n.tabs.renderer.TableRenderer; import de.marhali.easyi18n.tabs.mapper.TableModelMapper; import de.marhali.easyi18n.util.KeyPathConverter; diff --git a/src/main/java/de/marhali/easyi18n/tabs/TreeView.java b/src/main/java/de/marhali/easyi18n/tabs/TreeView.java index 5f466e3..d7df294 100644 --- a/src/main/java/de/marhali/easyi18n/tabs/TreeView.java +++ b/src/main/java/de/marhali/easyi18n/tabs/TreeView.java @@ -21,7 +21,7 @@ import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.model.bus.FilteredBusListener; -import de.marhali.easyi18n.renderer.TreeRenderer; +import de.marhali.easyi18n.tabs.renderer.TreeRenderer; import de.marhali.easyi18n.settings.ProjectSettingsService; import de.marhali.easyi18n.tabs.mapper.TreeModelMapper; import de.marhali.easyi18n.util.TreeUtil; 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 caa9580..1baf35d 100644 --- a/src/main/java/de/marhali/easyi18n/tabs/mapper/TreeModelMapper.java +++ b/src/main/java/de/marhali/easyi18n/tabs/mapper/TreeModelMapper.java @@ -3,10 +3,12 @@ package de.marhali.easyi18n.tabs.mapper; import com.intellij.ide.projectView.PresentationData; import com.intellij.ui.JBColor; +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.settings.ProjectSettings; +import de.marhali.easyi18n.util.TranslationUtil; import de.marhali.easyi18n.util.UiUtil; import org.jetbrains.annotations.NotNull; @@ -33,31 +35,33 @@ public class TreeModelMapper extends DefaultTreeModel { this.state = state; DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); - this.generateNodes(rootNode, this.data.getRootNode()); + this.generateNodes(rootNode, new KeyPath(), this.data.getRootNode()); super.setRoot(rootNode); } /** * @param parent Parent tree node * @param translationNode Layer of translation node to write to tree - * @return true if children nodes misses any translation values + * @return color to apply on the parent node */ - private boolean generateNodes(@NotNull DefaultMutableTreeNode parent, @NotNull TranslationNode translationNode) { - boolean foundMissing = false; + private JBColor generateNodes(@NotNull DefaultMutableTreeNode parent, @NotNull KeyPath parentPath, @NotNull TranslationNode translationNode) { + JBColor color = null; for(Map.Entry entry : translationNode.getChildren().entrySet()) { String key = entry.getKey(); + KeyPath keyPath = new KeyPath(parentPath, key); TranslationNode childTranslationNode = entry.getValue(); - if(!childTranslationNode.isLeaf()) { - // Nested node - run recursively + if(!childTranslationNode.isLeaf()) { // Nested node - run recursively DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(key); - if(this.generateNodes(childNode, childTranslationNode)) { // Mark red if any children misses translations + JBColor childColor = this.generateNodes(childNode, keyPath, childTranslationNode); + + if(childColor != null) { PresentationData data = new PresentationData(key, null, null, null); - data.setForcedTextForeground(JBColor.RED); + data.setForcedTextForeground(childColor); childNode.setUserObject(data); - foundMissing = true; + color = childColor; } parent.add(childNode); @@ -72,14 +76,17 @@ public class TreeModelMapper extends DefaultTreeModel { if(childTranslationNode.getValue().size() != this.data.getLocales().size()) { data.setForcedTextForeground(JBColor.RED); - foundMissing = true; + color = JBColor.RED; + } else if(TranslationUtil.hasDuplicates(new Translation(keyPath, childTranslationNode.getValue()), this.data)) { + data.setForcedTextForeground(JBColor.YELLOW); + color = JBColor.YELLOW; } parent.add(new DefaultMutableTreeNode(data)); } } - return foundMissing; + return color; } /** diff --git a/src/main/java/de/marhali/easyi18n/tabs/renderer/TableRenderer.java b/src/main/java/de/marhali/easyi18n/tabs/renderer/TableRenderer.java new file mode 100644 index 0000000..a9e2e7c --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/tabs/renderer/TableRenderer.java @@ -0,0 +1,74 @@ +package de.marhali.easyi18n.tabs.renderer; + +import com.intellij.ui.JBColor; + +import javax.swing.*; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.*; +import java.util.HashSet; +import java.util.Set; + +/** + * Similar to {@link DefaultTableCellRenderer} but will mark the first column red if any column is empty. + * @author marhali + */ +public class TableRenderer extends DefaultTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + // Always reset color + component.setForeground(null); + + if(column != 0) { + return component; + } + + if(missesValues(row, table)) { + component.setForeground(JBColor.RED); + } else if(hasDuplicates(row, table)) { + component.setForeground(JBColor.YELLOW); + } + + return component; + } + + private boolean missesValues(int row, JTable table) { + int columns = table.getColumnCount(); + + for(int i = 1; i < columns; i++) { + Object value = table.getValueAt(row, i); + + if(value == null || value.toString().isEmpty()) { + return true; + } + } + + return false; + } + + private boolean hasDuplicates(int checkRow, JTable table) { + int columns = table.getColumnCount(); + int rows = table.getRowCount(); + + Set contents = new HashSet<>(); + for(int column = 1; column < columns; column++) { + contents.add(String.valueOf(table.getValueAt(checkRow, column))); + } + + for(int row = 1; row < rows; row++) { + if(row == checkRow) { + continue; + } + + for(int column = 1; column < columns; column++) { + if(contents.contains(String.valueOf(table.getValueAt(row, column)))) { + return true; + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/renderer/TreeRenderer.java b/src/main/java/de/marhali/easyi18n/tabs/renderer/TreeRenderer.java similarity index 95% rename from src/main/java/de/marhali/easyi18n/renderer/TreeRenderer.java rename to src/main/java/de/marhali/easyi18n/tabs/renderer/TreeRenderer.java index f643dd7..522d69e 100644 --- a/src/main/java/de/marhali/easyi18n/renderer/TreeRenderer.java +++ b/src/main/java/de/marhali/easyi18n/tabs/renderer/TreeRenderer.java @@ -1,4 +1,4 @@ -package de.marhali.easyi18n.renderer; +package de.marhali.easyi18n.tabs.renderer; import com.intellij.ide.util.treeView.NodeRenderer; import com.intellij.navigation.ItemPresentation; diff --git a/src/main/java/de/marhali/easyi18n/util/TranslationUtil.java b/src/main/java/de/marhali/easyi18n/util/TranslationUtil.java new file mode 100644 index 0000000..f4f1b17 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/util/TranslationUtil.java @@ -0,0 +1,79 @@ +package de.marhali.easyi18n.util; + +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.ProjectSettingsState; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Objects; + +/** + * Utilities for translations + * @author marhali + */ +public class TranslationUtil { + /** + * Check whether a given translation has duplicated values. + * @param translation The translation to check + * @param data Translation data cache + * @return true if duplicates were found otherwise false + */ + public static boolean hasDuplicates(@NotNull Translation translation, @NotNull TranslationData data) { + assert translation.getValue() != null; + Collection contents = translation.getValue().getLocaleContents(); + + for (KeyPath key : data.getFullKeys()) { + if(translation.getKey().equals(key)) { // Only consider other translations + continue; + } + + for (String localeContent : Objects.requireNonNull(data.getTranslation(key)).getLocaleContents()) { + if(contents.contains(localeContent)) { + return true; + } + } + } + + return false; + } + + /** + * Check whether a given translation has missing locale values. + * @param value The translation to check + * @param data Translation data cache + * @return true if missing values were found otherwise false + */ + public static boolean isIncomplete(@NotNull TranslationValue value, @NotNull TranslationData data) { + return value.getLocaleContents().size() != data.getLocales().size(); + } + + /** + * Check whether a given translation falls under a specified search query. + * @param settings Project specific settings + * @param translation The translation to check + * @param searchQuery Full-text search term + * @return true if translations is applicable otherwise false + */ + public static boolean isSearched(@NotNull ProjectSettingsState settings, + @NotNull Translation translation, @NotNull String searchQuery) { + + String concatKey = new KeyPathConverter(settings).toString(translation.getKey()).toLowerCase(); + + if(searchQuery.contains(concatKey) || concatKey.contains(searchQuery)) { + return true; + } + + assert translation.getValue() != null; + for (String localeContent : translation.getValue().getLocaleContents()) { + if(localeContent.toLowerCase().contains(searchQuery)) { + return true; + } + } + + return false; + } +} \ No newline at end of file