indicate translations with duplicated values yellow
This commit is contained in:
parent
d4ef478d48
commit
21e71f53d5
@ -5,6 +5,7 @@
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Duplicate translation values filter
|
||||
- Indicate translations with duplicated values yellow
|
||||
- Multiple translation filters can be used together
|
||||
|
||||
### Changed
|
||||
|
@ -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).
|
||||
|
@ -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<FilteredBusListener> 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<String> 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<String, TranslationNode> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<String> 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;
|
||||
}
|
||||
}
|
@ -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;
|
79
src/main/java/de/marhali/easyi18n/util/TranslationUtil.java
Normal file
79
src/main/java/de/marhali/easyi18n/util/TranslationUtil.java
Normal file
@ -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<String> 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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user