diff --git a/.inno.iss b/.inno.iss index 85e069d..57ec05f 100644 --- a/.inno.iss +++ b/.inno.iss @@ -1,5 +1,5 @@ #define AppName "i18n-editor" -#define AppVersion "1.0.0" +#define AppVersion "1.0.0-beta.1" #define AppPublisher "JvMs Software" #define AppURL "https://github.com/jcbvm/i18n-editor" #define AppExeName "i18n-editor.exe" diff --git a/pom.xml b/pom.xml index 3c053ad..0bd1d18 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.jvms i18n-editor - 1.0.0 + 1.0.0-beta.1 jar diff --git a/src/main/java/com/jvms/i18neditor/Main.java b/src/main/java/com/jvms/i18neditor/Main.java index 7e8efe9..ff65272 100644 --- a/src/main/java/com/jvms/i18neditor/Main.java +++ b/src/main/java/com/jvms/i18neditor/Main.java @@ -9,6 +9,10 @@ import com.jvms.i18neditor.editor.Editor; +/** + * + * @author Jacob + */ public class Main { public static void main(String[] args) throws IOException { diff --git a/src/main/java/com/jvms/i18neditor/Resource.java b/src/main/java/com/jvms/i18neditor/Resource.java index 1baa6b2..df2dc81 100644 --- a/src/main/java/com/jvms/i18neditor/Resource.java +++ b/src/main/java/com/jvms/i18neditor/Resource.java @@ -30,22 +30,15 @@ public class Resource { private final Path path; private final Locale locale; - private final SortedMap translations; - private final List listeners = Lists.newLinkedList(); private final ResourceType type; + private final List listeners = Lists.newLinkedList(); + private SortedMap translations = Maps.newTreeMap(); /** - * An enum for defining the type of a resource. - */ - public enum ResourceType { - JSON, ES6 - } - - /** - * See {@link #Resource(ResourceType, Path, Locale, SortedMap)}. + * See {@link #Resource(ResourceType, Path, Locale)}. */ - public Resource(ResourceType type, Path path, Locale locale) { - this(type, path, locale, Maps.newTreeMap()); + public Resource(ResourceType type, Path path) { + this(type, path, null); } /** @@ -54,11 +47,9 @@ public Resource(ResourceType type, Path path, Locale locale) { * @param type the type of the resource. * @param path the path to the file on disk. * @param locale the locale of the translations. - * @param translations the actual translation data. */ - public Resource(ResourceType type, Path path, Locale locale, SortedMap translations) { + public Resource(ResourceType type, Path path, Locale locale) { this.path = path; - this.translations = translations; this.locale = locale; this.type = type; } @@ -84,7 +75,7 @@ public Path getPath() { /** * Gets the locale of the translations of the resource. * - * @return the locale of the resource. + * @return the locale of the resource, may be {@code null}. */ public Locale getLocale() { return locale; @@ -103,6 +94,10 @@ public SortedMap getTranslations() { return ImmutableSortedMap.copyOf(translations); } + public void setTranslations(SortedMap translations) { + this.translations = translations; + } + /** * Gets a translation from the resource's translations. * diff --git a/src/main/java/com/jvms/i18neditor/ResourceType.java b/src/main/java/com/jvms/i18neditor/ResourceType.java new file mode 100644 index 0000000..6e6fad4 --- /dev/null +++ b/src/main/java/com/jvms/i18neditor/ResourceType.java @@ -0,0 +1,38 @@ +package com.jvms.i18neditor; + +/** + * An enum describing the type of a {@link Resource}. + * + *

A resource type additionally holds information about the filename representation.

