re-engineer translation filters

Resolves #77
Resolves #101
Resolves #122
Resolves #127
This commit is contained in:
marhali 2022-05-28 14:25:57 +02:00
parent 8ce09a6a5b
commit e8236b272b
14 changed files with 342 additions and 204 deletions

View File

@ -3,26 +3,36 @@
# easy-i18n Changelog # easy-i18n Changelog
## [Unreleased] ## [Unreleased]
### Added
- Duplicate translation values filter
- Multiple translation filters can be used together
### Changed
- Reengineered how translation filters are applied internally
### Fixed
- Exception during batch delete
- Translation filters keep their status across updates
## [4.0.0] ## [4.0.0]
### BREAKING CHANGES ### BREAKING CHANGES
- Configuration rework. Existing settings will be lost and must be configured via the new configuration page - Configuration rework. Existing settings will be lost and must be configured via the new configuration page
### Added ### Added
- Key delimiters (namespace / section) can be configured - Key delimiters (namespace / section) can be configured
- Extract translation intention - Extract translation intention
- Full language support for Java, Kotlin, JavaScript / TypeScript, Vue and PHP - Full language support for Java, Kotlin, JavaScript / TypeScript, Vue and PHP
- Expand already expanded nodes after data update - Expand already expanded nodes after data update
- Experimental option to force translation key folding - Experimental option to force translation key folding
- Individual icon for tool-window and lookup items - Individual icon for tool-window and lookup items
- Dedicated configuration file (easy-i18n.xml) inside <kbd>.idea</kbd> folder - Dedicated configuration file (easy-i18n.xml) inside <kbd>.idea</kbd> folder
### Changed ### Changed
- Editor assistance has been reengineered. This will affect key suggestion and annotation - Editor assistance has been reengineered. This will affect key suggestion and annotation
- Moved configuration dialog into own page inside <kbd>IDE Settings</kbd> - Moved configuration dialog into own page inside <kbd>IDE Settings</kbd>
### Fixed ### Fixed
- AlreadyDisposedException on FileChangeListener after project dispose - AlreadyDisposedException on FileChangeListener after project dispose
- Request-URL limit for error reports - Request-URL limit for error reports
## [3.2.0] ## [3.2.0]

View File

