commit
b6d8251db3
12
CHANGELOG.md
12
CHANGELOG.md
@ -3,6 +3,18 @@
|
|||||||
# easy-i18n Changelog
|
# easy-i18n Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Scroll to created / edited translation inside Tree-/Table-View
|
||||||
|
- Support for working with multiple projects at once
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies
|
||||||
|
- Load translations even if ui tool window is not opened
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- NullPointerException's on translation annotation / completion inside editor
|
||||||
|
- Always synchronize ui with loaded state by reloadFromDisk function
|
||||||
|
|
||||||
## [1.2.0]
|
## [1.2.0]
|
||||||
### Added
|
### Added
|
||||||
- Sorting for properties files
|
- Sorting for properties files
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
pluginGroup = de.marhali.easyi18n
|
pluginGroup = de.marhali.easyi18n
|
||||||
pluginName = easy-i18n
|
pluginName = easy-i18n
|
||||||
pluginVersion = 1.2.0
|
pluginVersion = 1.3.0
|
||||||
pluginSinceBuild = 202
|
pluginSinceBuild = 202
|
||||||
pluginUntilBuild = 211.*
|
pluginUntilBuild = 211.*
|
||||||
# Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl
|
# Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl
|
||||||
|
@ -54,11 +54,10 @@ public class TranslatorToolWindowFactory implements ToolWindowFactory {
|
|||||||
// Initialize Window Manager
|
// Initialize Window Manager
|
||||||
WindowManager.getInstance().initialize(toolWindow, treeView, tableView);
|
WindowManager.getInstance().initialize(toolWindow, treeView, tableView);
|
||||||
|
|
||||||
// Initialize data store and load from disk
|
// Synchronize ui with underlying data
|
||||||
DataStore store = DataStore.getInstance(project);
|
DataStore store = DataStore.getInstance(project);
|
||||||
store.addSynchronizer(treeView);
|
store.addSynchronizer(treeView);
|
||||||
store.addSynchronizer(tableView);
|
store.addSynchronizer(tableView);
|
||||||
|
store.synchronize(null, null);
|
||||||
store.reloadFromDisk();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,7 +12,8 @@ public interface DataSynchronizer {
|
|||||||
/**
|
/**
|
||||||
* Propagates data changes to implementation classes.
|
* Propagates data changes to implementation classes.
|
||||||
* @param translations Updated translations model
|
* @param translations Updated translations model
|
||||||
* @param searchQuery Can be used to filter visible data. Like a search function for the full key path.
|
* @param searchQuery Can be used to filter visible data. Like a search function for the full key path
|
||||||
|
* @param scrollToKey Focus specific translation. Can be null to disable this function
|
||||||
*/
|
*/
|
||||||
void synchronize(@NotNull Translations translations, @Nullable String searchQuery);
|
void synchronize(@NotNull Translations translations, @Nullable String searchQuery, @Nullable String scrollToKey);
|
||||||
}
|
}
|
@ -14,6 +14,10 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class Translations {
|
public class Translations {
|
||||||
|
|
||||||
|
public static Translations empty() {
|
||||||
|
return new Translations(new ArrayList<>(), new LocalizedNode(LocalizedNode.ROOT_KEY, new ArrayList<>()));
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private final List<String> locales;
|
private final List<String> locales;
|
||||||
|
|
||||||
@ -34,7 +38,7 @@ public class Translations {
|
|||||||
return locales;
|
return locales;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalizedNode getNodes() {
|
public @NotNull LocalizedNode getNodes() {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,4 +91,42 @@ public class TreeModelTranslator extends DefaultTreeModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TreePath findTreePath(@NotNull String fullPath) {
|
||||||
|
List<String> sections = TranslationsUtil.getSections(fullPath);
|
||||||
|
Object[] nodes = new Object[sections.size() + 1];
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
TreeNode currentNode = (TreeNode) this.getRoot();
|
||||||
|
nodes[pos] = currentNode;
|
||||||
|
|
||||||
|
for(String section : sections) {
|
||||||
|
pos++;
|
||||||
|
currentNode = findNode(currentNode, section);
|
||||||
|
nodes[pos] = currentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TreePath(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable DefaultMutableTreeNode findNode(@NotNull TreeNode parent, @NotNull String key) {
|
||||||
|
for(int i = 0; i < parent.getChildCount(); i++) {
|
||||||
|
TreeNode child = parent.getChildAt(i);
|
||||||
|
|
||||||
|
if(child instanceof DefaultMutableTreeNode) {
|
||||||
|
DefaultMutableTreeNode mutableChild = (DefaultMutableTreeNode) child;
|
||||||
|
String childKey = mutableChild.getUserObject().toString();
|
||||||
|
|
||||||
|
if(mutableChild.getUserObject() instanceof PresentationData) {
|
||||||
|
childKey = ((PresentationData) mutableChild.getUserObject()).getPresentableText();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(childKey != null && childKey.equals(key)) {
|
||||||
|
return mutableChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NullPointerException("Cannot find node by key: " + key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package de.marhali.easyi18n.service;
|
package de.marhali.easyi18n.service;
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.application.ModalityState;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
|
|
||||||
import de.marhali.easyi18n.model.LocalizedNode;
|
import de.marhali.easyi18n.model.LocalizedNode;
|
||||||
@ -17,15 +19,17 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton service to manage localized messages.
|
* Factory service to manage localized messages for multiple projects at once.
|
||||||
* @author marhali
|
* @author marhali
|
||||||
*/
|
*/
|
||||||
public class DataStore {
|
public class DataStore {
|
||||||
|
|
||||||
private static DataStore INSTANCE;
|
private static final Map<Project, DataStore> INSTANCES = new WeakHashMap<>();
|
||||||
|
|
||||||
private final Project project;
|
private final Project project;
|
||||||
private final List<DataSynchronizer> synchronizer;
|
private final List<DataSynchronizer> synchronizer;
|
||||||
@ -33,13 +37,24 @@ public class DataStore {
|
|||||||
private Translations translations;
|
private Translations translations;
|
||||||
private String searchQuery;
|
private String searchQuery;
|
||||||
|
|
||||||
public static DataStore getInstance(Project project) {
|
public static DataStore getInstance(@NotNull Project project) {
|
||||||
return INSTANCE == null ? INSTANCE = new DataStore(project) : INSTANCE;
|
DataStore store = INSTANCES.get(project);
|
||||||
|
|
||||||
|
if(store == null) {
|
||||||
|
store = new DataStore(project);
|
||||||
|
INSTANCES.put(project, store);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataStore(Project project) {
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataStore(@NotNull Project project) {
|
||||||
this.project = project;
|
this.project = project;
|
||||||
this.synchronizer = new ArrayList<>();
|
this.synchronizer = new ArrayList<>();
|
||||||
|
this.translations = Translations.empty();
|
||||||
|
|
||||||
|
// Load data after first initialization
|
||||||
|
ApplicationManager.getApplication().invokeLater(this::reloadFromDisk, ModalityState.NON_MODAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,24 +72,16 @@ public class DataStore {
|
|||||||
String localesPath = SettingsService.getInstance(project).getState().getLocalesPath();
|
String localesPath = SettingsService.getInstance(project).getState().getLocalesPath();
|
||||||
|
|
||||||
if(localesPath == null || localesPath.isEmpty()) {
|
if(localesPath == null || localesPath.isEmpty()) {
|
||||||
translations = new Translations(new ArrayList<>(),
|
// Propagate empty state
|
||||||
new LocalizedNode(LocalizedNode.ROOT_KEY, new ArrayList<>()));
|
this.translations = Translations.empty();
|
||||||
|
synchronize(searchQuery, null);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
TranslatorIO io = IOUtil.determineFormat(localesPath);
|
TranslatorIO io = IOUtil.determineFormat(localesPath);
|
||||||
|
|
||||||
io.read(project, localesPath, (translations) -> {
|
io.read(project, localesPath, (loadedTranslations) -> {
|
||||||
if(translations != null) { // Read was successful
|
this.translations = loadedTranslations == null ? Translations.empty() : loadedTranslations;
|
||||||
this.translations = translations;
|
synchronize(searchQuery, null);
|
||||||
|
|
||||||
// Propagate changes
|
|
||||||
synchronizer.forEach(synchronizer -> synchronizer.synchronize(translations, searchQuery));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// If state cannot be loaded from disk, show empty instance
|
|
||||||
this.translations = new Translations(new ArrayList<>(),
|
|
||||||
new LocalizedNode(LocalizedNode.ROOT_KEY, new ArrayList<>()));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +107,7 @@ public class DataStore {
|
|||||||
*/
|
*/
|
||||||
public void searchBeyKey(@Nullable String fullPath) {
|
public void searchBeyKey(@Nullable String fullPath) {
|
||||||
// Use synchronizer to propagate search instance to all views
|
// Use synchronizer to propagate search instance to all views
|
||||||
synchronizer.forEach(synchronizer -> synchronizer.synchronize(translations, this.searchQuery = fullPath));
|
synchronize(this.searchQuery = fullPath, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,6 +140,8 @@ public class DataStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String scrollTo = update.isDeletion() ? null : update.getChange().getKey();
|
||||||
|
|
||||||
if(!update.isDeletion()) { // Recreate with changed val / create
|
if(!update.isDeletion()) { // Recreate with changed val / create
|
||||||
LocalizedNode node = translations.getOrCreateNode(update.getChange().getKey());
|
LocalizedNode node = translations.getOrCreateNode(update.getChange().getKey());
|
||||||
node.setValue(update.getChange().getTranslations());
|
node.setValue(update.getChange().getTranslations());
|
||||||
@ -141,7 +150,7 @@ public class DataStore {
|
|||||||
// Persist changes and propagate them on success
|
// Persist changes and propagate them on success
|
||||||
saveToDisk(success -> {
|
saveToDisk(success -> {
|
||||||
if(success) {
|
if(success) {
|
||||||
synchronizer.forEach(synchronizer -> synchronizer.synchronize(translations, searchQuery));
|
synchronize(searchQuery, scrollTo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -149,7 +158,16 @@ public class DataStore {
|
|||||||
/**
|
/**
|
||||||
* @return Current translation state
|
* @return Current translation state
|
||||||
*/
|
*/
|
||||||
public Translations getTranslations() {
|
public @NotNull Translations getTranslations() {
|
||||||
return translations;
|
return translations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes current translation's state to all connected subscribers.
|
||||||
|
* @param searchQuery Optional search by full key filter (ui view)
|
||||||
|
* @param scrollTo Optional scroll to full key (ui view)
|
||||||
|
*/
|
||||||
|
public void synchronize(@Nullable String searchQuery, @Nullable String scrollTo) {
|
||||||
|
synchronizer.forEach(subscriber -> subscriber.synchronize(this.translations, searchQuery, scrollTo));
|
||||||
|
}
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ public class I18nCompletionProvider extends CompletionProvider<CompletionParamet
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
|
protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
|
||||||
Project project = parameters.getEditor().getProject();
|
Project project = parameters.getOriginalFile().getProject();
|
||||||
String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
|
String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
|
||||||
|
|
||||||
String query = result.getPrefixMatcher().getPrefix();
|
String query = result.getPrefixMatcher().getPrefix();
|
||||||
@ -40,6 +40,10 @@ public class I18nCompletionProvider extends CompletionProvider<CompletionParamet
|
|||||||
LocalizedNode node = sections.isEmpty() ? DataStore.getInstance(project).getTranslations().getNodes()
|
LocalizedNode node = sections.isEmpty() ? DataStore.getInstance(project).getTranslations().getNodes()
|
||||||
: DataStore.getInstance(project).getTranslations().getNode(path);
|
: DataStore.getInstance(project).getTranslations().getNode(path);
|
||||||
|
|
||||||
|
if(node == null) { // Unknown translation
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for(LocalizedNode children : node.getChildren()) {
|
for(LocalizedNode children : node.getChildren()) {
|
||||||
if(lastSection == null || children.getKey().startsWith(lastSection)) {
|
if(lastSection == null || children.getKey().startsWith(lastSection)) {
|
||||||
// Construct full key path / Fore nested objects add '.' to indicate deeper level
|
// Construct full key path / Fore nested objects add '.' to indicate deeper level
|
||||||
|
@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@ -73,9 +74,26 @@ public class TableView implements DataSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void synchronize(@NotNull Translations translations, @Nullable String searchQuery) {
|
public void synchronize(@NotNull Translations translations,
|
||||||
|
@Nullable String searchQuery, @Nullable String scrollTo) {
|
||||||
|
|
||||||
table.setModel(new TableModelTranslator(translations, searchQuery, update ->
|
table.setModel(new TableModelTranslator(translations, searchQuery, update ->
|
||||||
DataStore.getInstance(project).processUpdate(update)));
|
DataStore.getInstance(project).processUpdate(update)));
|
||||||
|
|
||||||
|
if(scrollTo != null) {
|
||||||
|
int row = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < table.getRowCount(); i++) {
|
||||||
|
if (String.valueOf(table.getValueAt(i, 0)).equals(scrollTo)) {
|
||||||
|
row = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row > -1) { // Matched @scrollTo
|
||||||
|
table.scrollRectToVisible(
|
||||||
|
new Rectangle(0, (row * table.getRowHeight()) + table.getHeight(), 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public JPanel getRootPanel() {
|
public JPanel getRootPanel() {
|
||||||
|
@ -20,6 +20,7 @@ import de.marhali.easyi18n.ui.dialog.EditDialog;
|
|||||||
import de.marhali.easyi18n.ui.listener.DeleteKeyListener;
|
import de.marhali.easyi18n.ui.listener.DeleteKeyListener;
|
||||||
import de.marhali.easyi18n.ui.listener.PopupClickListener;
|
import de.marhali.easyi18n.ui.listener.PopupClickListener;
|
||||||
import de.marhali.easyi18n.ui.renderer.TreeRenderer;
|
import de.marhali.easyi18n.ui.renderer.TreeRenderer;
|
||||||
|
import de.marhali.easyi18n.util.TranslationsUtil;
|
||||||
import de.marhali.easyi18n.util.TreeUtil;
|
import de.marhali.easyi18n.util.TreeUtil;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -75,12 +76,19 @@ public class TreeView implements DataSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void synchronize(@NotNull Translations translations, @Nullable String searchQuery) {
|
public void synchronize(@NotNull Translations translations,
|
||||||
tree.setModel(new TreeModelTranslator(project, translations, searchQuery));
|
@Nullable String searchQuery, @Nullable String scrollTo) {
|
||||||
|
|
||||||
|
TreeModelTranslator model = new TreeModelTranslator(project, translations, searchQuery);
|
||||||
|
tree.setModel(model);
|
||||||
|
|
||||||
if(searchQuery != null && !searchQuery.isEmpty()) {
|
if(searchQuery != null && !searchQuery.isEmpty()) {
|
||||||
expandAll().run();
|
expandAll().run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(scrollTo != null) {
|
||||||
|
tree.scrollPathToVisible(model.findTreePath(scrollTo));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePopup(MouseEvent e) {
|
private void handlePopup(MouseEvent e) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user