+ */ +public enum ResourceType { + JSON(".json", false), + ES6(".js", false), + Properties(".properties", true); + + private final String extension; + private final boolean embedLocale; + + /** + * Gets the file extension of the resource type. + * + * @return the file extension. + */ + public String getExtension() { + return extension; + } + + /** + * Whether the locale should be embedded in the filename for this resource type. + * + * @return whether the locale should be embedded in the filename. + */ + public boolean isEmbedLocale() { + return embedLocale; + } + + private ResourceType(String extension, boolean embedLocale) { + this.extension = extension; + this.embedLocale = embedLocale; + } +} \ No newline at end of file diff --git a/src/main/java/com/jvms/i18neditor/editor/Editor.java b/src/main/java/com/jvms/i18neditor/editor/Editor.java index 52c911c..69d91cf 100644 --- a/src/main/java/com/jvms/i18neditor/editor/Editor.java +++ b/src/main/java/com/jvms/i18neditor/editor/Editor.java @@ -10,11 +10,12 @@ import java.awt.event.WindowEvent; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -37,21 +38,22 @@ import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; +import org.apache.commons.lang3.LocaleUtils; + +import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.jvms.i18neditor.Resource; -import com.jvms.i18neditor.Resource.ResourceType; -import com.jvms.i18neditor.editor.tree.TranslationTreeModel; -import com.jvms.i18neditor.editor.tree.TranslationTreeNode; +import com.jvms.i18neditor.ResourceType; import com.jvms.i18neditor.swing.JFileDrop; import com.jvms.i18neditor.swing.JScrollablePanel; import com.jvms.i18neditor.swing.util.Dialogs; import com.jvms.i18neditor.util.ExtendedProperties; import com.jvms.i18neditor.util.GithubRepoUtil; -import com.jvms.i18neditor.util.GithubRepoUtil.GithubReleaseData; +import com.jvms.i18neditor.util.GithubRepoUtil.GithubRepoReleaseData; import com.jvms.i18neditor.util.MessageBundle; -import com.jvms.i18neditor.util.ResourceFiles; import com.jvms.i18neditor.util.ResourceKeys; +import com.jvms.i18neditor.util.Resources; /** * This class represents the main class of the editor. @@ -61,19 +63,20 @@ public class Editor extends JFrame { private final static long serialVersionUID = 1113029729495390082L; - public final static Path SETTINGS_PATH = Paths.get(System.getProperty("user.home"), ".i18n-editor"); public final static String TITLE = "i18n-editor"; - public final static String VERSION = "1.0.0"; + public final static String VERSION = "1.0.0-beta.1"; public final static String GITHUB_REPO = "jcbvm/i18n-editor"; - public final static int DEFAULT_WIDTH = 1024; - public final static int DEFAULT_HEIGHT = 768; - - private List resources = Lists.newLinkedList(); - private Path resourcesDir; + public final static String DEFAULT_RESOURCE_NAME = "translations"; + public final static String PROJECT_FILE = ".i18n-editor-metadata"; + public final static String SETTINGS_FILE = ".i18n-editor"; + public final static String SETTINGS_DIR = System.getProperty("user.home"); + + private EditorProject project; + private EditorSettings settings = new EditorSettings(); + private ExecutorService executor = Executors.newCachedThreadPool(); private boolean dirty; - private boolean minifyOutput; - private EditorMenu editorMenu; + private EditorMenuBar editorMenu; private JSplitPane contentPane; private JLabel introText; private JPanel translationsPanel; @@ -82,8 +85,6 @@ public class Editor extends JFrame { private TranslationField translationField; private JPanel resourcesPanel; private List resourceFields = Lists.newLinkedList(); - private ExtendedProperties settings = new ExtendedProperties(); - private ExecutorService executor = Executors.newFixedThreadPool(1); public Editor() { super(); @@ -91,56 +92,103 @@ public Editor() { setupFileDrop(); } - public void importResources(Path dir) { - if (!closeCurrentSession()) { - return; - } - if (Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS)) { - if (resourcesDir != null) { - reset(); + public void createProject(Path dir, ResourceType type) { + try { + Preconditions.checkArgument(Files.isDirectory(dir)); + + if (project != null) { + if (!closeCurrentProject()) { + return; + } + reset(); } - resourcesDir = dir; - } else { - showError(MessageBundle.get("resources.open.error.multiple")); - return; + + project = new EditorProject(dir, type); + + if (type == ResourceType.Properties) { + Resource resource = Resources.create(dir, type, Optional.empty(), project.getResourceName()); + setupResource(resource); + project.addResource(resource); + } + translationTree.setModel(new TranslationTreeModel(Lists.newLinkedList())); + + updateHistory(); + updateUI(); + } catch (IOException e) { + showError(MessageBundle.get("resources.import.error.single")); } + } + + public void importProject(Path dir, boolean showEmptyProjectError) { try { - Files.walk(resourcesDir, 1).filter(path -> ResourceFiles.isResource(path)).forEach(path -> { - try { - Resource resource = ResourceFiles.read(path); - setupResource(resource); - } catch (IOException e) { - showError(MessageBundle.get("resources.open.error.single", path.toString())); + Preconditions.checkArgument(Files.isDirectory(dir)); + + if (project != null) { + if (!closeCurrentProject()) { + return; } - }); + reset(); + } + + project = new EditorProject(dir); + restoreProjectState(project); - Map keys = Maps.newTreeMap(); - resources.forEach(resource -> keys.putAll(resource.getTranslations())); - List keyList = Lists.newArrayList(keys.keySet()); + Optional type = Optional.ofNullable(project.getResourceType()); + List resourceList = Resources.get(dir, project.getResourceName(), type); + List keyList = Lists.newLinkedList(); + + if (resourceList.isEmpty()) { + project = null; + if (showEmptyProjectError) { + executor.execute(() -> showError(MessageBundle.get("resources.import.empty", dir))); + } + } else { + project.setResourceType(type.orElseGet(() -> { + ResourceType t = resourceList.get(0).getType(); + resourceList.removeIf(r -> r.getType() != t); + return t; + })); + resourceList.forEach(resource -> { + try { + Resources.load(resource); + setupResource(resource); + project.addResource(resource); + } catch (IOException e) { + showError(MessageBundle.get("resources.import.error.single", resource.getPath().toString())); + } + }); + Map keys = Maps.newTreeMap(); + project.getResources().forEach(resource -> keys.putAll(resource.getTranslations())); + keyList.addAll(keys.keySet()); + } translationTree.setModel(new TranslationTreeModel(keyList)); updateHistory(); updateUI(); } catch (IOException e) { - showError(MessageBundle.get("resources.open.error.multiple")); + showError(MessageBundle.get("resources.import.error.multiple")); } } - public void saveResources() { + public void saveProject() { boolean error = false; - for (Resource resource : resources) { - try { - ResourceFiles.write(resource, !minifyOutput); - } catch (IOException e) { - error = true; - showError(MessageBundle.get("resources.write.error.single", resource.getPath().toString())); + if (project != null) { + for (Resource resource : project.getResources()) { + try { + Resources.write(resource, !project.isMinifyResources()); + } catch (IOException e) { + error = true; + showError(MessageBundle.get("resources.write.error.single", resource.getPath().toString())); + } } } setDirty(error); } - public void reloadResources() { - importResources(resourcesDir); + public void reloadProject() { + if (project != null) { + importProject(project.getPath(), true); + } } public void removeSelectedTranslation() { @@ -167,36 +215,44 @@ public void duplicateSelectedTranslation() { } public void addTranslationKey(String key) { - if (resources.isEmpty()) return; TranslationTreeNode node = translationTree.getNodeByKey(key); if (node != null) { translationTree.setSelectedNode(node); } else { - resources.forEach(resource -> resource.storeTranslation(key, "")); translationTree.addNodeByKey(key); + if (project != null) { + project.getResources().forEach(resource -> resource.storeTranslation(key, "")); + } } } public void removeTranslationKey(String key) { - if (resources.isEmpty()) return; - resources.forEach(resource -> resource.removeTranslation(key)); translationTree.removeNodeByKey(key); + if (project != null) { + project.getResources().forEach(resource -> resource.removeTranslation(key)); + } } public void renameTranslationKey(String key, String newKey) { - if (resources.isEmpty() || key.equals(newKey)) return; - resources.forEach(resource -> resource.renameTranslation(key, newKey)); translationTree.renameNodeByKey(key, newKey); + if (project != null) { + project.getResources().forEach(resource -> resource.renameTranslation(key, newKey)); + } } public void duplicateTranslationKey(String key, String newKey) { - if (resources.isEmpty() || key.equals(newKey)) return; - resources.forEach(resource -> resource.duplicateTranslation(key, newKey)); translationTree.duplicateNodeByKey(key, newKey); + if (project != null) { + project.getResources().forEach(resource -> resource.duplicateTranslation(key, newKey)); + } } - public Path getResourcesPath() { - return resourcesDir; + public EditorProject getProject() { + return project; + } + + public EditorSettings getSettings() { + return settings; } public boolean isDirty() { @@ -209,50 +265,59 @@ public void setDirty(boolean dirty) { editorMenu.setSaveable(dirty); } - public boolean isMinifyOutput() { - return minifyOutput; + public void clearHistory() { + settings.setHistory(Lists.newArrayList()); + editorMenu.setRecentItems(Lists.newArrayList()); } - public void setMinifyOutput(boolean minifyOutput) { - this.minifyOutput = minifyOutput; + public void showCreateProjectDialog(ResourceType type) { + JFileChooser fc = new JFileChooser(); + fc.setDialogTitle(MessageBundle.get("dialogs.project.new.title")); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int result = fc.showOpenDialog(this); + if (result == JFileChooser.APPROVE_OPTION) { + createProject(Paths.get(fc.getSelectedFile().getPath()), type); + } else { + updateHistory(); + updateUI(); + } } - public void showImportDialog() { + public void showImportProjectDialog() { String path = null; - if (resourcesDir != null) { - path = resourcesDir.toString(); + if (project != null) { + path = project.getPath().toString(); } JFileChooser fc = new JFileChooser(path); - fc.setDialogTitle(MessageBundle.get("dialogs.import.title")); + fc.setDialogTitle(MessageBundle.get("dialogs.project.import.title")); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int result = fc.showOpenDialog(this); if (result == JFileChooser.APPROVE_OPTION) { - importResources(Paths.get(fc.getSelectedFile().getPath())); - } else { - updateHistory(); - updateUI(); + importProject(Paths.get(fc.getSelectedFile().getPath()), true); } } - public void showAddLocaleDialog(ResourceType type) { - String locale = ""; - while (locale != null && locale.isEmpty()) { - locale = Dialogs.showInputDialog(this, + public void showAddLocaleDialog() { + String localeString = ""; + Path path = project.getPath(); + ResourceType type = project.getResourceType(); + while (localeString != null && localeString.isEmpty()) { + localeString = Dialogs.showInputDialog(this, MessageBundle.get("dialogs.locale.add.title", type), MessageBundle.get("dialogs.locale.add.text"), JOptionPane.QUESTION_MESSAGE); - if (locale != null) { - locale = locale.trim(); - Path path = Paths.get(resourcesDir.toString(), locale); - if (locale.isEmpty() || Files.isDirectory(path)) { + if (localeString != null) { + localeString = localeString.trim(); + if (localeString.isEmpty()) { showError(MessageBundle.get("dialogs.locale.add.error.invalid")); } else { try { - Resource resource = ResourceFiles.create(type, path); + Locale locale = LocaleUtils.toLocale(localeString); + Resource resource = Resources.create(path, type, Optional.of(locale), project.getResourceName()); setupResource(resource); + project.addResource(resource); updateUI(); } catch (IOException e) { - e.printStackTrace(); showError(MessageBundle.get("dialogs.locale.add.error.create")); } } @@ -277,7 +342,8 @@ public void showRenameTranslationDialog(String key) { boolean isReplace = newNode.isLeaf() || oldNode.isLeaf(); boolean confirm = Dialogs.showConfirmDialog(this, MessageBundle.get("dialogs.translation.conflict.title"), - MessageBundle.get("dialogs.translation.conflict.text." + (isReplace ? "replace" : "merge"))); + MessageBundle.get("dialogs.translation.conflict.text." + (isReplace ? "replace" : "merge")), + JOptionPane.WARNING_MESSAGE); if (confirm) { renameTranslationKey(key, newKey); } @@ -307,7 +373,8 @@ public void showDuplicateTranslationDialog(String key) { boolean isReplace = newNode.isLeaf() || oldNode.isLeaf(); boolean confirm = Dialogs.showConfirmDialog(this, MessageBundle.get("dialogs.translation.conflict.title"), - MessageBundle.get("dialogs.translation.conflict.text." + (isReplace ? "replace" : "merge"))); + MessageBundle.get("dialogs.translation.conflict.text." + (isReplace ? "replace" : "merge")), + JOptionPane.WARNING_MESSAGE); if (confirm) { duplicateTranslationKey(key, newKey); } @@ -370,7 +437,7 @@ public void showAboutDialog() { public void showVersionDialog(boolean newVersionOnly) { executor.execute(() -> { - GithubReleaseData data; + GithubRepoReleaseData data; String content; try { data = GithubRepoUtil.getLatestRelease(GITHUB_REPO).get(30, TimeUnit.SECONDS); @@ -392,62 +459,73 @@ public void showVersionDialog(boolean newVersionOnly) { }); } - public boolean closeCurrentSession() { + public boolean closeCurrentProject() { + int result = JOptionPane.NO_OPTION; if (isDirty()) { - int result = JOptionPane.showConfirmDialog(this, + result = JOptionPane.showConfirmDialog(this, MessageBundle.get("dialogs.save.text"), MessageBundle.get("dialogs.save.title"), JOptionPane.YES_NO_CANCEL_OPTION); if (result == JOptionPane.YES_OPTION) { - saveResources(); + saveProject(); } - return result != JOptionPane.CANCEL_OPTION; } - return true; + if (project != null) { + storeProjectState(); + } + return result != JOptionPane.CANCEL_OPTION; } public void reset() { + translationField.clear(); translationTree.clear(); - resources.clear(); resourceFields.clear(); setDirty(false); updateUI(); } public void launch() { - settings.load(SETTINGS_PATH); + restoreEditorState(); - // Restore editor settings - minifyOutput = settings.getBooleanProperty("minify_output"); - - // Restore window bounds - setPreferredSize(new Dimension(settings.getIntegerProperty("window_width", 1024), settings.getIntegerProperty("window_height", 768))); - setLocation(settings.getIntegerProperty("window_pos_x", 0), settings.getIntegerProperty("window_pos_y", 0)); - contentPane.setDividerLocation(settings.getIntegerProperty("divider_pos", 250)); + setPreferredSize(new Dimension(settings.getWindowWidth(), settings.getWindowHeight())); + setLocation(settings.getWindowPositionX(), settings.getWindowPositionY()); + contentPane.setDividerLocation(settings.getWindowDeviderPosition()); pack(); setVisible(true); - if (!loadResourcesFromHistory()) { - showImportDialog(); - } else { - // Restore last expanded nodes - List expandedKeys = settings.getListProperty("last_expanded"); + List dirs = settings.getHistory(); + if (!dirs.isEmpty()) { + String lastDir = dirs.get(dirs.size()-1); + Path path = Paths.get(lastDir); + if (Files.exists(path)) { + importProject(path, false); + } + } + + if (project == null) { + updateHistory(); + } + + if (project != null && project.hasResources()) { + // Restore last expanded nodes + List expandedKeys = settings.getLastExpandedNodes(); List expandedNodes = expandedKeys.stream() - .map(k -> translationTree.getNodeByKey(k)) + .map(translationTree::getNodeByKey) .filter(n -> n != null) .collect(Collectors.toList()); translationTree.expand(expandedNodes); - // Restore last selected node - String selectedKey = settings.getProperty("last_selected"); + String selectedKey = settings.getLastSelectedNode(); TranslationTreeNode selectedNode = translationTree.getNodeByKey(selectedKey); if (selectedNode != null) { translationTree.setSelectedNode(selectedNode); } - } + } - showVersionDialog(false); + if (settings.isCheckVersionOnStartup()) { + showVersionDialog(false); + } } private void setupUI() { @@ -456,7 +534,7 @@ private void setupUI() { addWindowListener(new EditorWindowListener()); setIconImages(Lists.newArrayList("512","256","128","64","48","32","24","20","16").stream() - .map(size -> getResourceImage("images/icon-" + size + ".png")) + .map(size -> getClasspathImage("images/icon-" + size + ".png")) .collect(Collectors.toList())); translationsPanel = new JPanel(new BorderLayout()); @@ -476,7 +554,7 @@ private void setupUI() { resourcesScrollPane.setBackground(resourcesPanel.getBackground()); contentPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, translationsPanel, resourcesScrollPane); - editorMenu = new EditorMenu(this, translationTree); + editorMenu = new EditorMenuBar(this, translationTree); introText = new JLabel("" + MessageBundle.get("core.intro.text") + ""); introText.setOpaque(true); @@ -486,7 +564,10 @@ private void setupUI() { introText.setHorizontalAlignment(JLabel.CENTER); introText.setVerticalAlignment(JLabel.CENTER); introText.setForeground(getBackground().darker()); - introText.setIcon(new ImageIcon(getResourceImage("images/icon-intro.png"))); + introText.setIcon(new ImageIcon(getClasspathImage("images/icon-intro.png"))); + + Container container = getContentPane(); + container.add(introText); setJMenuBar(editorMenu); } @@ -497,7 +578,7 @@ private void setupFileDrop() { public void filesDropped(java.io.File[] files) { try { Path path = Paths.get(files[0].getCanonicalPath()); - importResources(path); + importProject(path, true); } catch (IOException e ) { e.printStackTrace(); showError(MessageBundle.get("resources.open.error.multiple")); @@ -510,7 +591,6 @@ private void setupResource(Resource resource) { resource.addListener(e -> setDirty(true)); ResourceField field = new ResourceField(resource); field.addKeyListener(new ResourceFieldKeyListener()); - resources.add(resource); resourceFields.add(field); } @@ -519,9 +599,10 @@ private void updateUI() { resourcesPanel.removeAll(); resourceFields.stream().sorted().forEach(field -> { + Locale locale = field.getResource().getLocale(); field.setEditable(selectedNode != null && selectedNode.isEditable()); resourcesPanel.add(Box.createVerticalStrut(5)); - resourcesPanel.add(new JLabel(field.getResource().getLocale().getDisplayName())); + resourcesPanel.add(new JLabel(locale != null ? locale.getDisplayName() : "Default")); resourcesPanel.add(Box.createVerticalStrut(5)); resourcesPanel.add(field); resourcesPanel.add(Box.createVerticalStrut(5)); @@ -532,89 +613,115 @@ private void updateUI() { } Container container = getContentPane(); - if (resourcesDir != null) { + if (project != null) { container.add(contentPane); container.remove(introText); + List resources = project.getResources(); + editorMenu.setEnabled(true); + editorMenu.setEditable(!resources.isEmpty()); + translationTree.setEditable(!resources.isEmpty()); + translationField.setEditable(!resources.isEmpty()); } else { container.add(introText); container.remove(contentPane); + editorMenu.setEnabled(false); + editorMenu.setEditable(false); + translationTree.setEditable(false); + translationField.setEditable(false); } - editorMenu.setEnabled(resourcesDir != null); - editorMenu.setEditable(!resources.isEmpty()); - translationTree.setEditable(!resources.isEmpty()); - translationField.setEditable(!resources.isEmpty()); - updateTitle(); validate(); repaint(); } private void updateHistory() { - List recentDirs = settings.getListProperty("history"); - if (resourcesDir != null) { - String path = resourcesDir.toString(); + List recentDirs = settings.getHistory(); + if (project != null) { + String path = project.getPath().toString(); recentDirs.remove(path); recentDirs.add(path); if (recentDirs.size() > 5) { recentDirs.remove(0); } - settings.setProperty("history", recentDirs); + settings.setHistory(recentDirs); } editorMenu.setRecentItems(Lists.reverse(recentDirs)); } private void updateTitle() { String dirtyPart = dirty ? "*" : ""; - String filePart = resourcesDir == null ? "" : resourcesDir.toString() + " - "; - setTitle(dirtyPart + filePart + TITLE); - } - - private boolean loadResourcesFromHistory() { - List dirs = settings.getListProperty("history"); - if (!dirs.isEmpty()) { - String lastDir = dirs.get(dirs.size()-1); - Path path = Paths.get(lastDir); - if (Files.exists(path)) { - importResources(path); - return true; - } - } - return false; + String projectPart = ""; + if (project != null) { + projectPart = project.getPath().toString() + " [" + project.getResourceType() + "] - "; + } + setTitle(dirtyPart + projectPart + TITLE); } private void showError(String message) { Dialogs.showErrorDialog(this, MessageBundle.get("dialogs.error.title"), message); } - private Image getResourceImage(String path) { + private Image getClasspathImage(String path) { return new ImageIcon(getClass().getClassLoader().getResource(path)).getImage(); } + private void storeProjectState() { + ExtendedProperties props = new ExtendedProperties(); + props.setProperty("minify_resources", project.isMinifyResources()); + props.setProperty("resource_name", project.getResourceName()); + props.setProperty("resource_type", project.getResourceType().toString()); + props.store(Paths.get(project.getPath().toString(), PROJECT_FILE)); + } + + private void restoreProjectState(EditorProject project) { + ExtendedProperties props = new ExtendedProperties(); + props.load(Paths.get(project.getPath().toString(), PROJECT_FILE)); + project.setMinifyResources(props.getBooleanProperty("minify_resources", settings.isMinifyResources())); + project.setResourceName(props.getProperty("resource_name", settings.getResourceName())); + project.setResourceType(props.getEnumProperty("resource_type", ResourceType.class)); + } + private void storeEditorState() { - // Store editor settings - settings.setProperty("minify_output", minifyOutput); - - // Store window bounds - settings.setProperty("window_width", getWidth()); - settings.setProperty("window_height", getHeight()); - settings.setProperty("window_pos_x", getX()); - settings.setProperty("window_pos_y", getY()); - settings.setProperty("divider_pos", contentPane.getDividerLocation()); - - if (!resources.isEmpty()) { + ExtendedProperties props = new ExtendedProperties(); + props.setProperty("window_width", getWidth()); + props.setProperty("window_height", getHeight()); + props.setProperty("window_pos_x", getX()); + props.setProperty("window_pos_y", getY()); + props.setProperty("window_div_pos", contentPane.getDividerLocation()); + props.setProperty("minify_resources", settings.isMinifyResources()); + props.setProperty("resource_name", settings.getResourceName()); + props.setProperty("check_version", settings.isCheckVersionOnStartup()); + if (!settings.getHistory().isEmpty()) { + props.setProperty("history", settings.getHistory()); + } + if (project != null) { // Store keys of expanded nodes List expandedNodeKeys = translationTree.getExpandedNodes().stream() - .map(n -> n.getKey()) + .map(TranslationTreeNode::getKey) .collect(Collectors.toList()); - settings.setProperty("last_expanded", expandedNodeKeys); - + props.setProperty("last_expanded", expandedNodeKeys); // Store key of selected node TranslationTreeNode selectedNode = translationTree.getSelectedNode(); - settings.setProperty("last_selected", selectedNode == null ? "" : selectedNode.getKey()); + props.setProperty("last_selected", selectedNode == null ? "" : selectedNode.getKey()); } - - settings.store(SETTINGS_PATH, TITLE + " " + VERSION); + props.store(Paths.get(SETTINGS_DIR, SETTINGS_FILE)); + } + + private void restoreEditorState() { + ExtendedProperties props = new ExtendedProperties(); + props.load(Paths.get(SETTINGS_DIR, SETTINGS_FILE)); + settings.setWindowWidth(props.getIntegerProperty("window_width", 1024)); + settings.setWindowHeight(props.getIntegerProperty("window_height", 768)); + settings.setWindowPositionX(props.getIntegerProperty("window_pos_x", 0)); + settings.setWindowPositionY(props.getIntegerProperty("window_pos_y", 0)); + settings.setWindowDeviderPosition(props.getIntegerProperty("window_div_pos", 250)); + settings.setHistory(props.getListProperty("history")); + settings.setLastExpandedNodes(props.getListProperty("last_expanded")); + settings.setLastSelectedNode(props.getProperty("last_selected")); + settings.setMinifyResources(props.getBooleanProperty("minify_resources", false)); + settings.setResourceName(props.getProperty("resource_name", DEFAULT_RESOURCE_NAME)); + settings.setCheckVersionOnStartup(props.getBooleanProperty("check_version", true)); } private class TranslationTreeNodeSelectionListener implements TreeSelectionListener { @@ -666,7 +773,7 @@ public void keyReleased(KeyEvent e) { private class EditorWindowListener extends WindowAdapter { @Override public void windowClosing(WindowEvent e) { - if (closeCurrentSession()) { + if (closeCurrentProject()) { storeEditorState(); System.exit(0); } diff --git a/src/main/java/com/jvms/i18neditor/editor/EditorMenu.java b/src/main/java/com/jvms/i18neditor/editor/EditorMenuBar.java similarity index 65% rename from src/main/java/com/jvms/i18neditor/editor/EditorMenu.java rename to src/main/java/com/jvms/i18neditor/editor/EditorMenuBar.java index 5599fc3..ba296f7 100644 --- a/src/main/java/com/jvms/i18neditor/editor/EditorMenu.java +++ b/src/main/java/com/jvms/i18neditor/editor/EditorMenuBar.java @@ -8,14 +8,14 @@ import java.nio.file.Paths; import java.util.List; -import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; -import com.jvms.i18neditor.Resource.ResourceType; +import com.jvms.i18neditor.ResourceType; +import com.jvms.i18neditor.editor.menu.AddLocaleMenuItem; import com.jvms.i18neditor.editor.menu.AddTranslationMenuItem; import com.jvms.i18neditor.editor.menu.CollapseTranslationsMenuItem; import com.jvms.i18neditor.editor.menu.DuplicateTranslationMenuItem; @@ -23,7 +23,7 @@ import com.jvms.i18neditor.editor.menu.FindTranslationMenuItem; import com.jvms.i18neditor.editor.menu.RemoveTranslationMenuItem; import com.jvms.i18neditor.editor.menu.RenameTranslationMenuItem; -import com.jvms.i18neditor.editor.tree.TranslationTreeNode; +import com.jvms.i18neditor.swing.util.Dialogs; import com.jvms.i18neditor.util.MessageBundle; /** @@ -31,7 +31,7 @@ * * @author Jacob */ -public class EditorMenu extends JMenuBar { +public class EditorMenuBar extends JMenuBar { private final static long serialVersionUID = -101788804096708514L; private final Editor editor; @@ -44,22 +44,20 @@ public class EditorMenu extends JMenuBar { private JMenuItem duplicateTranslationMenuItem; private JMenuItem removeTranslationMenuItem; private JMenuItem openContainingFolderMenuItem; + private JMenuItem projectSettingsMenuItem; + private JMenuItem editorSettingsMenuItem; + private JMenu openRecentMenuItem; private JMenu editMenu; private JMenu viewMenu; - private JMenu openRecentMenuItem; + private JMenu settingsMenu; - public EditorMenu(Editor editor, TranslationTree tree) { + public EditorMenuBar(Editor editor, TranslationTree tree) { super(); this.editor = editor; this.tree = tree; setupUI(); } - public void setSaveable(boolean saveable) { - saveMenuItem.setEnabled(saveable); - updateComponentTreeUI(); - } - @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); @@ -67,6 +65,19 @@ public void setEnabled(boolean enabled) { openContainingFolderMenuItem.setEnabled(enabled); editMenu.setEnabled(enabled); viewMenu.setEnabled(enabled); + settingsMenu.removeAll(); + if (enabled) { + settingsMenu.add(projectSettingsMenuItem); + settingsMenu.addSeparator(); + settingsMenu.add(editorSettingsMenuItem); + } else { + settingsMenu.add(editorSettingsMenuItem); + } + updateComponentTreeUI(); + } + + public void setSaveable(boolean saveable) { + saveMenuItem.setEnabled(saveable); updateComponentTreeUI(); } @@ -86,10 +97,14 @@ public void setRecentItems(List items) { Integer n = i + 1; JMenuItem menuItem = new JMenuItem(n + ": " + items.get(i), Character.forDigit(i, 10)); Path path = Paths.get(menuItem.getText().replaceFirst("[0-9]+: ","")); - menuItem.addActionListener(e -> editor.importResources(path)); + menuItem.addActionListener(e -> editor.importProject(path, true)); menuItem.setAccelerator(KeyStroke.getKeyStroke(n.toString().charAt(0), Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); openRecentMenuItem.add(menuItem); } + JMenuItem clearMenuItem = new JMenuItem(MessageBundle.get("menu.file.recent.clear.title")); + clearMenuItem.addActionListener(e -> editor.clearHistory()); + openRecentMenuItem.addSeparator(); + openRecentMenuItem.add(clearMenuItem); } } @@ -97,37 +112,58 @@ private void setupUI() { // File menu JMenu fileMenu = new JMenu(MessageBundle.get("menu.file.title")); fileMenu.setMnemonic(MessageBundle.getMnemonic("menu.file.vk")); + + JMenuItem createJsonMenuItem = new JMenuItem(MessageBundle.get("menu.file.project.new.json.title")); + createJsonMenuItem.addActionListener(e -> editor.showCreateProjectDialog(ResourceType.JSON)); + createJsonMenuItem.setAccelerator(KeyStroke.getKeyStroke('J', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + + JMenuItem createEs6MenuItem = new JMenuItem(MessageBundle.get("menu.file.project.new.es6.title")); + createEs6MenuItem.addActionListener(e -> editor.showCreateProjectDialog(ResourceType.ES6)); + createEs6MenuItem.setAccelerator(KeyStroke.getKeyStroke('E', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + + JMenuItem createPropertiesMenuItem = new JMenuItem(MessageBundle.get("menu.file.project.new.properties.title")); + createPropertiesMenuItem.addActionListener(e -> editor.showCreateProjectDialog(ResourceType.Properties)); + createPropertiesMenuItem.setAccelerator(KeyStroke.getKeyStroke('P', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + + JMenu createMenuItem = new JMenu(MessageBundle.get("menu.file.project.new.title")); + createMenuItem.setMnemonic(MessageBundle.getMnemonic("menu.file.project.new.vk")); + createMenuItem.add(createJsonMenuItem); + createMenuItem.add(createEs6MenuItem); + createMenuItem.add(createPropertiesMenuItem); - JMenuItem openMenuItem = new JMenuItem(MessageBundle.get("menu.file.open.title"), MessageBundle.getMnemonic("menu.file.open.vk")); - openMenuItem.setAccelerator(KeyStroke.getKeyStroke('O', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); - openMenuItem.addActionListener(e -> editor.showImportDialog()); + JMenuItem importMenuItem = new JMenuItem(MessageBundle.get("menu.file.project.import.title")); + importMenuItem.setMnemonic(MessageBundle.getMnemonic("menu.file.project.import.vk")); + importMenuItem.addActionListener(e -> editor.showImportProjectDialog()); openContainingFolderMenuItem = new JMenuItem(MessageBundle.get("menu.file.folder.title")); + openContainingFolderMenuItem.setEnabled(false); openContainingFolderMenuItem.addActionListener(e -> { try { - Desktop.getDesktop().open(editor.getResourcesPath().toFile()); + Desktop.getDesktop().open(editor.getProject().getPath().toFile()); } catch (IOException ex) { ex.printStackTrace(); } }); openRecentMenuItem = new JMenu(MessageBundle.get("menu.file.recent.title")); + openRecentMenuItem.setEnabled(false); openRecentMenuItem.setMnemonic(MessageBundle.getMnemonic("menu.file.recent.vk")); saveMenuItem = new JMenuItem(MessageBundle.get("menu.file.save.title"), MessageBundle.getMnemonic("menu.file.save.vk")); saveMenuItem.setEnabled(false); saveMenuItem.setAccelerator(KeyStroke.getKeyStroke('S', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); - saveMenuItem.addActionListener(e -> editor.saveResources()); + saveMenuItem.addActionListener(e -> editor.saveProject()); reloadMenuItem = new JMenuItem(MessageBundle.get("menu.file.reload.title"), MessageBundle.getMnemonic("menu.file.reload.vk")); reloadMenuItem.setEnabled(false); reloadMenuItem.setAccelerator(KeyStroke.getKeyStroke("F5")); - reloadMenuItem.addActionListener(e -> editor.reloadResources()); + reloadMenuItem.addActionListener(e -> editor.reloadProject()); JMenuItem exitMenuItem = new JMenuItem(MessageBundle.get("menu.file.exit.title"), MessageBundle.getMnemonic("menu.file.exit.vk")); exitMenuItem.addActionListener(e -> editor.dispatchEvent(new WindowEvent(editor, WindowEvent.WINDOW_CLOSING))); - fileMenu.add(openMenuItem); + fileMenu.add(createMenuItem); + fileMenu.add(importMenuItem); if (Desktop.isDesktopSupported()) { fileMenu.add(openContainingFolderMenuItem); } @@ -143,25 +179,13 @@ private void setupUI() { editMenu.setMnemonic(MessageBundle.getMnemonic("menu.edit.vk")); editMenu.setEnabled(false); - JMenuItem addJsonResourceMenuItem = new JMenuItem(MessageBundle.get("menu.edit.add.locale.json.title")); - addJsonResourceMenuItem.addActionListener(e -> editor.showAddLocaleDialog(ResourceType.JSON)); - addJsonResourceMenuItem.setAccelerator(KeyStroke.getKeyStroke('J', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); - - JMenuItem addEs6ResourceMenuItem = new JMenuItem(MessageBundle.get("menu.edit.add.locale.es6.title")); - addEs6ResourceMenuItem.addActionListener(e -> editor.showAddLocaleDialog(ResourceType.ES6)); - addEs6ResourceMenuItem.setAccelerator(KeyStroke.getKeyStroke('E', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); - - JMenu addLocaleMenuItem = new JMenu(MessageBundle.get("menu.edit.add.locale.title")); - addLocaleMenuItem.add(addJsonResourceMenuItem); - addLocaleMenuItem.add(addEs6ResourceMenuItem); - addTranslationMenuItem = new AddTranslationMenuItem(editor, false); findTranslationMenuItem = new FindTranslationMenuItem(editor, false); removeTranslationMenuItem = new RemoveTranslationMenuItem(editor, false); duplicateTranslationMenuItem = new DuplicateTranslationMenuItem(editor, true); renameTranslationMenuItem = new RenameTranslationMenuItem(editor, false); - editMenu.add(addLocaleMenuItem); + editMenu.add(new AddLocaleMenuItem(editor, true)); editMenu.addSeparator(); editMenu.add(addTranslationMenuItem); editMenu.add(findTranslationMenuItem); @@ -178,13 +202,24 @@ private void setupUI() { viewMenu.add(new CollapseTranslationsMenuItem(tree)); // Settings menu - JMenu settingsMenu = new JMenu(MessageBundle.get("menu.settings.title")); + settingsMenu = new JMenu(MessageBundle.get("menu.settings.title")); settingsMenu.setMnemonic(MessageBundle.getMnemonic("menu.settings.vk")); - JCheckBoxMenuItem minifyMenuItem = new JCheckBoxMenuItem(MessageBundle.get("menu.settings.minify.title"), editor.isMinifyOutput()); - minifyMenuItem.addActionListener(e -> editor.setMinifyOutput(minifyMenuItem.isSelected())); + editorSettingsMenuItem = new JMenuItem(MessageBundle.get("menu.settings.preferences.editor.title")); + editorSettingsMenuItem.addActionListener(e -> { + Dialogs.showComponentDialog(editor, + MessageBundle.get("dialogs.preferences.editor.title"), + new EditorSettingsPane(editor)); + }); + + projectSettingsMenuItem = new JMenuItem(MessageBundle.get("menu.settings.preferences.project.title")); + projectSettingsMenuItem.addActionListener(e -> { + Dialogs.showComponentDialog(editor, + MessageBundle.get("dialogs.preferences.project.title"), + new EditorProjectSettingsPane(editor)); + }); - settingsMenu.add(minifyMenuItem); + settingsMenu.add(editorSettingsMenuItem); // Help menu JMenu helpMenu = new JMenu(MessageBundle.get("menu.help.title")); diff --git a/src/main/java/com/jvms/i18neditor/editor/EditorProject.java b/src/main/java/com/jvms/i18neditor/editor/EditorProject.java new file mode 100644 index 0000000..f50f44c --- /dev/null +++ b/src/main/java/com/jvms/i18neditor/editor/EditorProject.java @@ -0,0 +1,74 @@ +package com.jvms.i18neditor.editor; + +import java.nio.file.Path; +import java.util.List; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.jvms.i18neditor.Resource; +import com.jvms.i18neditor.ResourceType; + +public class EditorProject { + private Path path; + private String resourceName; + private ResourceType resourceType; + private List resources = Lists.newLinkedList(); + private boolean minifyResources; + + public EditorProject(Path path) { + this(path, null); + } + + public EditorProject(Path path, ResourceType resourceType) { + this.path = path; + this.resourceType = resourceType; + } + + public Path getPath() { + return path; + } + + public void setPath(Path path) { + this.path = path; + } + + public ResourceType getResourceType() { + return resourceType; + } + + public void setResourceType(ResourceType resourceType) { + this.resourceType = resourceType; + } + + public List getResources() { + return ImmutableList.copyOf(resources); + } + + public void setResources(List resources) { + this.resources = resources; + } + + public void addResource(Resource resource) { + resources.add(resource); + } + + public boolean hasResources() { + return !resources.isEmpty(); + } + + public String getResourceName() { + return resourceName; + } + + public void setResourceName(String resourceFilename) { + this.resourceName = resourceFilename; + } + + public boolean isMinifyResources() { + return minifyResources; + } + + public void setMinifyResources(boolean minifyResources) { + this.minifyResources = minifyResources; + } +} diff --git a/src/main/java/com/jvms/i18neditor/editor/EditorProjectSettingsPane.java b/src/main/java/com/jvms/i18neditor/editor/EditorProjectSettingsPane.java new file mode 100644 index 0000000..c677364 --- /dev/null +++ b/src/main/java/com/jvms/i18neditor/editor/EditorProjectSettingsPane.java @@ -0,0 +1,67 @@ +package com.jvms.i18neditor.editor; + +import java.awt.GridLayout; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.TitledBorder; + +import com.jvms.i18neditor.ResourceType; +import com.jvms.i18neditor.swing.JUndoableTextField; +import com.jvms.i18neditor.util.MessageBundle; + +public class EditorProjectSettingsPane extends JPanel { + private final static long serialVersionUID = 5665963334924596315L; + private Editor editor; + + public EditorProjectSettingsPane(Editor editor) { + super(); + this.editor = editor; + this.setupUI(); + } + + private void setupUI() { + EditorSettings settings = editor.getSettings(); + EditorProject project = editor.getProject(); + + GridLayout fieldsetLayout = new GridLayout(0, 1); + fieldsetLayout.setVgap(5); + + // General settings + JPanel fieldset1 = new JPanel(fieldsetLayout); + fieldset1.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(null, MessageBundle.get("settings.fieldset.general"), + TitledBorder.CENTER, TitledBorder.TOP), + BorderFactory.createEmptyBorder(10,10,10,10))); + + JPanel resourcePanel = new JPanel(new GridLayout(0, 1)); + JLabel resourceNameLabel = new JLabel(MessageBundle.get("settings.resourcename.title")); + JUndoableTextField resourceNameField = new JUndoableTextField(project.getResourceName(), 25); + resourceNameField.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + String value = resourceNameField.getText().trim(); + project.setResourceName(value.isEmpty() ? settings.getResourceName() : value); + } + }); + resourcePanel.add(resourceNameLabel); + resourcePanel.add(resourceNameField); + fieldset1.add(resourcePanel); + + if (project.getResourceType() != ResourceType.Properties) { + JPanel minifyPanel = new JPanel(new GridLayout(0, 1)); + JCheckBox minifyBox = new JCheckBox(MessageBundle.get("settings.minify.title")); + minifyBox.addChangeListener(e -> project.setMinifyResources(minifyBox.isSelected())); + minifyPanel.add(minifyBox); + fieldset1.add(minifyPanel); + } + + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + add(fieldset1); + } +} diff --git a/src/main/java/com/jvms/i18neditor/editor/EditorSettings.java b/src/main/java/com/jvms/i18neditor/editor/EditorSettings.java new file mode 100644 index 0000000..7bb883e --- /dev/null +++ b/src/main/java/com/jvms/i18neditor/editor/EditorSettings.java @@ -0,0 +1,105 @@ +package com.jvms.i18neditor.editor; + +import java.util.List; + +public class EditorSettings { + private int windowPositionX; + private int windowPositionY; + private int windowDeviderPosition; + private int windowWidth; + private int windowHeight; + private String resourceName; + private boolean minifyResources; + private List history; + private List lastExpandedNodes; + private String lastSelectedNode; + private boolean checkVersionOnStartup; + + public int getWindowPositionX() { + return windowPositionX; + } + + public void setWindowPositionX(int windowPositionX) { + this.windowPositionX = windowPositionX; + } + + public int getWindowPositionY() { + return windowPositionY; + } + + public void setWindowPositionY(int windowPositionY) { + this.windowPositionY = windowPositionY; + } + + public int getWindowDeviderPosition() { + return windowDeviderPosition; + } + + public void setWindowDeviderPosition(int deviderPosition) { + this.windowDeviderPosition = deviderPosition; + } + + public int getWindowWidth() { + return windowWidth; + } + + public void setWindowWidth(int width) { + this.windowWidth = width; + } + + public int getWindowHeight() { + return windowHeight; + } + + public void setWindowHeight(int height) { + this.windowHeight = height; + } + + public List getHistory() { + return history; + } + + public void setHistory(List history) { + this.history = history; + } + + public List getLastExpandedNodes() { + return lastExpandedNodes; + } + + public void setLastExpandedNodes(List lastExpandedNodes) { + this.lastExpandedNodes = lastExpandedNodes; + } + + public String getLastSelectedNode() { + return lastSelectedNode; + } + + public void setLastSelectedNode(String lastSelectedNode) { + this.lastSelectedNode = lastSelectedNode; + } + + public String getResourceName() { + return resourceName; + } + + public void setResourceName(String resourceName) { + this.resourceName = resourceName; + } + + public boolean isMinifyResources() { + return minifyResources; + } + + public void setMinifyResources(boolean minifyResources) { + this.minifyResources = minifyResources; + } + + public boolean isCheckVersionOnStartup() { + return checkVersionOnStartup; + } + + public void setCheckVersionOnStartup(boolean checkVersionOnStartup) { + this.checkVersionOnStartup = checkVersionOnStartup; + } +} diff --git a/src/main/java/com/jvms/i18neditor/editor/EditorSettingsPane.java b/src/main/java/com/jvms/i18neditor/editor/EditorSettingsPane.java new file mode 100644 index 0000000..b03bfef --- /dev/null +++ b/src/main/java/com/jvms/i18neditor/editor/EditorSettingsPane.java @@ -0,0 +1,80 @@ +package com.jvms.i18neditor.editor; + +import java.awt.GridLayout; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.TitledBorder; + +import com.jvms.i18neditor.swing.JUndoableTextField; +import com.jvms.i18neditor.util.MessageBundle; + +public class EditorSettingsPane extends JPanel { + private final static long serialVersionUID = 4488173853564278813L; + private Editor editor; + + public EditorSettingsPane(Editor editor) { + super(); + this.editor = editor; + this.setupUI(); + } + + private void setupUI() { + EditorSettings settings = editor.getSettings(); + + GridLayout fieldsetLayout = new GridLayout(0, 1); + fieldsetLayout.setVgap(5); + + // General settings + JPanel fieldset1 = new JPanel(fieldsetLayout); + fieldset1.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(null, MessageBundle.get("settings.fieldset.general"), + TitledBorder.CENTER, TitledBorder.TOP), + BorderFactory.createEmptyBorder(10,10,10,10))); + + JPanel updatesPanel = new JPanel(new GridLayout(0, 1)); + JCheckBox checkForUpdatesBox = new JCheckBox(MessageBundle.get("settings.version.title")); + checkForUpdatesBox.setSelected(settings.isCheckVersionOnStartup()); + checkForUpdatesBox.addChangeListener(e -> settings.setCheckVersionOnStartup(checkForUpdatesBox.isSelected())); + updatesPanel.add(checkForUpdatesBox); + fieldset1.add(updatesPanel); + + // New project settings + JPanel fieldset2 = new JPanel(fieldsetLayout); + fieldset2.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(null, MessageBundle.get("settings.fieldset.newprojects"), + TitledBorder.CENTER, TitledBorder.TOP), + BorderFactory.createEmptyBorder(10,10,10,10))); + + JPanel resourcePanel = new JPanel(new GridLayout(0, 1)); + JLabel resourceNameLabel = new JLabel(MessageBundle.get("settings.resourcename.title")); + JUndoableTextField resourceNameField = new JUndoableTextField(settings.getResourceName(), 25); + resourceNameField.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + String value = resourceNameField.getText().trim(); + settings.setResourceName(value.isEmpty() ? Editor.DEFAULT_RESOURCE_NAME : value); + } + }); + resourcePanel.add(resourceNameLabel); + resourcePanel.add(resourceNameField); + fieldset2.add(resourcePanel); + + JPanel minifyPanel = new JPanel(new GridLayout(0, 1)); + JCheckBox minifyBox = new JCheckBox(MessageBundle.get("settings.minify.title")); + minifyBox.addChangeListener(e -> settings.setMinifyResources(minifyBox.isSelected())); + minifyPanel.add(minifyBox); + fieldset2.add(minifyPanel); + + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + add(fieldset1); + add(Box.createVerticalStrut(10)); + add(fieldset2); + } +} diff --git a/src/main/java/com/jvms/i18neditor/editor/ResourceField.java b/src/main/java/com/jvms/i18neditor/editor/ResourceField.java index 25fdda0..8165b12 100644 --- a/src/main/java/com/jvms/i18neditor/editor/ResourceField.java +++ b/src/main/java/com/jvms/i18neditor/editor/ResourceField.java @@ -2,6 +2,7 @@ import java.awt.Color; import java.awt.KeyboardFocusManager; +import java.util.Locale; import javax.swing.BorderFactory; import javax.swing.border.Border; @@ -39,9 +40,15 @@ public Resource getResource() { @Override public int compareTo(ResourceField o) { - String a = getResource().getLocale().getDisplayName(); - String b = o.getResource().getLocale().getDisplayName(); - return a.compareTo(b); + Locale a = getResource().getLocale(); + Locale b = o.getResource().getLocale(); + if (a == null) { + return -1; + } + if (b == null) { + return 1; + } + return a.getDisplayName().compareTo(b.getDisplayName()); } private void setupUI() { diff --git a/src/main/java/com/jvms/i18neditor/editor/TranslationField.java b/src/main/java/com/jvms/i18neditor/editor/TranslationField.java index 03819a0..5c5ae53 100644 --- a/src/main/java/com/jvms/i18neditor/editor/TranslationField.java +++ b/src/main/java/com/jvms/i18neditor/editor/TranslationField.java @@ -17,6 +17,10 @@ public TranslationField() { setupUI(); } + public void clear() { + setValue(null); + } + public String getValue() { return getText().trim(); } diff --git a/src/main/java/com/jvms/i18neditor/editor/TranslationTree.java b/src/main/java/com/jvms/i18neditor/editor/TranslationTree.java index 323b65f..51497b0 100644 --- a/src/main/java/com/jvms/i18neditor/editor/TranslationTree.java +++ b/src/main/java/com/jvms/i18neditor/editor/TranslationTree.java @@ -14,8 +14,6 @@ import javax.swing.tree.TreePath; import com.google.common.collect.Lists; -import com.jvms.i18neditor.editor.tree.TranslationTreeModel; -import com.jvms.i18neditor.editor.tree.TranslationTreeNode; import com.jvms.i18neditor.util.ResourceKeys; /** diff --git a/src/main/java/com/jvms/i18neditor/editor/tree/TranslationTreeModel.java b/src/main/java/com/jvms/i18neditor/editor/TranslationTreeModel.java similarity index 94% rename from src/main/java/com/jvms/i18neditor/editor/tree/TranslationTreeModel.java rename to src/main/java/com/jvms/i18neditor/editor/TranslationTreeModel.java index ea30f82..9921566 100644 --- a/src/main/java/com/jvms/i18neditor/editor/tree/TranslationTreeModel.java +++ b/src/main/java/com/jvms/i18neditor/editor/TranslationTreeModel.java @@ -1,4 +1,4 @@ -package com.jvms.i18neditor.editor.tree; +package com.jvms.i18neditor.editor; import java.util.Enumeration; import java.util.List; @@ -21,7 +21,7 @@ public TranslationTreeModel() { } public TranslationTreeModel(List keys) { - super(new TranslationTreeNode(MessageBundle.get("translations.model.name"), keys)); + super(new TranslationTreeNode(MessageBundle.get("tree.root.name"), keys)); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jvms/i18neditor/editor/tree/TranslationTreeNode.java b/src/main/java/com/jvms/i18neditor/editor/TranslationTreeNode.java similarity index 98% rename from src/main/java/com/jvms/i18neditor/editor/tree/TranslationTreeNode.java rename to src/main/java/com/jvms/i18neditor/editor/TranslationTreeNode.java index 81df573..b63de13 100644 --- a/src/main/java/com/jvms/i18neditor/editor/tree/TranslationTreeNode.java +++ b/src/main/java/com/jvms/i18neditor/editor/TranslationTreeNode.java @@ -1,4 +1,4 @@ -package com.jvms.i18neditor.editor.tree; +package com.jvms.i18neditor.editor; import java.util.Arrays; import java.util.Collections; diff --git a/src/main/java/com/jvms/i18neditor/editor/TranslationTreeNodeMenu.java b/src/main/java/com/jvms/i18neditor/editor/TranslationTreeNodeMenu.java index a1d49e5..e0c1be7 100644 --- a/src/main/java/com/jvms/i18neditor/editor/TranslationTreeNodeMenu.java +++ b/src/main/java/com/jvms/i18neditor/editor/TranslationTreeNodeMenu.java @@ -6,7 +6,6 @@ import com.jvms.i18neditor.editor.menu.DuplicateTranslationMenuItem; import com.jvms.i18neditor.editor.menu.RemoveTranslationMenuItem; import com.jvms.i18neditor.editor.menu.RenameTranslationMenuItem; -import com.jvms.i18neditor.editor.tree.TranslationTreeNode; /** * This class represents a right click menu for a single node of the translation tree. @@ -14,8 +13,8 @@ * @author Jacob */ public class TranslationTreeNodeMenu extends JPopupMenu { - private final static long serialVersionUID = -4407236120087907574L; - + private final static long serialVersionUID = -8450484152294368841L; + public TranslationTreeNodeMenu(Editor editor, TranslationTreeNode node) { super(); add(new AddTranslationMenuItem(editor, true)); diff --git a/src/main/java/com/jvms/i18neditor/editor/menu/AddLocaleMenuItem.java b/src/main/java/com/jvms/i18neditor/editor/menu/AddLocaleMenuItem.java new file mode 100644 index 0000000..801c8ba --- /dev/null +++ b/src/main/java/com/jvms/i18neditor/editor/menu/AddLocaleMenuItem.java @@ -0,0 +1,25 @@ +package com.jvms.i18neditor.editor.menu; + +import java.awt.Toolkit; + +import javax.swing.JMenuItem; +import javax.swing.KeyStroke; + +import com.jvms.i18neditor.editor.Editor; +import com.jvms.i18neditor.util.MessageBundle; + +/** + * This class represents a menu item for adding a new locale. + * + * @author Jacob + */ +public class AddLocaleMenuItem extends JMenuItem { + private final static long serialVersionUID = -5108677891532028898L; + + public AddLocaleMenuItem(Editor editor, boolean enabled) { + super(MessageBundle.get("menu.edit.add.locale.title")); + setAccelerator(KeyStroke.getKeyStroke('L', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + addActionListener(e -> editor.showAddLocaleDialog()); + setEnabled(enabled); + } +} \ No newline at end of file diff --git a/src/main/java/com/jvms/i18neditor/editor/menu/FindTranslationMenuItem.java b/src/main/java/com/jvms/i18neditor/editor/menu/FindTranslationMenuItem.java index 762cea8..9740b07 100644 --- a/src/main/java/com/jvms/i18neditor/editor/menu/FindTranslationMenuItem.java +++ b/src/main/java/com/jvms/i18neditor/editor/menu/FindTranslationMenuItem.java @@ -14,7 +14,7 @@ * @author Jacob */ public class FindTranslationMenuItem extends JMenuItem { - private final static long serialVersionUID = 5207946396515235714L; + private final static long serialVersionUID = -1298283182450978961L; public FindTranslationMenuItem(Editor editor, boolean enabled) { super(MessageBundle.get("menu.edit.find.translation.title")); diff --git a/src/main/java/com/jvms/i18neditor/editor/menu/RenameTranslationMenuItem.java b/src/main/java/com/jvms/i18neditor/editor/menu/RenameTranslationMenuItem.java index b1c9682..51a0e96 100644 --- a/src/main/java/com/jvms/i18neditor/editor/menu/RenameTranslationMenuItem.java +++ b/src/main/java/com/jvms/i18neditor/editor/menu/RenameTranslationMenuItem.java @@ -12,8 +12,8 @@ * @author Jacob */ public class RenameTranslationMenuItem extends JMenuItem { - private final static long serialVersionUID = 5207946396515235714L; - + private final static long serialVersionUID = 907122077814626286L; + public RenameTranslationMenuItem(Editor editor, boolean enabled) { super(MessageBundle.get("menu.edit.rename.title")); setAccelerator(KeyStroke.getKeyStroke("F2")); diff --git a/src/main/java/com/jvms/i18neditor/swing/util/Dialogs.java b/src/main/java/com/jvms/i18neditor/swing/util/Dialogs.java index 763f542..3e0735e 100644 --- a/src/main/java/com/jvms/i18neditor/swing/util/Dialogs.java +++ b/src/main/java/com/jvms/i18neditor/swing/util/Dialogs.java @@ -22,45 +22,33 @@ public final class Dialogs { public static void showErrorDialog(Component parent, String title, String message) { - showMessageDialog(parent, title, message, JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(parent, message, title, JOptionPane.ERROR_MESSAGE); } public static void showWarningDialog(Component parent, String title, String message) { - showMessageDialog(parent, title, message, JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog(parent, message, title, JOptionPane.WARNING_MESSAGE); } - public static void showMessageDialog(Component parent, String title, String message) { - showMessageDialog(parent, title, message, JOptionPane.PLAIN_MESSAGE); - } - - public static void showMessageDialog(Component parent, String title, Component component) { - showMessageDialog(parent, title, component, JOptionPane.PLAIN_MESSAGE); + public static void showInfoDialog(Component parent, String title, String message) { + JOptionPane.showMessageDialog(parent, message, title, JOptionPane.INFORMATION_MESSAGE); } - public static void showMessageDialog(Component parent, String title, String message, int type) { - JOptionPane.showMessageDialog(parent, message, title, type); + public static void showMessageDialog(Component parent, String title, String message) { + JOptionPane.showMessageDialog(parent, message, title, JOptionPane.PLAIN_MESSAGE); } - public static void showMessageDialog(Component parent, String title, Component component, int type) { - JOptionPane.showMessageDialog(parent, component, title, type); + public static void showComponentDialog(Component parent, String title, Component component) { + JOptionPane.showMessageDialog(parent, component, title, JOptionPane.PLAIN_MESSAGE); } public static void showHtmlDialog(Component parent, String title, String body) { Font font = parent.getFont(); JHtmlPane pane = new JHtmlPane(parent, "" + body + ""); - showMessageDialog(parent, title, pane); - } - - public static boolean showConfirmDialog(Component parent, String title, String message) { - return showConfirmDialog(parent, title, message, JOptionPane.WARNING_MESSAGE); + showComponentDialog(parent, title, pane); } public static boolean showConfirmDialog(Component parent, String title, String message, int type) { - return JOptionPane.showConfirmDialog(parent, message, title, JOptionPane.YES_NO_OPTION, type) == 0 ? true : false; - } - - public static String showInputDialog(Component parent, String title, String label, int type) { - return showInputDialog(parent, title, label, type, null, false); + return JOptionPane.showConfirmDialog(parent, message, title, type) == JOptionPane.YES_OPTION; } public static String showInputDialog(Component parent, String title, String label, int type, String initialText, boolean selectAll) { @@ -81,4 +69,8 @@ public static String showInputDialog(Component parent, String title, String labe int result = JOptionPane.showConfirmDialog(parent, panel, title, JOptionPane.OK_CANCEL_OPTION, type); return result == JOptionPane.OK_OPTION ? field.getText() : null; } + + public static String showInputDialog(Component parent, String title, String label, int type) { + return showInputDialog(parent, title, label, type, null, false); + } } diff --git a/src/main/java/com/jvms/i18neditor/util/ExtendedProperties.java b/src/main/java/com/jvms/i18neditor/util/ExtendedProperties.java index 873e456..704b9dc 100644 --- a/src/main/java/com/jvms/i18neditor/util/ExtendedProperties.java +++ b/src/main/java/com/jvms/i18neditor/util/ExtendedProperties.java @@ -1,5 +1,6 @@ package com.jvms.i18neditor.util; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -10,6 +11,7 @@ import java.util.Properties; import java.util.stream.Collectors; +import com.google.common.base.Strings; import com.google.common.collect.Lists; /** @@ -65,29 +67,29 @@ public ExtendedProperties(Properties defaults, String separator) { /** * Reads a property list from the given file path. * + *

Any {@code IOException} will be ignored.

+ * * @param path the path to the property file. */ public void load(Path path) { - if (Files.exists(path)) { - try (InputStream in = Files.newInputStream(path)) { - load(in); - } catch (IOException e) { - e.printStackTrace(); - } + try (InputStream in = Files.newInputStream(path)) { + load(in); + } catch (IOException e) { } } /** * Writes the property list to the given file path. * + *

Any {@code IOException} will be ignored.

+ * * @param path the path to the property file. * @param comments the comments to add to the property file. */ - public void store(Path path, String comments) { - try (OutputStream out = Files.newOutputStream(path)) { - store(out, comments); + public void store(Path path) { + try (OutputStream out = new OutputStreamWrapper(Files.newOutputStream(path))) { + store(out, null); } catch (IOException e) { - e.printStackTrace(); } } @@ -120,8 +122,19 @@ public void setProperty(String key, Integer value) { * @param key the key to be placed in this property list. * @param value the value corresponding to {@code key}. */ - public void setProperty(String key, boolean value) { - setProperty(key, value ? 1 : 0); + public void setProperty(String key, Boolean value) { + setProperty(key, value == null ? null : (value ? 1 : 0)); + } + + /** + * Sets a value in the property list. The {@code Enum} value will be + * stored as a {@code String} value. + * + * @param key the key to be placed in this property list. + * @param value the value corresponding to {@code key}. + */ + public void setProperty(String key, Enum value) { + setProperty(key, value.toString()); } /** @@ -129,7 +142,8 @@ public void setProperty(String key, boolean value) { * to retrieve a value previously stored by {@link #setProperty(String, List)}. * * @param key the property key. - * @return the value in this property list with the specified key value. + * @return the value in this property list with the specified key value or an empty list + * if no such key exists. */ public List getListProperty(String key) { String value = getProperty(key); @@ -141,37 +155,94 @@ public List getListProperty(String key) { * to retrieve a value previously stored by {@link #setProperty(String, Integer)}. * * @param key the property key. - * @return the value in this property list with the specified key value. + * @return the value in this property list with the specified key value or {@code null} + * if no such key exists or the value is not a valid {@code Integer}. */ public Integer getIntegerProperty(String key) { String value = getProperty(key); - return value != null && !value.isEmpty() ? Integer.parseInt(value) : null; + if (!Strings.isNullOrEmpty(value)) { + try { + return Integer.parseInt(value); + } catch (Exception e) { + } + } + return null; + } + + /** + * See {@link #getIntegerProperty(String)}. This method returns {@code defaultValue} when + * there is no value in the property list with the specified {@code key} or when + * the value is not a valid {@code Integer}. + * + * @param key the property key. + * @param defaultValue the default value to return when there is no value for the specified key + * @return the value in this property list with the specified key value or {@code defaultValue} + * if no such key exists or the value is not a valid {@code Integer}. + */ + public Integer getIntegerProperty(String key, Integer defaultValue) { + Integer value = getIntegerProperty(key); + return value != null ? value : defaultValue; } /** - * Gets a value from the property list as an {@code boolean}. This method should be used - * to retrieve a value previously stored by {@link #setProperty(String, boolean)}. + * Gets a value from the property list as an {@code Boolean}. This method should be used + * to retrieve a value previously stored by {@link #setProperty(String, Boolean)}. * * @param key the property key. - * @return the value in this property list with the specified key value or {@code false} + * @return the value in this property list with the specified key value or {@code null} * if no such key exists. */ - public boolean getBooleanProperty(String key) { - Integer value = getIntegerProperty(key, 0); - return value == 1; + public Boolean getBooleanProperty(String key) { + Integer value = getIntegerProperty(key); + return value != null ? (value != 0) : null; } /** - * See {@link #getIntegerProperty(String)}. This method returns {@code defaultValue} when + * See {@link #getBooleanProperty(String)}. This method returns {@code defaultValue} when * there is no value in the property list with the specified {@code key}. * - * @param key the property key. + * @param key the property key. * @param defaultValue the default value to return when there is no value for the specified key - * @return the value in this property list with the specified key value or the defaultValue - * when there is no value with the specified key value. + * @return the value in this property list with the specified key value or {@code defaultValue} + * if no such key exists. */ - public Integer getIntegerProperty(String key, Integer defaultValue) { - Integer value = getIntegerProperty(key); + public Boolean getBooleanProperty(String key, boolean defaultValue) { + Boolean value = getBooleanProperty(key); + return value != null ? value : defaultValue; + } + + /** + * Gets a value from the property list as an {@code Enum}. This method should be used + * to retrieve a value previously stored by {@link #setProperty(String, Enum)}. + * + * @param key the property key. + * @param defaultValue the default value to return when there is no value for the specified key + * @return the value in this property list with the specified key value or {@code null} + * if no such key exists or the value is not a valid enum value. + */ + public > T getEnumProperty(String key, Class enumType) { + String value = getProperty(key); + if (!Strings.isNullOrEmpty(value)) { + try { + return T.valueOf(enumType, value); + } catch (Exception e) { + } + } + return null; + } + + /** + * See {@link #getEnumProperty(String, Class)}. This method returns {@code defaultValue} when + * there is no value in the property list with the specified {@code key} or when + * the value is not a valid enum value. + * + * @param key the property key. + * @param defaultValue the default value to return when there is no value for the specified key + * @return the value in this property list with the specified key value or {@code defaultValue} + * if no such key exists or the value is not a valid enum value. + */ + public > T getEnumProperty(String key, Class enumType, T defaultValue) { + T value = getEnumProperty(key, enumType); return value != null ? value : defaultValue; } @@ -185,4 +256,21 @@ public Integer getIntegerProperty(String key, Integer defaultValue) { public boolean containsKeys(String... keys) { return Arrays.asList(keys).stream().allMatch(k -> containsKey(k)); } + + private class OutputStreamWrapper extends FilterOutputStream { + private boolean firstlineseen = false; + + public OutputStreamWrapper(OutputStream out) { + super(out); + } + + @Override + public void write(int b) throws IOException { + if (firstlineseen) { + super.write(b); + } else if (b == '\n') { + firstlineseen = true; + } + } + } } \ No newline at end of file diff --git a/src/main/java/com/jvms/i18neditor/util/GithubRepoUtil.java b/src/main/java/com/jvms/i18neditor/util/GithubRepoUtil.java index 1df62e7..578ab9a 100644 --- a/src/main/java/com/jvms/i18neditor/util/GithubRepoUtil.java +++ b/src/main/java/com/jvms/i18neditor/util/GithubRepoUtil.java @@ -14,7 +14,7 @@ import com.google.gson.annotations.SerializedName; /** - * This class provides utility functions for retrieving Github repo data. + * This class provides utility functions for retrieving Github Repository data. * * @author Jacob */ @@ -24,24 +24,24 @@ public final class GithubRepoUtil { static { executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder() - .setNameFormat("github-repo-pool-%d") + .setNameFormat("github-repo-util-pool-%d") .build()); } /** - * Gets the latest release data of a Github repo. + * Gets the latest release data of a Github Repository. * - * @param repo the Github repo to get the latest release data from. - * @return the latest Github release data. + * @param repo the Github Repository to get the latest release data from. + * @return the latest Github Repository release data. */ - public static Future getLatestRelease(String repo) { + public static Future getLatestRelease(String repo) { return executor.submit(() -> { HttpURLConnection connection = null; URL url = new URL("https://api.github.com/repos/" + repo + "/releases/latest"); try { connection = (HttpURLConnection)url.openConnection(); try (InputStreamReader reader = new InputStreamReader(connection.getInputStream(), Charsets.UTF_8)) { - return gson.fromJson(reader, GithubReleaseData.class); + return gson.fromJson(reader, GithubRepoReleaseData.class); } } catch (IOException e) { return null; @@ -54,9 +54,9 @@ public static Future getLatestRelease(String repo) { } /** - * This class represents Github release data. + * This class represents Github Repository release data. */ - public final class GithubReleaseData { + public final class GithubRepoReleaseData { @SerializedName("tag_name") private String tagName; @SerializedName("html_url") diff --git a/src/main/java/com/jvms/i18neditor/util/ResourceFiles.java b/src/main/java/com/jvms/i18neditor/util/ResourceFiles.java deleted file mode 100644 index e44e666..0000000 --- a/src/main/java/com/jvms/i18neditor/util/ResourceFiles.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.jvms.i18neditor.util; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.SortedMap; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.StringEscapeUtils; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; -import com.jvms.i18neditor.Resource; -import com.jvms.i18neditor.Resource.ResourceType; - -/** - * This class provides file utility functions for a {@link Resource}. - * - * @author Jacob - */ -public final class ResourceFiles { - private final static String RESOURCE_FILENAME = "translations"; - private final static Charset DEFAULT_ENCODING = Charset.forName("UTF-8"); - private final static String LOCALE_REGEX = "[a-z]{2}(_[a-z]{2})?"; - - /** - * Checks whether the given path is a valid resource path. - * A valid resource path is either a valid JSON or ES6 resource path. - * - * @see {@link #isJsonResource(Path)}. - * @see {@link #isEs6Resource(Path)}. - * - * @param path the path to check. - * @return whether the given path is a valid resource path. - */ - public static boolean isResource(Path path) { - return isJsonResource(path) || isEs6Resource(path); - } - - /** - * Checks whether the given path is a valid JSON resource path. - * A valid JSON resource path is of the form 'en_US/translations.json'. - * - * @param path the path to check. - * @return whether the given path is a valid JSON resource path. - */ - public static boolean isJsonResource(Path path) { - return Files.isDirectory(path) - && Pattern.matches("^(?i:" + LOCALE_REGEX + ")$", path.getFileName().toString()) - && Files.isRegularFile(Paths.get(path.toString(), RESOURCE_FILENAME + ".json")); - } - - /** - * Checks whether the given path is a valid ES6 resource path. - * A valid ES6 resource path is of the form 'en_US/translations.js'. - * - * @param path the path to check. - * @return whether the given path is a valid ES6 resource path. - */ - public static boolean isEs6Resource(Path path) { - return Files.isDirectory(path) - && Pattern.matches("^(?i:" + LOCALE_REGEX + ")$", path.getFileName().toString()) - && Files.isRegularFile(Paths.get(path.toString(), RESOURCE_FILENAME + ".js")); - } - - /** - * Creates a new {@link Resource} from the given resource path. - * If the path is not a valid resource path, {@code null} will be returned. - * - * @param path the path to read. - * @return the resource. - * @throws IOException if an I/O error occurs reading the file. - */ - public static Resource read(Path path) throws IOException { - if (!isResource(path)) return null; - ResourceType type; - Path filePath; - if (isEs6Resource(path)) { - type = ResourceType.ES6; - filePath = Paths.get(path.toString(), RESOURCE_FILENAME + ".js"); - } else { - type = ResourceType.JSON; - filePath = Paths.get(path.toString(), RESOURCE_FILENAME + ".json"); - } - String content = Files.lines(filePath, DEFAULT_ENCODING).collect(Collectors.joining()); - if (type == ResourceType.ES6) { - content = es6ToJson(content); - } - Locale locale = parseLocale(path.getFileName().toString()); - return new Resource(type, filePath, locale, fromJson(content)); - } - - /** - * Writes the contents of the given resource to disk. - * - * @param resource the resource to write. - * @param prettyPrinting whether to pretty print the contents - * @throws IOException if an I/O error occurs writing the file. - */ - public static void write(Resource resource, boolean prettyPrinting) throws IOException { - String content = toJson(resource.getTranslations(), prettyPrinting); - if (resource.getType() == ResourceType.ES6) { - content = jsonToEs6(content); - } - if (!Files.exists(resource.getPath())) { - Files.createDirectories(resource.getPath().getParent()); - Files.createFile(resource.getPath()); - } - Files.write(resource.getPath(), Lists.newArrayList(content), DEFAULT_ENCODING); - } - - /** - * Creates a new {@link Resource} from the given {@link ResourceType} and resource path. - * This function should be used to create new resources. For creating an instance of an - * existing resource on disk, see {@link #read(Path)}. - * - * @param type the type of the resource to create. - * @param path the path to write the resource to. - * @return The newly created resource. - * @throws IOException if an I/O error occurs writing the file. - */ - public static Resource create(ResourceType type, Path path) throws IOException { - Path filePath; - if (type == ResourceType.ES6) { - filePath = Paths.get(path.toString(), RESOURCE_FILENAME + ".js"); - } else { - filePath = Paths.get(path.toString(), RESOURCE_FILENAME + ".json"); - } - Locale locale = parseLocale(path.getFileName().toString()); - Resource resource = new Resource(type, filePath, locale); - write(resource, false); - return resource; - } - - private static Locale parseLocale(String locale) { - String[] localeParts = locale.split("_"); - if (localeParts.length > 1) { - return new Locale(localeParts[0], localeParts[1]); - } else { - return new Locale(localeParts[0]); - } - } - - private static SortedMap fromJson(String json) { - SortedMap result = Maps.newTreeMap(); - JsonElement elem = new JsonParser().parse(json); - fromJson(null, elem, result); - return result; - } - - private static void fromJson(String key, JsonElement elem, Map content) { - if (elem.isJsonObject()) { - elem.getAsJsonObject().entrySet().forEach(entry -> { - String newKey = key == null ? entry.getKey() : ResourceKeys.create(key, entry.getKey()); - fromJson(newKey, entry.getValue(), content); - }); - } else if (elem.isJsonPrimitive()) { - content.put(key, StringEscapeUtils.unescapeJava(elem.getAsString())); - } else if (elem.isJsonNull()) { - content.put(key, ""); - } else { - throw new IllegalArgumentException("Found invalid json element."); - } - } - - private static String toJson(Map translations, boolean prettyPrinting) { - List keys = Lists.newArrayList(translations.keySet()); - JsonElement elem = toJson(translations, null, keys); - GsonBuilder builder = new GsonBuilder().disableHtmlEscaping(); - if (prettyPrinting) { - builder.setPrettyPrinting(); - } - return builder.create().toJson(elem); - } - - private static JsonElement toJson(Map translations, String key, List keys) { - if (keys.size() > 0) { - JsonObject object = new JsonObject(); - ResourceKeys.uniqueRootKeys(keys).forEach(rootKey -> { - String subKey = ResourceKeys.create(key, rootKey); - List subKeys = ResourceKeys.extractChildKeys(keys, rootKey); - object.add(rootKey, toJson(translations, subKey, subKeys)); - }); - return object; - } - if (key == null) { - return new JsonObject(); - } - return new JsonPrimitive(translations.get(key)); - } - - private static String es6ToJson(String content) { - return content.replaceAll("export +default", "").replaceAll("} *;", "}"); - } - - private static String jsonToEs6(String content) { - return "export default " + content + ";"; - } -} diff --git a/src/main/java/com/jvms/i18neditor/util/Resources.java b/src/main/java/com/jvms/i18neditor/util/Resources.java new file mode 100644 index 0000000..2153872 --- /dev/null +++ b/src/main/java/com/jvms/i18neditor/util/Resources.java @@ -0,0 +1,241 @@ +package com.jvms.i18neditor.util; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.SortedMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.LocaleUtils; +import org.apache.commons.lang3.StringEscapeUtils; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.jvms.i18neditor.Resource; +import com.jvms.i18neditor.ResourceType; + +/** + * This class provides utility functions for a {@link Resource}. + * + * @author Jacob + */ +public final class Resources { + private final static Charset UTF8_ENCODING = Charset.forName("UTF-8"); + private final static String LOCALE_REGEX = "[a-z]{2}(_[A-Z]{2})?"; + + /** + * Gets all resources from the given {@code rootDir} directory path. + * + *

The {@code baseName} is the base of the filename of the resource files to look for.
+ * The base name is without extension and without any locale information.
+ * When a resource type is given, only resources of that type will be looked for.

+ * + *

This function will not load the contents of the file, only its description.
+ * If you want to load the contents, use {@link #load(Resource)} afterwards.

+ * + * @param rootDir the root directory of the resources + * @param baseName the base name of the resource files to look for + * @param type the type of the resource files to look for + * @return list of found resources + * @throws IOException if an I/O error occurs reading the directory. + */ + public static List get(Path rootDir, String baseName, Optional type) throws IOException { + List result = Lists.newLinkedList(); + Files.walk(rootDir, 1).forEach(p -> { + ResourceType resourceType = null; + if (isResource(p, ResourceType.ES6, baseName)) { + resourceType = ResourceType.ES6; + } else if (isResource(p, ResourceType.JSON, baseName)) { + resourceType = ResourceType.JSON; + } else if (isResource(p, ResourceType.Properties, baseName)) { + resourceType = ResourceType.Properties; + } + if (resourceType != null && (!type.isPresent() || type.get() == resourceType)) { + String fileName = p.getFileName().toString(); + String extension = resourceType.getExtension(); + Locale locale = null; + Path path = null; + if (resourceType.isEmbedLocale()) { + String pattern = "^" + baseName + "_("+LOCALE_REGEX+")" + extension + "$"; + Matcher match = Pattern.compile(pattern).matcher(fileName); + if (match.find()) { + locale = LocaleUtils.toLocale(match.group(1)); + } + path = Paths.get(rootDir.toString(), baseName + (locale == null ? "" : "_" + locale.toString()) + extension); + } else { + locale = LocaleUtils.toLocale(fileName); + path = Paths.get(rootDir.toString(), locale.toString(), baseName + extension); + } + result.add(new Resource(resourceType, path, locale)); + } + }); + return result; + } + + /** + * Loads the translations of a {@link Resource} from disk. + * + * @param resource the resource. + * @throws IOException if an I/O error occurs reading the file. + */ + public static void load(Resource resource) throws IOException { + ResourceType type = resource.getType(); + Path path = resource.getPath(); + SortedMap translations; + if (type == ResourceType.Properties) { + ExtendedProperties content = new ExtendedProperties(); + content.load(path); + translations = fromProperties(content); + } else { + String content = Files.lines(path, UTF8_ENCODING).collect(Collectors.joining()); + if (type == ResourceType.ES6) { + content = es6ToJson(content); + } + translations = fromJson(content); + } + resource.setTranslations(translations); + } + + /** + * Writes the translations of the given resource to disk. + * + * @param resource the resource to write. + * @param prettyPrinting whether to pretty print the contents + * @throws IOException if an I/O error occurs writing the file. + */ + public static void write(Resource resource, boolean prettyPrinting) throws IOException { + ResourceType type = resource.getType(); + if (type == ResourceType.Properties) { + ExtendedProperties content = toProperties(resource.getTranslations()); + content.store(resource.getPath()); + } else { + String content = toJson(resource.getTranslations(), prettyPrinting); + if (type == ResourceType.ES6) { + content = jsonToEs6(content); + } + if (!Files.exists(resource.getPath())) { + Files.createDirectories(resource.getPath().getParent()); + Files.createFile(resource.getPath()); + } + Files.write(resource.getPath(), Lists.newArrayList(content), UTF8_ENCODING); + } + } + + /** + * Creates a new {@link Resource} with the given {@link ResourceType} in the given directory path. + * This function should be used to create new resources. For creating an instance of an + * existing resource on disk, see {@link #read(Path)}. + * + * @param type the type of the resource to create. + * @param root the root directory to write the resource to. + * @return The newly created resource. + * @throws IOException if an I/O error occurs writing the file. + */ + public static Resource create(Path root, ResourceType type, Optional locale, String baseName) throws IOException { + String extension = type.getExtension(); + Path path; + if (type.isEmbedLocale()) { + path = Paths.get(root.toString(), baseName + (locale.isPresent() ? "_" + locale.get().toString() : "") + extension); + } else { + path = Paths.get(root.toString(), locale.get().toString(), baseName + extension); + } + Resource resource = new Resource(type, path, locale.orElse(null)); + write(resource, false); + return resource; + } + + private static boolean isResource(Path path, ResourceType type, String baseName) { + String extension = type.getExtension(); + if (type.isEmbedLocale()) { + return Files.isRegularFile(path) && + Pattern.matches("^" + baseName + "(_"+LOCALE_REGEX+")?" + extension + "$", path.getFileName().toString()); + } else { + return Files.isDirectory(path) && + Pattern.matches("^" + LOCALE_REGEX + "$", path.getFileName().toString()) && + Files.isRegularFile(Paths.get(path.toString(), baseName + extension)); + } + } + + private static SortedMap fromProperties(ExtendedProperties properties) { + SortedMap result = Maps.newTreeMap(); + properties.forEach((key, value) -> { + result.put((String)key, StringEscapeUtils.unescapeJava((String)value)); + }); + return result; + } + + private static ExtendedProperties toProperties(Map translations) { + ExtendedProperties result = new ExtendedProperties(); + result.putAll(translations); + return result; + } + + private static SortedMap fromJson(String json) { + SortedMap result = Maps.newTreeMap(); + JsonElement elem = new JsonParser().parse(json); + fromJson(null, elem, result); + return result; + } + + private static void fromJson(String key, JsonElement elem, Map content) { + if (elem.isJsonObject()) { + elem.getAsJsonObject().entrySet().forEach(entry -> { + String newKey = key == null ? entry.getKey() : ResourceKeys.create(key, entry.getKey()); + fromJson(newKey, entry.getValue(), content); + }); + } else if (elem.isJsonPrimitive()) { + content.put(key, StringEscapeUtils.unescapeJava(elem.getAsString())); + } else if (elem.isJsonNull()) { + content.put(key, ""); + } else { + throw new IllegalArgumentException("Found invalid json element."); + } + } + + private static String toJson(Map translations, boolean prettify) { + List keys = Lists.newArrayList(translations.keySet()); + JsonElement elem = toJson(translations, null, keys); + GsonBuilder builder = new GsonBuilder().disableHtmlEscaping(); + if (prettify) { + builder.setPrettyPrinting(); + } + return builder.create().toJson(elem); + } + + private static JsonElement toJson(Map translations, String key, List keys) { + if (keys.size() > 0) { + JsonObject object = new JsonObject(); + ResourceKeys.uniqueRootKeys(keys).forEach(rootKey -> { + String subKey = ResourceKeys.create(key, rootKey); + List subKeys = ResourceKeys.extractChildKeys(keys, rootKey); + object.add(rootKey, toJson(translations, subKey, subKeys)); + }); + return object; + } + if (key == null) { + return new JsonObject(); + } + return new JsonPrimitive(translations.get(key)); + } + + private static String es6ToJson(String content) { + return content.replaceAll("export +default", "").replaceAll("} *;", "}"); + } + + private static String jsonToEs6(String content) { + return "export default " + content + ";"; + } +} diff --git a/src/main/resources/bundles/messages.properties b/src/main/resources/bundles/messages.properties index 1c8e51f..08c8ca7 100644 --- a/src/main/resources/bundles/messages.properties +++ b/src/main/resources/bundles/messages.properties @@ -1,13 +1,16 @@ -core.intro.text = Drop a folder here or go to File\ufe65Open Folder... to start +core.intro.text = Drop an existing project here or go to File\ufe65New Project to start dialogs.about.title = About {0} -dialogs.import.title = Open Folder dialogs.error.title = Error dialogs.locale.add.error.create = An error occurred while creating the new locale. dialogs.locale.add.error.invalid = The locale you entered is invalid or does already exist. dialogs.locale.add.text = Enter locale (i.e. en_US): -dialogs.locale.add.title = Add Locale ({0}) +dialogs.locale.add.title = Add Locale +dialogs.preferences.editor.title = Preferences +dialogs.preferences.project.title = Project Preferences +dialogs.project.import.title = Import Project +dialogs.project.new.title = New Project dialogs.save.text = You have unsaved changes, do you want to save them? dialogs.save.title = Save Translations dialogs.translation.add.error = The translation key you entered is invalid. @@ -30,9 +33,7 @@ dialogs.version.new = A new version is available: dialogs.version.title = Available Updates dialogs.version.uptodate = You are using the latest version. -menu.edit.add.locale.es6.title = ES6 Format... -menu.edit.add.locale.json.title = JSON Format... -menu.edit.add.locale.title = Add Locale +menu.edit.add.locale.title = Add Locale... menu.edit.add.translation.title = Add Translation... menu.edit.delete.title = Delete Translation menu.edit.duplicate.title = Duplicate Translation... @@ -43,9 +44,15 @@ menu.edit.vk = E menu.file.exit.title = Exit menu.file.exit.vk = E menu.file.folder.title = Open Containing Folder -menu.file.open.title = Open Folder... -menu.file.open.vk = O -menu.file.recent.title = Open Recent Folder +menu.file.project.import.title = Import Project... +menu.file.project.import.vk = I +menu.file.project.new.es6.title = ES6 Format... +menu.file.project.new.json.title = JSON Format... +menu.file.project.new.properties.title = Properties Format... +menu.file.project.new.title = New Project +menu.file.project.new.vk = N +menu.file.recent.clear.title = Clear List +menu.file.recent.title = Open Recent menu.file.recent.vk = C menu.file.reload.title = Reload from Disk menu.file.reload.vk = R @@ -57,7 +64,8 @@ menu.help.about.title = About {0} menu.help.title = Help menu.help.version.title = Check for Updates... menu.help.vk = H -menu.settings.minify.title = Minify Translations on Save +menu.settings.preferences.editor.title = Preferences... +menu.settings.preferences.project.title = Project Preferences... menu.settings.title = Settings menu.settings.vk = S menu.view.collapse.title = Collapse All Translations @@ -65,8 +73,15 @@ menu.view.expand.title = Expand All Translations menu.view.title = View menu.view.vk = V -resources.open.error.multiple = An error occurred while opening translation files. -resources.open.error.single = An error occurred while opening the translation file ''{0}''. +resources.import.empty = No translation files found in ''{0}''. +resources.import.error.multiple = An error occurred while opening translation files. +resources.import.error.single = An error occurred while opening the translation file ''{0}''. resources.write.error.single = An error occurred while writing the translation file ''{0}''. -translations.model.name = Translations +settings.fieldset.general = General +settings.fieldset.newprojects = New Projects +settings.minify.title = Minify translations on save +settings.resourcename.title = Translations filename +settings.version.title = Check for new version on startup + +tree.root.name = Translations diff --git a/src/main/resources/bundles/messages_nl.properties b/src/main/resources/bundles/messages_nl.properties index fa8bda2..2a17c57 100644 --- a/src/main/resources/bundles/messages_nl.properties +++ b/src/main/resources/bundles/messages_nl.properties @@ -1,13 +1,16 @@ -core.intro.text = Sleep een map hierheen of ga naar Bestand\ufe65Map Openen... om te starten +core.intro.text = Sleep een bestaand project hierheen of ga naar Bestand\ufe65Nieuw Project om te starten dialogs.about.title = Over {0} -dialogs.import.title = Map Openen dialogs.error.title = Fout dialogs.locale.add.error.create = Er is iets fout gegaan bij het toevoegen van de nieuwe locale. dialogs.locale.add.error.invalid = De opgegeven locale is niet geldig of bestaat al. dialogs.locale.add.text = Locale (bijv. nl_NL): -dialogs.locale.add.title = Locale Toevoegen ({0}) +dialogs.locale.add.title = Locale Toevoegen +dialogs.preferences.editor.title = Voorkeuren +dialogs.preferences.project.title = Projectvoorkeuren +dialogs.project.import.title = Importeer Project +dialogs.project.new.title = Nieuw Project dialogs.save.text = U heeft nog onopgeslagen wijzigingen, wilt u deze opslaan? dialogs.save.title = Vertalingen Opslaan dialogs.translation.add.error = De opgegeven naam voor de vertaling is niet geldig. @@ -30,9 +33,7 @@ dialogs.version.new = Een nieuwe versie is beschikbaar: dialogs.version.title = Beschikbare Updates dialogs.version.uptodate = Je gebruikt de nieuwste versie. -menu.edit.add.locale.es6.title = ES6 Formaat... -menu.edit.add.locale.json.title = JSON Formaat... -menu.edit.add.locale.title = Locale Toevoegen +menu.edit.add.locale.title = Locale Toevoegen... menu.edit.add.translation.title = Vertaling Toevoegen... menu.edit.delete.title = Vertaling Verwijderen menu.edit.duplicate.title = Vertaling Dupliceren... @@ -43,11 +44,17 @@ menu.edit.vk = W menu.file.exit.title = Sluiten menu.file.exit.vk = S menu.file.folder.title = In Map Weergeven -menu.file.open.title = Map Openen... -menu.file.open.vk = O -menu.file.recent.title = Recente Map Openen +menu.file.project.import.title = Importeer Project... +menu.file.project.import.vk = I +menu.file.project.new.es6.title = ES6 Formaat... +menu.file.project.new.json.title = JSON Formaat... +menu.file.project.new.properties.title = Properties Formaat... +menu.file.project.new.title = Nieuw Project +menu.file.project.new.vk = N +menu.file.recent.clear.title = Lijst Wissen +menu.file.recent.title = Recent Geopend menu.file.recent.vk = R -menu.file.reload.title = Herladen van Schijf +menu.file.reload.title = Herladen vanaf Schijf menu.file.reload.vk = H menu.file.save.title = Opslaan menu.file.save.vk = P @@ -57,7 +64,8 @@ menu.help.about.title = Over {0} menu.help.title = Help menu.help.version.title = Controleer nieuwe Versie... menu.help.vk = H -menu.settings.minify.title = Comprimeer Vertalingen bij Opslaan +menu.settings.preferences.editor.title = Voorkeuren... +menu.settings.preferences.project.title = Projectvoorkeuren... menu.settings.title = Instellingen menu.settings.vk = I menu.view.collapse.title = Alle Vertalingen Invouwen @@ -65,8 +73,15 @@ menu.view.expand.title = Alle Vertalingen Uitvouwen menu.view.title = Beeld menu.view.vk = L -resources.open.error.multiple = Er is iets fout gegaan bij het openen van de vertaalbetanden. -resources.open.error.single = Er is iets fout gegaan bij het openen van het vertaalbestand ''{0}''. +resources.import.empty = Geen vertaalbestanden gevonden in ''{0}''. +resources.import.error.multiple = Er is iets fout gegaan bij het openen van de vertaalbetanden. +resources.import.error.single = Er is iets fout gegaan bij het openen van het vertaalbestand ''{0}''. resources.write.error.single = Er is iets fout gegaan bij het opslaan van het vertaalbestand ''{0}''. -translations.model.name = Vertalingen +settings.fieldset.general = Algemeen +settings.fieldset.newprojects = Nieuwe Projecten +settings.minify.title = Comprimeer vertalingen bij opslaan +settings.resourcename.title = Vertaalbestandsnaam +settings.version.title = Controleer op nieuwe versie bij opstarten + +tree.root.name = Vertalingen diff --git a/src/main/resources/bundles/messages_pt_BR.properties b/src/main/resources/bundles/messages_pt_BR.properties index ce14991..96baa75 100644 --- a/src/main/resources/bundles/messages_pt_BR.properties +++ b/src/main/resources/bundles/messages_pt_BR.properties @@ -1,11 +1,16 @@ +core.intro.text = Solte um projeto existente aqui ou v\u00e1 para Arquivo\ufe65Novo Projeto para iniciar + dialogs.about.title = Sobre {0} -dialogs.import.title = Abrir Pasta dialogs.error.title = Erro dialogs.locale.add.error.create = Ocorreu um erro ao criar o nova localidade. dialogs.locale.add.error.invalid = A localidade digitada \u00e9 inv\u00e1lida ou n\u00e3o existe. dialogs.locale.add.text = Informe uma localidade (Ex. pt_BR): -dialogs.locale.add.title = Incluir localidade ({0}) +dialogs.locale.add.title = Incluir localidade +dialogs.preferences.editor.title = Prefer\u00eancias... +dialogs.preferences.project.title = Prefer\u00eancias do Projeto... +dialogs.project.import.title = Projeto de Importa\u00E7\u00E3o +dialogs.project.new.title = Novo Projeto dialogs.save.text = Voc\u00ea tem modifica\u00e7\u00f5es n\u00e3o salvas, deseja salv\u00e1-las? dialogs.save.title = Salvar Tradu\u00e7\u00f5es dialogs.translation.add.error = A chave de tradu\u00e7\u00e3o inofrmada \u00e9 inv\u00e1lida. @@ -28,9 +33,7 @@ dialogs.version.new = Uma nova vers\u00e3o est\u00e1 dispon\u00edvel: dialogs.version.title = Atualiza\u00e7\u00f5es Dispon\u00edveis dialogs.version.uptodate = Voc\u00ea est\u00e1 atualizado com a vers\u00e3o mais recente. -menu.edit.add.locale.es6.title = Formato ES6... -menu.edit.add.locale.json.title = Formato JSON... -menu.edit.add.locale.title = Incluir Localiza\u00e7\u00e3o +menu.edit.add.locale.title = Incluir Localiza\u00e7\u00e3o... menu.edit.add.translation.title = Incluir Tradu\u00e7\u00e3o... menu.edit.delete.title = Excluir Tradu\u00e7\u00e3o menu.edit.duplicate.title = Duplicar Tradu\u00e7\u00e3o... @@ -41,9 +44,15 @@ menu.edit.vk = E menu.file.exit.title = Sair menu.file.exit.vk = S menu.file.folder.title = Abrir Pasta de Conte\u00fado -menu.file.open.title = Abrir Pasta... -menu.file.open.vk = A -menu.file.recent.title = Abrir Pasta Recente +menu.file.project.import.title = Projeto de Importa\u00e7\u00e3o... +menu.file.project.import.vk = I +menu.file.project.new.es6.title = Formato ES6... +menu.file.project.new.json.title = Formato JSON... +menu.file.project.new.properties.title = Formato Properties... +menu.file.project.new.title = Novo Projeto +menu.file.project.new.vk = N +menu.file.recent.clear.title = Limpar Lista +menu.file.recent.title = Abrir Recente menu.file.recent.vk = E menu.file.reload.title = Recarregar do Disco menu.file.reload.vk = R @@ -53,9 +62,10 @@ menu.file.title = Arquivo menu.file.vk = A menu.help.about.title = Sobre {0} menu.help.title = Ajuda -menu.help.version.title = Verificar novas vers\u00f5es... +menu.help.version.title = Verificar a nova vers\u00e3o... menu.help.vk = J -menu.settings.minify.title = Minificar JSON ao salvar +menu.settings.preferences.editor.title = Prefer\u00eancias... +menu.settings.preferences.project.title = Prefer\u00eancias do Projeto... menu.settings.title = Configura\u00e7\u00f5es menu.settings.vk = C menu.view.collapse.title = Recolher todas as tradu\u00e7\u00f5es @@ -63,8 +73,15 @@ menu.view.expand.title = Expandir todas as tradu\u00e7\u00f5es menu.view.title = Ver menu.view.vk = V -resources.open.error.multiple = Um erro ocorreu enquanto os arquivos de tradu\u00e7\u00f5es eram carregados. -resources.open.error.single = Um erro ocorreu enquanto o arquivo de tradu\u00e7\u00e3o era carregado ''{0}''. +resources.import.empty = Nenhum arquivo de tradu\u00e7\u00e3o encontrado em ''{0}''. +resources.import.error.multiple = Um erro ocorreu enquanto os arquivos de tradu\u00e7\u00f5es eram carregados. +resources.import.error.single = Um erro ocorreu enquanto o arquivo de tradu\u00e7\u00e3o era carregado ''{0}''. resources.write.error.single = Um erro ocorreu enquanto o arquivo de tradu\u00e7\u00e3o era salvo ''{0}''. -translations.model.name = Tradu\u00e7\u00f5es +settings.fieldset.general = Geral +settings.fieldset.newprojects = Novos Projetos +settings.minify.title = Minificar tradu\u00e7\u00f5es ao salvar +settings.resourcename.title = Nome do arquivo de tradu\u00e7\u00e3es +settings.version.title = Verificar a nova vers\u00e3o na inicializa\u00e7\u00e3o + +tree.root.name = Tradu\u00e7\u00f5es diff --git a/src/test/java/com/jvms/i18neditor/ResourceTest.java b/src/test/java/com/jvms/i18neditor/ResourceTest.java index 6942421..04437de 100644 --- a/src/test/java/com/jvms/i18neditor/ResourceTest.java +++ b/src/test/java/com/jvms/i18neditor/ResourceTest.java @@ -8,7 +8,6 @@ import com.google.common.collect.Maps; import com.jvms.i18neditor.Resource; -import com.jvms.i18neditor.Resource.ResourceType; import static org.junit.Assert.*; @@ -20,7 +19,8 @@ public void setup() throws Exception { SortedMap translations = Maps.newTreeMap(); translations.put("a.a", "aa"); translations.put("a.b", "ab"); - resource = new Resource(ResourceType.JSON, null, new Locale("en"), translations); + resource = new Resource(ResourceType.JSON, null, new Locale("en")); + resource.setTranslations(translations); } @Test