@ -2,8 +2,8 @@ package de.marhali.easyi18n;
import de.marhali.easyi18n.model.bus.BusListener; import de.marhali.easyi18n.model.bus.BusListener;
import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.KeyPath;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -11,7 +11,8 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
/** /**
* Data-bus which is used to distribute changes regarding translations or ui tools to the participating components. * Eventbus which is mandatory to distribute changes to the participating components.
* For user interface related components the {@link FilteredDataBus} has a builtin solution to apply all relevant filters.
* @author marhali * @author marhali
*/ */
public class DataBus { public class DataBus {
@ -38,8 +39,13 @@ public class DataBus {
public BusListener propagate() { public BusListener propagate() {
return new BusListener() { return new BusListener() {
@Override @Override
public void onUpdateData(@NotNull TranslationData data) { public void onFilterDuplicate(boolean filter) {
listener.forEach(li -> li.onUpdateData(data)); listener.forEach(li -> li.onFilterDuplicate(filter));
}
@Override
public void onFilterIncomplete(boolean filter) {
listener.forEach(li -> li.onFilterIncomplete(filter));
} }
@Override @Override
@ -53,8 +59,8 @@ public class DataBus {
} }
@Override @Override
public void onFilterMissingTranslations(boolean filter) { public void onUpdateData(@NotNull TranslationData data) {
listener.forEach(li -> li.onFilterMissingTranslations(filter)); listener.forEach(li -> li.onUpdateData(data));
} }
}; };
} }

View File

@ -0,0 +1,193 @@
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.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 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;
/**
* UI related eventbus. Uses the {@link BusListener} by {@link DataBus} under the hood.
* User-Interface components (e.g. tabs) use this component by implementing {@link FilteredBusListener}.
* @author marhali
*/
public class FilteredDataBus implements BusListener {
private final Project project;
private final Set<FilteredBusListener> listener;
private TranslationData data;
private ProjectSettingsState settings;
private boolean filterDuplicate;
private boolean filterIncomplete;
private String searchQuery;
private KeyPath focusKey;
/**
* Constructs a new project specific UI eventbus.
* @param project Associated project
*/
public FilteredDataBus(@NotNull Project project) {
this.project = project;
this.listener = new HashSet<>();
}
/**
* Adds a participant to the event bus. Every participant needs to be added manually.
* @param listener Bus listener
*/
public void addListener(FilteredBusListener listener) {
this.listener.add(listener);
}
@Override
public void onFilterDuplicate(boolean filter) {
this.filterDuplicate = filter;
this.processAndPropagate();
fire(ExpandAllListener::onExpandAll);
}
@Override
public void onFilterIncomplete(boolean filter) {
this.filterIncomplete = filter;
this.processAndPropagate();
fire(ExpandAllListener::onExpandAll);
}
@Override
public void onFocusKey(@NotNull KeyPath key) {
this.focusKey = key;
fire(li -> li.onFocusKey(key));
}
@Override
public void onSearchQuery(@Nullable String query) {
this.searchQuery = query == null ? null : query.toLowerCase();
this.processAndPropagate();
fire(ExpandAllListener::onExpandAll);
}
@Override
public void onUpdateData(@NotNull TranslationData data) {
this.data = data;
this.settings = ProjectSettingsService.get(this.project).getState();
processAndPropagate();
}
/**
* Filter translations based on supplied filters and propagate changes
* to all registered participants.
* Internally creates a shallow copy of the cached translations and
* removes any that does not apply with the configured filters.
*/
private void processAndPropagate() {
TranslationData shadow = new TranslationData(
this.data.getLocales(), new TranslationNode(this.data.isSorting()));
for (KeyPath key : this.data.getFullKeys()) {
TranslationValue value = this.data.getTranslation(key);
assert value != null;
// We create a shallow copy of the current translation instance
// and remove every translation that does not conform with the configured filter/s
shadow.setTranslation(key, value);
// filter incomplete translations
if(filterIncomplete) {
if(!isIncomplete(value)) {
shadow.setTranslation(key, null);
}
}
// filter duplicate values
if(filterDuplicate) {
if(!isDuplicate(value)) {
shadow.setTranslation(key, null);
}
}
// full-text-search
if(searchQuery != null) {
if(!isSearched(key, value)) {
shadow.setTranslation(key, null);
}
}
}
fire(li -> {
li.onUpdateData(shadow);
if(focusKey != null) {
li.onFocusKey(focusKey);
}
});
}
/**
* @param li Notify all registered participants about an fired event.
*/
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 TranslationValue value) {
Collection<String> contents = value.getLocaleContents();
for (KeyPath currentKey : this.data.getFullKeys()) {
TranslationValue currentValue = this.data.getTranslation(currentKey);
assert currentValue != null;
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;
}
}

View File

@ -20,6 +20,7 @@ public class InstanceManager {
private final DataStore store; private final DataStore store;
private final DataBus bus; private final DataBus bus;
private final FilteredDataBus uiBus;
public static InstanceManager get(@NotNull Project project) { public static InstanceManager get(@NotNull Project project) {
InstanceManager instance = INSTANCES.get(project); InstanceManager instance = INSTANCES.get(project);
@ -35,6 +36,10 @@ public class InstanceManager {
private InstanceManager(@NotNull Project project) { private InstanceManager(@NotNull Project project) {
this.store = new DataStore(project); this.store = new DataStore(project);
this.bus = new DataBus(); this.bus = new DataBus();
this.uiBus = new FilteredDataBus(project);
// Register ui eventbus on top of the normal eventbus
this.bus.addListener(this.uiBus);
// Load data after first initialization // Load data after first initialization
ApplicationManager.getApplication().invokeLater(() -> { ApplicationManager.getApplication().invokeLater(() -> {
@ -48,10 +53,20 @@ public class InstanceManager {
return this.store; return this.store;
} }
/**
* Primary eventbus.
*/
public DataBus bus() { public DataBus bus() {
return this.bus; return this.bus;
} }
/**
* UI optimized eventbus with builtin filter logic.
*/
public FilteredDataBus uiBus() {
return this.uiBus;
}
/** /**
* Reloads the plugin instance. Unsaved cached data will be deleted. * Reloads the plugin instance. Unsaved cached data will be deleted.
* Fetches data from persistence layer and notifies all endpoints via {@link DataBus}. * Fetches data from persistence layer and notifies all endpoints via {@link DataBus}.

View File

@ -0,0 +1,30 @@
package de.marhali.easyi18n.action;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
import de.marhali.easyi18n.InstanceManager;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.ResourceBundle;
/**
* Action to toggle duplicate translation values filter.
* @author marhali
*/
public class FilterDuplicateAction extends AnAction {
// TODO: Custom icon to differentiate between incomplete and duplicate filter
public FilterDuplicateAction() {
super(ResourceBundle.getBundle("messages").getString("action.filter.duplicate"),
null, AllIcons.General.ShowWarning);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = Objects.requireNonNull(e.getProject());
boolean enable = e.getPresentation().getIcon() == AllIcons.General.ShowWarning;
e.getPresentation().setIcon(enable ? AllIcons.General.Warning : AllIcons.General.ShowWarning);
InstanceManager.get(project).bus().propagate().onFilterDuplicate(enable);
}
}

View File

@ -14,9 +14,10 @@ import java.util.ResourceBundle;
* Action which toggles translation filter on missing values. * Action which toggles translation filter on missing values.
* @author marhali * @author marhali
*/ */
public class FilterMissingTranslationsAction extends AnAction { public class FilterIncompleteAction extends AnAction {
public FilterMissingTranslationsAction() { // TODO: Custom icon to differentiate between incomplete and duplicate filter
super(ResourceBundle.getBundle("messages").getString("action.toggle-missing"), public FilterIncompleteAction() {
super(ResourceBundle.getBundle("messages").getString("action.filter.incomplete"),
null, AllIcons.General.ShowWarning); null, AllIcons.General.ShowWarning);
} }
@ -25,6 +26,6 @@ public class FilterMissingTranslationsAction extends AnAction {
Project project = Objects.requireNonNull(e.getProject()); Project project = Objects.requireNonNull(e.getProject());
boolean enable = e.getPresentation().getIcon() == AllIcons.General.ShowWarning; boolean enable = e.getPresentation().getIcon() == AllIcons.General.ShowWarning;
e.getPresentation().setIcon(enable ? AllIcons.General.Warning : AllIcons.General.ShowWarning); e.getPresentation().setIcon(enable ? AllIcons.General.Warning : AllIcons.General.ShowWarning);
InstanceManager.get(project).bus().propagate().onFilterMissingTranslations(enable); InstanceManager.get(project).bus().propagate().onFilterIncomplete(enable);
} }
} }

View File

@ -0,0 +1,13 @@
package de.marhali.easyi18n.model.bus;
/**
* Single event listener.
* @see #onExpandAll()
* @author marhali
*/
public interface ExpandAllListener {
/**
* Action to expand all nodes
*/
void onExpandAll();
}

View File

@ -0,0 +1,9 @@
package de.marhali.easyi18n.model.bus;
import de.marhali.easyi18n.FilteredDataBus;
/**
* Interface to replicate the state of {@link FilteredDataBus} to underlying components.
* @author marhali
*/
public interface FilteredBusListener extends UpdateDataListener, FocusKeyListener, ExpandAllListener {}

View File

@ -27,6 +27,7 @@ public class TranslatorToolWindowFactory implements ToolWindowFactory, DumbAware
@Override @Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
InstanceManager manager = InstanceManager.get(project);
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
// Translations tree view // Translations tree view
@ -46,19 +47,19 @@ public class TranslatorToolWindowFactory implements ToolWindowFactory, DumbAware
// ToolWindow Actions (Can be used for every view) // ToolWindow Actions (Can be used for every view)
List<AnAction> actions = new ArrayList<>(); List<AnAction> actions = new ArrayList<>();
actions.add(new AddAction()); actions.add(new AddAction());
actions.add(new FilterMissingTranslationsAction()); actions.add(new FilterIncompleteAction());
actions.add(new FilterDuplicateAction());
actions.add(new ReloadAction()); actions.add(new ReloadAction());
actions.add(new SettingsAction()); actions.add(new SettingsAction());
actions.add(new SearchAction((query) -> InstanceManager.get(project).bus().propagate().onSearchQuery(query))); actions.add(new SearchAction((query) -> manager.bus().propagate().onSearchQuery(query)));
toolWindow.setTitleActions(actions); toolWindow.setTitleActions(actions);
// Initialize Window Manager // Initialize Window Manager
WindowManager.getInstance().initialize(toolWindow, treeView, tableView); WindowManager.getInstance().initialize(toolWindow, treeView, tableView);
// Synchronize ui with underlying data // Synchronize ui with underlying data
InstanceManager manager = InstanceManager.get(project); manager.uiBus().addListener(treeView);
manager.bus().addListener(treeView); manager.uiBus().addListener(tableView);
manager.bus().addListener(tableView);
manager.bus().propagate().onUpdateData(manager.store().getData()); manager.bus().propagate().onUpdateData(manager.store().getData());
} }
} }

View File

@ -11,33 +11,30 @@ import de.marhali.easyi18n.listener.DeleteKeyListener;
import de.marhali.easyi18n.listener.PopupClickListener; import de.marhali.easyi18n.listener.PopupClickListener;
import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.action.TranslationDelete; import de.marhali.easyi18n.model.action.TranslationDelete;
import de.marhali.easyi18n.model.bus.BusListener;
import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.Translation;
import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.model.TranslationValue;
import de.marhali.easyi18n.model.bus.FilteredBusListener;
import de.marhali.easyi18n.renderer.TableRenderer; import de.marhali.easyi18n.renderer.TableRenderer;
import de.marhali.easyi18n.tabs.mapper.TableModelMapper; import de.marhali.easyi18n.tabs.mapper.TableModelMapper;
import de.marhali.easyi18n.util.KeyPathConverter; import de.marhali.easyi18n.util.KeyPathConverter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.util.ResourceBundle; import java.util.*;
/** /**
* Shows translation state as table. * Shows translation state as table.
*
* @author marhali * @author marhali
*/ */
public class TableView implements BusListener { public class TableView implements FilteredBusListener {
private final JBTable table; private final JBTable table;
private final Project project; private final Project project;
private TableModelMapper currentMapper;
private KeyPathConverter converter; private KeyPathConverter converter;
private JPanel rootPanel; private JPanel rootPanel;
@ -70,12 +67,14 @@ public class TableView implements BusListener {
} }
private void deleteSelectedRows() { private void deleteSelectedRows() {
for (int selectedRow : table.getSelectedRows()) { Set<KeyPath> batchDelete = new HashSet<>();
KeyPath fullPath = this.converter.fromString(String.valueOf(table.getValueAt(selectedRow, 0)));
InstanceManager.get(project).processUpdate( for (int selectedRow : table.getSelectedRows()) {
new TranslationDelete(new Translation(fullPath, null)) batchDelete.add(this.converter.fromString(String.valueOf(table.getValueAt(selectedRow, 0))));
); }
for (KeyPath key : batchDelete) {
InstanceManager.get(project).processUpdate(new TranslationDelete(new Translation(key, null)));
} }
} }
@ -83,7 +82,7 @@ public class TableView implements BusListener {
public void onUpdateData(@NotNull TranslationData data) { public void onUpdateData(@NotNull TranslationData data) {
this.converter = new KeyPathConverter(project); this.converter = new KeyPathConverter(project);
table.setModel(this.currentMapper = new TableModelMapper(data, this.converter, update -> table.setModel(new TableModelMapper(data, this.converter, update ->
InstanceManager.get(project).processUpdate(update))); InstanceManager.get(project).processUpdate(update)));
} }
@ -105,19 +104,8 @@ public class TableView implements BusListener {
} }
@Override @Override
public void onSearchQuery(@Nullable String query) { public void onExpandAll() {
if (this.currentMapper != null) { // table-view never collapse any rows
this.currentMapper.onSearchQuery(query);
this.table.updateUI();
}
}
@Override
public void onFilterMissingTranslations(boolean filter) {
if(this.currentMapper != null) {
this.currentMapper.onFilterMissingTranslations(filter);
this.table.updateUI();
}
} }
public JPanel getRootPanel() { public JPanel getRootPanel() {

View File

@ -13,7 +13,6 @@ import de.marhali.easyi18n.dialog.EditDialog;
import de.marhali.easyi18n.listener.ReturnKeyListener; import de.marhali.easyi18n.listener.ReturnKeyListener;
import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.action.TranslationDelete; import de.marhali.easyi18n.model.action.TranslationDelete;
import de.marhali.easyi18n.model.bus.BusListener;
import de.marhali.easyi18n.action.treeview.CollapseTreeViewAction; import de.marhali.easyi18n.action.treeview.CollapseTreeViewAction;
import de.marhali.easyi18n.action.treeview.ExpandTreeViewAction; import de.marhali.easyi18n.action.treeview.ExpandTreeViewAction;
import de.marhali.easyi18n.listener.DeleteKeyListener; import de.marhali.easyi18n.listener.DeleteKeyListener;
@ -21,6 +20,7 @@ import de.marhali.easyi18n.listener.PopupClickListener;
import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.Translation;
import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.model.TranslationValue;
import de.marhali.easyi18n.model.bus.FilteredBusListener;
import de.marhali.easyi18n.renderer.TreeRenderer; import de.marhali.easyi18n.renderer.TreeRenderer;
import de.marhali.easyi18n.settings.ProjectSettingsService; import de.marhali.easyi18n.settings.ProjectSettingsService;
import de.marhali.easyi18n.tabs.mapper.TreeModelMapper; import de.marhali.easyi18n.tabs.mapper.TreeModelMapper;
@ -32,16 +32,13 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.ResourceBundle;
/** /**
* Show translation state as tree. * Show translation state as tree.
*
* @author marhali * @author marhali
*/ */
public class TreeView implements BusListener { public class TreeView implements FilteredBusListener {
private final Tree tree; private final Tree tree;
@ -71,8 +68,8 @@ public class TreeView implements BusListener {
private void placeActions() { private void placeActions() {
DefaultActionGroup group = new DefaultActionGroup("TranslationsGroup", false); DefaultActionGroup group = new DefaultActionGroup("TranslationsGroup", false);
ExpandTreeViewAction expand = new ExpandTreeViewAction(this::expandAll); ExpandTreeViewAction expand = new ExpandTreeViewAction(this::onExpandAll);
CollapseTreeViewAction collapse = new CollapseTreeViewAction(this::collapseAll); CollapseTreeViewAction collapse = new CollapseTreeViewAction(this::onCollapseAll);
group.add(collapse); group.add(collapse);
group.add(expand); group.add(expand);
@ -117,24 +114,6 @@ public class TreeView implements BusListener {
} }
} }
@Override
public void onSearchQuery(@Nullable String query) {
if (this.currentMapper != null) {
this.currentMapper.onSearchQuery(query);
this.expandAll();
this.tree.updateUI();
}
}
@Override
public void onFilterMissingTranslations(boolean filter) {
if (this.currentMapper != null) {
this.currentMapper.onFilterMissingTranslations(filter);
this.expandAll();
this.tree.updateUI();
}
}
private void showEditPopup(@Nullable TreePath path) { private void showEditPopup(@Nullable TreePath path) {
if (path == null) { if (path == null) {
return; return;
@ -157,28 +136,30 @@ public class TreeView implements BusListener {
} }
private void deleteSelectedNodes() { private void deleteSelectedNodes() {
TreePath[] paths = tree.getSelectionPaths(); TreePath[] selection = tree.getSelectionPaths();
Set<KeyPath> batchDelete = new HashSet<>();
if (paths == null) { if(selection == null) {
return; return;
} }
for (TreePath path : tree.getSelectionPaths()) { for (TreePath path : selection) {
KeyPath fullPath = TreeUtil.getFullPath(path); batchDelete.add(TreeUtil.getFullPath(path));
}
InstanceManager.get(project).processUpdate( for (KeyPath key : batchDelete) {
new TranslationDelete(new Translation(fullPath, null)) InstanceManager.get(project).processUpdate(new TranslationDelete(new Translation(key, null)));
);
} }
} }
private void expandAll() { @Override
public void onExpandAll() {
for (int i = 0; i < tree.getRowCount(); i++) { for (int i = 0; i < tree.getRowCount(); i++) {
tree.expandRow(i); tree.expandRow(i);
} }
} }
private void collapseAll() { public void onCollapseAll() {
for (int i = 0; i < tree.getRowCount(); i++) { for (int i = 0; i < tree.getRowCount(); i++) {
tree.collapseRow(i); tree.collapseRow(i);
} }

View File

@ -2,8 +2,6 @@ package de.marhali.easyi18n.tabs.mapper;
import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.action.TranslationUpdate; import de.marhali.easyi18n.model.action.TranslationUpdate;
import de.marhali.easyi18n.model.bus.FilterIncompleteListener;
import de.marhali.easyi18n.model.bus.SearchQueryListener;
import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.model.Translation; import de.marhali.easyi18n.model.Translation;
import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.model.TranslationValue;
@ -11,7 +9,6 @@ import de.marhali.easyi18n.util.KeyPathConverter;
import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.event.TableModelListener; import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
@ -23,13 +20,13 @@ import java.util.function.Consumer;
* Mapping {@link TranslationData} to {@link TableModel}. * Mapping {@link TranslationData} to {@link TableModel}.
* @author marhali * @author marhali
*/ */
public class TableModelMapper implements TableModel, SearchQueryListener, FilterIncompleteListener { public class TableModelMapper implements TableModel {
private final @NotNull TranslationData data; private final @NotNull TranslationData data;
private final @NotNull KeyPathConverter converter; private final @NotNull KeyPathConverter converter;
private final @NotNull List<String> locales; private final @NotNull List<String> locales;
private @NotNull List<KeyPath> fullKeys; private final @NotNull List<KeyPath> fullKeys;
private final @NotNull Consumer<TranslationUpdate> updater; private final @NotNull Consumer<TranslationUpdate> updater;
@ -44,49 +41,6 @@ public class TableModelMapper implements TableModel, SearchQueryListener, Filter
this.updater = updater; this.updater = updater;
} }
@Override
public void onSearchQuery(@Nullable String query) {
if(query == null) { // Reset
this.fullKeys = new ArrayList<>(this.data.getFullKeys());
return;
}
query = query.toLowerCase();
List<KeyPath> matches = new ArrayList<>();
for(KeyPath key : this.data.getFullKeys()) {
if(this.converter.toString(key).toLowerCase().contains(query)) {
matches.add(key);
} else {
for(String content : this.data.getTranslation(key).getLocaleContents()) {
if(content.toLowerCase().contains(query)) {
matches.add(key);
}
}
}
}
this.fullKeys = matches;
}
@Override
public void onFilterMissingTranslations(boolean filter) {
if(!filter) { // Reset
this.fullKeys = new ArrayList<>(this.data.getFullKeys());
return;
}
List<KeyPath> matches = new ArrayList<>();
for(KeyPath key : this.data.getFullKeys()) {
if(this.data.getTranslation(key).getLocaleContents().size() != this.locales.size()) {
matches.add(key);
}
}
this.fullKeys = matches;
}
@Override @Override
public int getRowCount() { public int getRowCount() {
return this.fullKeys.size(); return this.fullKeys.size();

View File

@ -5,12 +5,8 @@ import com.intellij.ui.JBColor;
import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationData;
import de.marhali.easyi18n.model.TranslationNode; import de.marhali.easyi18n.model.TranslationNode;
import de.marhali.easyi18n.model.bus.FilterIncompleteListener;
import de.marhali.easyi18n.model.bus.SearchQueryListener;
import de.marhali.easyi18n.model.KeyPath; import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.model.TranslationValue;
import de.marhali.easyi18n.settings.ProjectSettings; import de.marhali.easyi18n.settings.ProjectSettings;
import de.marhali.easyi18n.util.KeyPathConverter;
import de.marhali.easyi18n.util.UiUtil; import de.marhali.easyi18n.util.UiUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -25,17 +21,15 @@ import java.util.Map;
* Mapping {@link TranslationData} to {@link TreeModel}. * Mapping {@link TranslationData} to {@link TreeModel}.
* @author marhali * @author marhali
*/ */
public class TreeModelMapper extends DefaultTreeModel implements SearchQueryListener, FilterIncompleteListener { public class TreeModelMapper extends DefaultTreeModel {
private final TranslationData data; private final TranslationData data;
private final KeyPathConverter converter;
private final ProjectSettings state; private final ProjectSettings state;
public TreeModelMapper(TranslationData data, ProjectSettings state) { public TreeModelMapper(TranslationData data, ProjectSettings state) {
super(null); super(null);
this.data = data; this.data = data;
this.converter = new KeyPathConverter(state);
this.state = state; this.state = state;
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
@ -43,65 +37,7 @@ public class TreeModelMapper extends DefaultTreeModel implements SearchQueryList
super.setRoot(rootNode); super.setRoot(rootNode);
} }
@Override
public void onSearchQuery(@Nullable String query) {
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
TranslationData shadow = new TranslationData(this.state.isSorting());
if(query == null) { // Reset
this.generateNodes(rootNode, this.data.getRootNode());
super.setRoot(rootNode);
return;
}
query = query.toLowerCase();
for(KeyPath currentKey : this.data.getFullKeys()) {
TranslationValue translation = this.data.getTranslation(currentKey);
String loweredKey = this.converter.toString(currentKey).toLowerCase();
if(query.contains(loweredKey) || loweredKey.contains(query)) {
shadow.setTranslation(currentKey, translation);
continue;
}
for(String currentContent : translation.getLocaleContents()) {
if(currentContent.toLowerCase().contains(query)) {
shadow.setTranslation(currentKey, translation);
break;
}
}
}
this.generateNodes(rootNode, shadow.getRootNode());
super.setRoot(rootNode);
}
@Override
public void onFilterMissingTranslations(boolean filter) {
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
TranslationData shadow = new TranslationData(this.state.isSorting());
if(!filter) { // Reset
this.generateNodes(rootNode, this.data.getRootNode());
super.setRoot(rootNode);
return;
}
for(KeyPath currentKey : this.data.getFullKeys()) {
TranslationValue translation = this.data.getTranslation(currentKey);
if(translation.getLocaleContents().size() != this.data.getLocales().size()) {
shadow.setTranslation(currentKey, translation);
}
}
this.generateNodes(rootNode, shadow.getRootNode());
super.setRoot(rootNode);
}
/** /**
*
* @param parent Parent tree node * @param parent Parent tree node
* @param translationNode Layer of translation node to write to tree * @param translationNode Layer of translation node to write to tree
* @return true if children nodes misses any translation values * @return true if children nodes misses any translation values

View File

@ -6,7 +6,8 @@ view.table.title=Table View
view.empty=No translations found. Click on settings to specify a locales directory or reload view.empty=No translations found. Click on settings to specify a locales directory or reload
action.add=Add Translation action.add=Add Translation
action.edit=Edit Translation action.edit=Edit Translation
action.toggle-missing=Toggle missing translations action.filter.incomplete=Filter Incomplete Translations
action.filter.duplicate=Filter Duplicate Translation Values
action.reload=Reload From Disk action.reload=Reload From Disk
action.settings=Settings action.settings=Settings
action.search=Search... action.search=Search...