diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettings.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettings.java new file mode 100644 index 0000000..1f17de5 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettings.java @@ -0,0 +1,32 @@ +package de.marhali.easyi18n.settings; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.model.FolderStrategyType; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * API to access the project-specific configuration for this plugin. + * @author marhaliu + */ +public interface ProjectSettings { + // Resource Configuration + @Nullable String getLocalesDirectory(); + @NotNull FolderStrategyType getFolderStrategy(); + @NotNull ParserStrategyType getParserStrategy(); + @NotNull String getFilePattern(); + + boolean isSorting(); + + // Editor Configuration + @Nullable String getNamespaceDelimiter(); + @NotNull String getSectionDelimiter(); + @Nullable String getContextDelimiter(); + @Nullable String getPluralDelimiter(); + @Nullable String getDefaultNamespace(); + @NotNull String getPreviewLocale(); + + boolean isNestedKeys(); + boolean isAssistance(); +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponent.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponent.java new file mode 100644 index 0000000..95f38e6 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponent.java @@ -0,0 +1,212 @@ +package de.marhali.easyi18n.settings; + +import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.ui.JBColor; +import com.intellij.ui.TitledSeparator; +import com.intellij.ui.components.*; +import com.intellij.ui.components.fields.ExtendableTextField; +import com.intellij.util.ui.FormBuilder; + +import de.marhali.easyi18n.io.parser.ArrayMapper; +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.settings.presets.Preset; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ResourceBundle; + +/** + * Configuration panel with all possible options for this plugin. + * @author marhali + */ +public class ProjectSettingsComponent extends ProjectSettingsComponentState { + + private final Project project; + private final ResourceBundle bundle; + private final JPanel mainPanel; + + // Data fields are provided by the underlying state class + + public ProjectSettingsComponent(Project project) { + this.project = project; + this.bundle = ResourceBundle.getBundle("messages"); + + this.mainPanel = FormBuilder.createFormBuilder() + .addComponent(new JBLabel(bundle.getString("settings.hint.text"))) + .addComponent(new ActionLink(bundle.getString("settings.hint.action"), + (ActionListener) (var) -> BrowserUtil.browse("https://github.com/marhali/easy-i18n"))) + .addVerticalGap(24) + .addLabeledComponent(bundle.getString("settings.preset.title"), constructPresetField(), 1, false) + .addVerticalGap(12) + .addComponent(new TitledSeparator(bundle.getString("settings.resource.title"))) + .addLabeledComponent(bundle.getString("settings.resource.path.title"), constructLocalesDirectoryField(), 1, false) + .addLabeledComponent(bundle.getString("settings.resource.strategy"), constructFileStrategyPanel(), 1, false) + .addVerticalGap(12) + .addComponent(constructSortingField()) + .addVerticalGap(24) + .addComponent(new TitledSeparator(bundle.getString("settings.editor.title"))) + .addLabeledComponent(bundle.getString("settings.editor.key.title"), constructKeyStrategyPanel(), 1, false) + .addLabeledComponent(bundle.getString("settings.editor.default-namespace.title"), constructDefaultNamespaceField(), 1, false) + .addLabeledComponent(bundle.getString("settings.editor.preview.title"), constructPreviewLocaleField(), 1, false) + .addVerticalGap(12) + .addComponent(constructNestedKeysField()) + .addComponent(constructAssistanceField()) + .addComponentFillVertically(new JPanel(), 0) + .getPanel(); + } + + private JComponent constructPresetField() { + preset = new ComboBox<>(Preset.values()); + preset.setToolTipText(bundle.getString("settings.preset.tooltip")); + preset.setMinimumAndPreferredWidth(196); + preset.addActionListener(e -> setState(preset.getItem().config())); // Listen to selection change + return preset; + } + + private JComponent constructLocalesDirectoryField() { + localesDirectory = new TextFieldWithBrowseButton(); + localesDirectory.setToolTipText(bundle.getString("settings.resource.path.tooltip")); + localesDirectory.addBrowseFolderListener(bundle.getString("settings.resource.path.window"), + bundle.getString("settings.resource.path.tooltip"), project, + new FileChooserDescriptor(false, true, + false, false, false, false)); + + // Listen to value change + localesDirectory.getTextField().getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + validateLocalesDirectory(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + validateLocalesDirectory(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + validateLocalesDirectory(); + } + }); + + validateLocalesDirectory(); + return localesDirectory; + } + + private void validateLocalesDirectory() { + // Paint red border to indicate missing value + localesDirectory.setBorder(localesDirectory.getText().isEmpty() + ? BorderFactory.createLineBorder(JBColor.red) : null); + } + + private JPanel constructFileStrategyPanel() { + JPanel panel = new JBPanel<>(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + + /* folder strategy */ + folderStrategy = new ComboBox<>(bundle.getString("settings.resource.folder.items").split(ArrayMapper.SPLITERATOR_REGEX)); + folderStrategy.setToolTipText(bundle.getString("settings.resource.folder.tooltip")); + folderStrategy.setMinimumAndPreferredWidth(256); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 0; + constraints.gridy = 0; + panel.add(folderStrategy, constraints); + + /* parser strategy */ + parserStrategy = new ComboBox<>(bundle.getString("settings.resource.parser.items").split(ArrayMapper.SPLITERATOR_REGEX)); + parserStrategy.setToolTipText(bundle.getString("settings.resource.parser.tooltip")); + parserStrategy.addItemListener(handleParserChange()); + parserStrategy.setMinimumAndPreferredWidth(128); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 1; + constraints.gridy = 0; + panel.add(parserStrategy, constraints); + + /* file pattern strategy */ + filePattern = new JBTextField(); + filePattern.setToolTipText(bundle.getString("settings.resource.file-pattern.tooltip")); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 2; + constraints.gridy = 0; + constraints.weightx = 1; + panel.add(filePattern, constraints); + + return panel; + } + + private JComponent constructSortingField() { + sorting = new JBCheckBox(bundle.getString("settings.resource.sorting.title")); + sorting.setToolTipText(bundle.getString("settings.resource.sorting.tooltip")); + return sorting; + } + + private JPanel constructKeyStrategyPanel() { + JPanel panel = new JBPanel<>(new FlowLayout(FlowLayout.LEFT)); + + panel.add(new JBLabel(bundle.getString("settings.editor.key.namespace.title"))); + panel.add(namespaceDelimiter = createDelimiterField(bundle.getString("settings.editor.key.namespace.tooltip"))); + panel.add(new JBLabel(bundle.getString("settings.editor.key.section.title"))); + panel.add(sectionDelimiter = createDelimiterField(bundle.getString("settings.editor.key.section.tooltip"))); + panel.add(new JBLabel(bundle.getString("settings.editor.key.node.title"))); + panel.add(contextDelimiter = createDelimiterField(bundle.getString("settings.editor.key.context.tooltip"))); + panel.add(new JBLabel(bundle.getString("settings.editor.key.context.title"))); + panel.add(pluralDelimiter = createDelimiterField(bundle.getString("settings.editor.key.plural.tooltip"))); + panel.add(new JBLabel(bundle.getString("settings.editor.key.plural.title"))); + + return panel; + } + + private JTextField createDelimiterField(String tooltip) { + JBTextField field = new JBTextField(); + field.setHorizontalAlignment(JTextField.CENTER); + field.setToolTipText(tooltip); + return field; + } + + private JComponent constructDefaultNamespaceField() { + defaultNamespace = new ExtendableTextField(20); + defaultNamespace.setToolTipText(bundle.getString("settings.editor.default-namespace.tooltip")); + return defaultNamespace; + } + + private JComponent constructPreviewLocaleField() { + previewLocale = new ExtendableTextField(12); + previewLocale.setToolTipText(bundle.getString("settings.editor.preview.tooltip")); + return previewLocale; + } + + private JComponent constructNestedKeysField() { + nestedKeys = new JBCheckBox(bundle.getString("settings.editor.key.nesting.title")); + nestedKeys.setToolTipText(bundle.getString("settings.editor.key.nesting.tooltip")); + return nestedKeys; + } + + private JComponent constructAssistanceField() { + assistance = new JBCheckBox(bundle.getString("settings.editor.assistance.title")); + assistance.setToolTipText(bundle.getString("settings.editor.assistance.tooltip")); + return assistance; + } + + private ItemListener handleParserChange() { + return e -> { + if(e.getStateChange() == ItemEvent.SELECTED) { + // Automatically suggest file pattern option on parser change + ParserStrategyType newStrategy = ParserStrategyType.fromIndex(parserStrategy.getSelectedIndex()); + filePattern.setText(newStrategy.getExampleFilePattern()); + } + }; + } + + public JPanel getMainPanel() { + return mainPanel; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponentState.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponentState.java new file mode 100644 index 0000000..3091472 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsComponentState.java @@ -0,0 +1,82 @@ +package de.marhali.easyi18n.settings; + +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.model.FolderStrategyType; +import de.marhali.easyi18n.settings.presets.Preset; + +import javax.swing.*; + +/** + * Mandatory for state management for the project settings component. + * @author marhali + */ +public class ProjectSettingsComponentState { + + protected ComboBox preset; + + // Resource Configuration + protected TextFieldWithBrowseButton localesDirectory; + protected ComboBox folderStrategy; + protected ComboBox parserStrategy; + protected JTextField filePattern; + + protected JCheckBox sorting; + + // Editor configuration + protected JTextField namespaceDelimiter; + protected JTextField sectionDelimiter; + protected JTextField contextDelimiter; + protected JTextField pluralDelimiter; + protected JTextField defaultNamespace; + protected JTextField previewLocale; + + protected JCheckBox nestedKeys; + protected JCheckBox assistance; + + protected ProjectSettingsState getState() { + // Every field needs to provide its state + ProjectSettingsState state = new ProjectSettingsState(); + + state.setLocalesDirectory(localesDirectory.getText()); + state.setFolderStrategy(FolderStrategyType.fromIndex(folderStrategy.getSelectedIndex())); + state.setParserStrategy(ParserStrategyType.fromIndex(parserStrategy.getSelectedIndex())); + state.setFilePattern(filePattern.getText()); + + state.setSorting(sorting.isSelected()); + + state.setNamespaceDelimiter(namespaceDelimiter.getText()); + state.setSectionDelimiter(sectionDelimiter.getText()); + state.setContextDelimiter(contextDelimiter.getText()); + state.setPluralDelimiter(pluralDelimiter.getText()); + state.setDefaultNamespace(defaultNamespace.getText()); + state.setPreviewLocale(previewLocale.getText()); + + state.setNestedKeys(nestedKeys.isSelected()); + state.setAssistance(assistance.isSelected()); + + return state; + } + + protected void setState(ProjectSettings state) { + // Update every field with the new state + localesDirectory.setText(state.getLocalesDirectory()); + folderStrategy.setSelectedIndex(state.getFolderStrategy().toIndex()); + parserStrategy.setSelectedIndex((state.getParserStrategy().toIndex())); + filePattern.setText(state.getFilePattern()); + + sorting.setSelected(state.isSorting()); + + namespaceDelimiter.setText(state.getNamespaceDelimiter()); + sectionDelimiter.setText(state.getSectionDelimiter()); + contextDelimiter.setText(state.getContextDelimiter()); + pluralDelimiter.setText(state.getPluralDelimiter()); + defaultNamespace.setText(state.getDefaultNamespace()); + previewLocale.setText(state.getPreviewLocale()); + + nestedKeys.setSelected(state.isNestedKeys()); + assistance.setSelected(state.isAssistance()); + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsConfigurable.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsConfigurable.java new file mode 100644 index 0000000..dcb07b6 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsConfigurable.java @@ -0,0 +1,59 @@ +package de.marhali.easyi18n.settings; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.project.Project; + +import de.marhali.easyi18n.InstanceManager; + +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** + * IDE settings panel for this plugin + * @author marhali + */ +public class ProjectSettingsConfigurable implements Configurable { + + private final Project project; + + private ProjectSettingsComponent component; + + public ProjectSettingsConfigurable(Project project) { + this.project = project; + } + + @Override + public String getDisplayName() { + return "Easy I18n"; + } + + @Override + public @Nullable JComponent createComponent() { + component = new ProjectSettingsComponent(project); + component.setState(ProjectSettingsService.get(project).getState()); + return component.getMainPanel(); + } + + @Override + public boolean isModified() { + ProjectSettingsState originState = ProjectSettingsService.get(project).getState(); + return !originState.equals(component.getState()); + } + + @Override + public void apply() { + ProjectSettingsService.get(project).setState(component.getState()); + InstanceManager.get(project).reload(); + } + + @Override + public void reset() { + component.setState(ProjectSettingsService.get(project).getState()); + } + + @Override + public void disposeUIResources() { + component = null; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsService.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsService.java new file mode 100644 index 0000000..1cc0538 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsService.java @@ -0,0 +1,47 @@ +package de.marhali.easyi18n.settings; + +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.project.Project; + +import org.jetbrains.annotations.NotNull; + +/** + * Persistent storage for project-specific settings. + * @author marhali + */ +@State( + name = "ProjectSettingsService", + storages = @Storage("easy-i18n.xml") +) +public class ProjectSettingsService implements PersistentStateComponent { + + public static @NotNull ProjectSettingsService get(@NotNull Project project) { + return project.getService(ProjectSettingsService.class); + } + + private ProjectSettingsState state; + + public ProjectSettingsService() { + this.state = new ProjectSettingsState(); + } + + /** + * Sets the provided configuration and invalidates the merged state. + * @param state New configuration + */ + protected void setState(@NotNull ProjectSettingsState state) { + this.state = state; + } + + @Override + public @NotNull ProjectSettingsState getState() { + return state; + } + + @Override + public void loadState(@NotNull ProjectSettingsState state) { + this.state = state; + } +} diff --git a/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsState.java b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsState.java new file mode 100644 index 0000000..3ec02df --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/settings/ProjectSettingsState.java @@ -0,0 +1,156 @@ +package de.marhali.easyi18n.settings; + +import de.marhali.easyi18n.io.parser.ParserStrategyType; +import de.marhali.easyi18n.model.FolderStrategyType; +import de.marhali.easyi18n.settings.presets.DefaultPreset; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the project-specific configuration of this plugin. + * @author marhali + */ +public class ProjectSettingsState implements ProjectSettings { + + private static final ProjectSettings defaults = new DefaultPreset(); + + // Resource Configuration + private String localesDirectory; + private FolderStrategyType folderStrategy; + private ParserStrategyType parserStrategy; + private String filePattern; + + private Boolean sorting; + + // Editor configuration + private String namespaceDelimiter; + private String sectionDelimiter; + private String contextDelimiter; + private String pluralDelimiter; + private String defaultNamespace; + private String previewLocale; + + private Boolean nestedKeys; + private Boolean assistance; + + public ProjectSettingsState() {} + + @Override + public @Nullable String getLocalesDirectory() { + return localesDirectory != null ? localesDirectory : defaults.getLocalesDirectory(); + } + + @Override + public @NotNull FolderStrategyType getFolderStrategy() { + return folderStrategy != null ? folderStrategy : defaults.getFolderStrategy(); + } + + @Override + public @NotNull ParserStrategyType getParserStrategy() { + return parserStrategy != null ? parserStrategy : defaults.getParserStrategy(); + } + + @Override + public @NotNull String getFilePattern() { + return filePattern != null ? filePattern : defaults.getFilePattern(); + } + + @Override + public boolean isSorting() { + return sorting != null ? sorting : defaults.isSorting(); + } + + @Override + public @Nullable String getNamespaceDelimiter() { + return namespaceDelimiter != null ? namespaceDelimiter : defaults.getNamespaceDelimiter(); + } + + @Override + public @NotNull String getSectionDelimiter() { + return sectionDelimiter != null ? sectionDelimiter : defaults.getSectionDelimiter(); + } + + @Override + public @Nullable String getContextDelimiter() { + return contextDelimiter != null ? contextDelimiter : defaults.getContextDelimiter(); + } + + @Override + public @Nullable String getPluralDelimiter() { + return pluralDelimiter != null ? pluralDelimiter : defaults.getPluralDelimiter(); + } + + @Nullable + @Override + public String getDefaultNamespace() { + return defaultNamespace; + } + + @Override + public @NotNull String getPreviewLocale() { + return previewLocale != null ? previewLocale : defaults.getPreviewLocale(); + } + + @Override + public boolean isNestedKeys() { + return nestedKeys != null ? nestedKeys : defaults.isNestedKeys(); + } + + @Override + public boolean isAssistance() { + return assistance != null ? assistance : defaults.isAssistance(); + } + + public void setLocalesDirectory(String localesDirectory) { + this.localesDirectory = localesDirectory; + } + + public void setFolderStrategy(FolderStrategyType folderStrategy) { + this.folderStrategy = folderStrategy; + } + + public void setParserStrategy(ParserStrategyType parserStrategy) { + this.parserStrategy = parserStrategy; + } + + public void setFilePattern(String filePattern) { + this.filePattern = filePattern; + } + + public void setSorting(Boolean sorting) { + this.sorting = sorting; + } + + public void setNamespaceDelimiter(String namespaceDelimiter) { + this.namespaceDelimiter = namespaceDelimiter; + } + + public void setSectionDelimiter(String sectionDelimiter) { + this.sectionDelimiter = sectionDelimiter; + } + + public void setContextDelimiter(String contextDelimiter) { + this.contextDelimiter = contextDelimiter; + } + + public void setPluralDelimiter(String pluralDelimiter) { + this.pluralDelimiter = pluralDelimiter; + } + + public void setDefaultNamespace(String defaultNamespace) { + this.defaultNamespace = defaultNamespace; + } + + public void setPreviewLocale(String previewLocale) { + this.previewLocale = previewLocale; + } + + public void setNestedKeys(Boolean nestedKeys) { + this.nestedKeys = nestedKeys; + } + + public void setAssistance(Boolean assistance) { + this.assistance = assistance; + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 4ff888e..a0c3ee7 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -24,21 +24,26 @@ - + - + + + + implementationClass="de.marhali.easyi18n.editor.generic.GenericKeyCompletionContributor"/> + implementationClass="de.marhali.easyi18n.editor.generic.GenericKeyAnnotator"/> + implementation="de.marhali.easyi18n.editor.generic.GenericKeyReferenceContributor"/> - - - + + + \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index ed38b92..2c4620c 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -12,19 +12,44 @@ action.search=Search... action.delete=Delete translation.key=Key translation.locales=Locales -settings.path.title=Locales Directory -settings.path.text=Locales directory -settings.strategy.title=Translation file structure -settings.strategy.folder=Single Directory;Modularized: Locale / Namespace;Modularized: Namespace / Locale -settings.strategy.folder.tooltip=What is the folder structure of your translation files? -settings.strategy.parser=JSON;JSON5;YAML;YML;Properties;ARB -settings.strategy.parser.tooltip=Which file parser should be used to process your translation files? -settings.strategy.file-pattern.tooltip=Defines a wildcard matcher to filter relevant translation files. For example *.json, *.???. -settings.preview=Preview locale -settings.path.prefix=Path prefix -settings.keys.sort=Sort translation keys alphabetically -settings.keys.nested=Escape delimiter character within a section layer. -settings.editor.assistance=I18n key completion, annotation and reference inside editor +# Settings +settings.hint.text=Project-specific configuration for using your translation files. For an easy start, you can use one of the existing presets. +settings.hint.action=Fore more information, see the documentation +settings.preset.title=Preset +settings.preset.tooltip=Choose a configuration template that best fits your project. After that you can make further changes. +# Resource Configuration +settings.resource.title=Resource Configuration +settings.resource.path.window=Locales Directory +settings.resource.path.title=Locales directory +settings.resource.path.tooltip=Define the folder which contains all translation files. For nested folders, use the top folder. +settings.resource.strategy=File structure +settings.resource.folder.items=Single Directory;Modularized: Locale / Namespace;Modularized: Namespace / Locale +settings.resource.folder.tooltip=What is the folder structure of your translation files? +settings.resource.parser.items=JSON;JSON5;YAML;YML;Properties;ARB +settings.resource.parser.tooltip=Which file parser should be used to process your translation files? +settings.resource.file-pattern.tooltip=Defines a wildcard matcher to filter relevant translation files. For example *.json, *.??? or *.*. +settings.resource.sorting.title=Sort translation keys alphabetically +settings.resource.sorting.tooltip=Sorts all translation keys alphabetically. If disabled, the original key-order in the files is kept. +# Editor Configuration +settings.editor.title=Editor Configuration +settings.editor.key.title=Key delimiters +settings.editor.key.namespace.title=[namespace] +settings.editor.key.namespace.tooltip=Sets the separator used between namespace and key path. +settings.editor.key.section.title=[section] +settings.editor.key.section.tooltip=Sets the separator used between section nodes of the key path. +settings.editor.key.node.title=[node] +settings.editor.key.context.title=[context] +settings.editor.key.context.tooltip=Sets the separator used to define context-specific variants of a translation. +settings.editor.key.plural.title=[pluralization] +settings.editor.key.plural.tooltip=Sets the separator used to define different pluralization's of a translation. +settings.editor.default-namespace.title=Default namespace +settings.editor.default-namespace.tooltip=Specifies the namespace to use by default if none is specified in a translation key. The field can be left blank to ignore this feature. +settings.editor.preview.title=Preview locale +settings.editor.preview.tooltip=Defines the language to be displayed in editor previews. +settings.editor.key.nesting.title=Escape section delimiter within a section layer +settings.editor.key.nesting.tooltip=Escapes the section delimiter within a section to properly reconstruct nested key sections. Disable this feature if nested key sections are the exception rather than the rule in your translation file. +settings.editor.assistance.title=Editor code assistance for translations +settings.editor.assistance.tooltip=Activates editor support to reference, auto-complete and folding of existing translations. error.io=An error occurred while processing translation files. \n\ Config: {0} => {1} ({2}) \n\ Path: {3} \n\