From 04e23ff21d15cbfe19ce0b6eb7734c57562368ae Mon Sep 17 00:00:00 2001 From: Christian Vette <6884391+cvette@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:23:19 +0200 Subject: [PATCH] feat: implement node type translation action #47 --- .../vette/idea/neos/LocaleDialogWrapper.java | 50 +++ .../java/de/vette/idea/neos/Settings.java | 5 + .../java/de/vette/idea/neos/SettingsForm.java | 76 +++- .../neos/actions/TranslateNodeTypeAction.java | 393 ++++++++++++++++++ .../idea/neos/lang/xliff/BodyDomElement.java | 9 + .../idea/neos/lang/xliff/FileDomElement.java | 7 + .../neos/lang/xliff/SourceDomElement.java | 7 + .../neos/lang/xliff/TargetDomElement.java | 7 + .../neos/lang/xliff/TransUnitDomElement.java | 12 + .../idea/neos/lang/xliff/XliffBundle.java | 23 + .../idea/neos/lang/xliff/XliffDomElement.java | 11 + .../lang/xliff/XliffDomFileDescription.java | 18 + .../idea/neos/lang/xliff/XliffFileType.java | 15 +- src/main/resources/META-INF/plugin.xml | 9 +- .../fileTemplates/internal/XLIFF File.xlf.ft | 2 +- .../resources/messages/XliffBundle.properties | 2 + 16 files changed, 628 insertions(+), 18 deletions(-) create mode 100644 src/main/java/de/vette/idea/neos/LocaleDialogWrapper.java create mode 100644 src/main/java/de/vette/idea/neos/actions/TranslateNodeTypeAction.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/BodyDomElement.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/FileDomElement.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/SourceDomElement.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/TargetDomElement.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/TransUnitDomElement.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/XliffBundle.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/XliffDomElement.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/XliffDomFileDescription.java create mode 100644 src/main/resources/messages/XliffBundle.properties diff --git a/src/main/java/de/vette/idea/neos/LocaleDialogWrapper.java b/src/main/java/de/vette/idea/neos/LocaleDialogWrapper.java new file mode 100644 index 0000000..0ad0491 --- /dev/null +++ b/src/main/java/de/vette/idea/neos/LocaleDialogWrapper.java @@ -0,0 +1,50 @@ +package de.vette.idea.neos; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.ui.FormBuilder; +import de.vette.idea.neos.lang.xliff.XliffBundle; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.ResourceBundle; +import java.util.regex.Pattern; + +public class LocaleDialogWrapper extends DialogWrapper { + JBTextField textField = new JBTextField(); + + Pattern pattern = Pattern.compile("^[A-Za-z]{2,4}([_-][A-Za-z]{4})?([_-]([A-Za-z]{2}|[0-9]{3}))?$"); + + public LocaleDialogWrapper(Project project, String title) { + super(project); + setTitle(title); + init(); + } + + @Override + protected @Nullable JComponent createCenterPanel() { + FormBuilder formBuilder = FormBuilder.createFormBuilder(); + formBuilder.addLabeledComponent("Locale", this.textField); + return formBuilder.getPanel(); + } + + public String getText() { + return this.textField.getText().trim(); + } + + @Override + protected @Nullable ValidationInfo doValidate() { + if (!pattern.matcher(this.getText()).matches()) { + return new ValidationInfo(XliffBundle.message("settings.dialog.validation.error"), this.textField); + } + + return null; + } + + @Override + public @Nullable JComponent getPreferredFocusedComponent() { + return this.textField; + } +} diff --git a/src/main/java/de/vette/idea/neos/Settings.java b/src/main/java/de/vette/idea/neos/Settings.java index 50b954f..6e5df2b 100644 --- a/src/main/java/de/vette/idea/neos/Settings.java +++ b/src/main/java/de/vette/idea/neos/Settings.java @@ -24,6 +24,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.LinkedList; +import java.util.List; + @State(name = "NeosPluginSettings") public class Settings implements PersistentStateComponent { @@ -31,6 +34,8 @@ public class Settings implements PersistentStateComponent { public boolean dismissEnableNotification = false; public boolean excludePackageSymlinks = false; + public List locales = new LinkedList<>(); + public static Settings getInstance(@NotNull Project project) { return project.getService(Settings.class); } diff --git a/src/main/java/de/vette/idea/neos/SettingsForm.java b/src/main/java/de/vette/idea/neos/SettingsForm.java index 2012bf8..2126b3e 100644 --- a/src/main/java/de/vette/idea/neos/SettingsForm.java +++ b/src/main/java/de/vette/idea/neos/SettingsForm.java @@ -20,19 +20,30 @@ import com.intellij.ide.actions.ShowSettingsUtilImpl; import com.intellij.openapi.project.Project; +import com.intellij.ui.AnActionButton; +import com.intellij.ui.AnActionButtonRunnable; +import com.intellij.ui.CollectionListModel; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBList; import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; +import com.intellij.uiDesigner.core.Spacer; import com.jetbrains.php.frameworks.PhpFrameworkConfigurable; +import de.vette.idea.neos.lang.xliff.XliffBundle; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; public class SettingsForm implements PhpFrameworkConfigurable { private JCheckBox pluginEnabled; private JCheckBox excludePackageSymlinks; + private CollectionListModel locales; private final Project project; public SettingsForm(@NotNull final Project project) { @@ -66,8 +77,17 @@ public String getHelpTopic() { public JComponent createComponent() { pluginEnabled = new JCheckBox("Enable plugin for this project"); excludePackageSymlinks = new JCheckBox("Exclude symlinked packages"); + locales = new CollectionListModel<>(); - GridLayoutManager layout = new GridLayoutManager(2,1); + JBList localeList = new JBList(); + localeList.setModel(this.locales); + + ToolbarDecorator editableList = ToolbarDecorator.createDecorator(localeList); + editableList.disableUpDownActions(); + editableList.setPreferredSize(new Dimension(150, 100)); + editableList.setAddAction(new AddAction(this.locales)); + + GridLayoutManager layout = new GridLayoutManager(5,1); GridConstraints c = new GridConstraints(); c.setAnchor(GridConstraints.ANCHOR_NORTHWEST); c.setRow(0); @@ -77,12 +97,37 @@ public JComponent createComponent() { c.setRow(1); panel1.add(excludePackageSymlinks, c); + + c.setRow(2); + panel1.add(new Spacer(), c); + + c.setRow(3); + panel1.add(new JBLabel(XliffBundle.message("settings.locales.label")), c); + + c.setRow(4); + panel1.add(editableList.createPanel(), c); + return panel1; } @Override public boolean isModified() { - return !pluginEnabled.isSelected() == getSettings().pluginEnabled || !excludePackageSymlinks.isSelected() == getSettings().excludePackageSymlinks; + int i = 0; + + if (getSettings().locales.size() != locales.getSize()) { + return true; + } + + for (String item : locales.getItems()) { + if (!item.equals(getSettings().locales.get(i))) { + return true; + } + + i++; + } + + return !pluginEnabled.isSelected() == getSettings().pluginEnabled + || !excludePackageSymlinks.isSelected() == getSettings().excludePackageSymlinks; } @Override @@ -93,6 +138,7 @@ public void apply() { getSettings().pluginEnabled = pluginEnabled.isSelected(); getSettings().excludePackageSymlinks = excludePackageSymlinks.isSelected(); + getSettings().locales = new ArrayList<>(locales.getItems()); } @Override @@ -103,6 +149,9 @@ public void reset() { private void updateUIFromSettings() { pluginEnabled.setSelected(getSettings().pluginEnabled); excludePackageSymlinks.setSelected(getSettings().excludePackageSymlinks); + getSettings().locales.forEach(s -> { + locales.add(s); + }); } private Settings getSettings() { @@ -112,4 +161,27 @@ private Settings getSettings() { public static void show(@NotNull Project project) { ShowSettingsUtilImpl.showSettingsDialog(project, "Neos.SettingsForm", null); } + + + class AddAction implements AnActionButtonRunnable { + + CollectionListModel list; + + public AddAction(CollectionListModel list) { + this.list = list; + } + + @Override + public void run(AnActionButton anActionButton) { + LocaleDialogWrapper dialog = new LocaleDialogWrapper(project, "Add Locale"); + if (dialog.showAndGet()) { + String text = dialog.getText(); + if (text.isEmpty()) { + return; + } + + this.list.add(text); + } + } + } } diff --git a/src/main/java/de/vette/idea/neos/actions/TranslateNodeTypeAction.java b/src/main/java/de/vette/idea/neos/actions/TranslateNodeTypeAction.java new file mode 100644 index 0000000..a305aab --- /dev/null +++ b/src/main/java/de/vette/idea/neos/actions/TranslateNodeTypeAction.java @@ -0,0 +1,393 @@ +package de.vette.idea.neos.actions; + +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.json.psi.JsonFile; +import com.intellij.notification.*; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypeRegistry; +import com.intellij.openapi.options.ShowSettingsUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomManager; +import de.vette.idea.neos.Settings; +import de.vette.idea.neos.lang.xliff.XliffDomElement; +import de.vette.idea.neos.lang.xliff.XliffFileType; +import de.vette.idea.neos.util.ComposerUtil; +import de.vette.idea.neos.util.NeosUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.YAMLElementGenerator; +import org.jetbrains.yaml.psi.*; + +import java.io.IOException; +import java.util.*; + +public class TranslateNodeTypeAction extends AnAction { + + @Override + public void update(@NotNull AnActionEvent e) { + @Nullable Project project = getEventProject(e); + @Nullable VirtualFile virtualFile = e.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE); + + if (project == null || virtualFile == null) { + e.getPresentation().setEnabledAndVisible(false); + return; + } + + if (!Settings.getInstance(project).pluginEnabled || !ActionPlaces.isPopupPlace(e.getPlace())) { + e.getPresentation().setEnabledAndVisible(false); + return; + } + + if (!NeosUtil.isNodeTypeDefinition(virtualFile)) { + e.getPresentation().setEnabledAndVisible(false); + } + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + @Nullable VirtualFile virtualFile = e.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE); + @Nullable Project project = getEventProject(e); + + if (project == null || virtualFile == null) { + return; + } + + List pairs = getTranslateablePaths(virtualFile, project); + String nodeType = extractNodeType(pairs); + + PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(virtualFile.getParent()); + JsonFile composerManifest = ComposerUtil.getComposerManifest(psiDirectory); + PsiDirectory packageDirectory; + if (composerManifest == null) { + return; + } + + String packageName = ComposerUtil.getPackageKey(composerManifest); + packageDirectory = composerManifest.getContainingDirectory(); + Collection locales = Settings.getInstance(project).locales; + + if (locales.isEmpty()) { + showNotification(project); + return; + } + + final Optional firstLocaleOpt = locales.stream().findFirst(); + if (firstLocaleOpt.isEmpty()) { + return; + } + + final String sourceLocale = firstLocaleOpt.get(); + + Properties properties = new Properties(); + properties.setProperty("NEOS_PACKAGE_NAME", packageName); + properties.setProperty("SOURCE_LANGUAGE", sourceLocale); + + HashMap yamlPairsByTransId = extractNodeTypeTranslationIds(pairs); + + WriteCommandAction.runWriteCommandAction(project, () -> { + for (String locale : locales) { + PsiDirectory translationDir = createTranslationDirectories(packageDirectory, locale, nodeType); + processTranslationFile(project, translationDir, locale, sourceLocale, yamlPairsByTransId, properties, nodeType); + } + + // replace text values in node type defintion by "i18n" + yamlPairsByTransId.forEach((transId, yamlKeyValue) -> { + if (!yamlKeyValue.getValueText().equals("i18n")) { + YAMLKeyValue newKeyValue = YAMLElementGenerator.getInstance(project).createYamlKeyValue(yamlKeyValue.getKeyText(), "i18n"); + yamlKeyValue.replace(newKeyValue); + } + }); + }); + } + + private static void showNotification(@NotNull Project project) { + NotificationGroupManager.getInstance() + .getNotificationGroup("Neos") + .createNotification("You don't have any locales configured", NotificationType.ERROR) + .addAction(new NotificationAction("Configure locales") { + @Override + public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) { + ShowSettingsUtil.getInstance().showSettingsDialog(project, "Neos"); + + } + }).notify(project); + } + + private void processTranslationFile( + Project project, + PsiDirectory dir, + String locale, + String sourceLocale, + Map yamlPairsByTransId, + Properties properties, + String nodeType + ) { + String fileName = getFileName(nodeType); + String[] nodeTypeParts = nodeType.split("\\."); + for (String nodeTypePart : nodeTypeParts) { + if (nodeTypePart.equals(fileName)) { + continue; + } + + dir = createSubdirectory(dir, nodeTypePart); + } + + properties.setProperty("TARGET_LANGUAGE", ""); + if (!locale.equals(sourceLocale)) { + properties.setProperty("TARGET_LANGUAGE", locale); + } + + try { + fileName = fileName.concat(XliffFileType.DOT_DEFAULT_EXTENSION); + createOrUpdateFileInDirectory(project, dir, fileName, properties); + + Set existingIds = getExistingIdsForFile(dir, fileName); + updateTranslationFileContent(project, dir, fileName, existingIds, yamlPairsByTransId, locale, sourceLocale); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private String getFileName(String nodeType) { + String[] nodeTypeParts = nodeType.split("\\."); + return nodeTypeParts[nodeTypeParts.length - 1]; + } + + private String extractNodeType(List pairs) { + String nodeType = getTranslationKeyParts(pairs.get(0)).get(0); + String[] nodeTypeParts = nodeType.split(":"); + return (nodeTypeParts.length == 2) ? nodeTypeParts[1] : nodeType; + } + + private PsiDirectory createTranslationDirectories(PsiDirectory baseDir, String locale, String nodeType) throws IncorrectOperationException { + PsiDirectory dir = createSubdirectory(baseDir, "Resources"); + dir = createSubdirectory(dir, "Private"); + PsiDirectory translationsDir = createSubdirectory(dir, "Translations"); + dir = createSubdirectory(translationsDir, locale); + dir = createSubdirectory(dir, "NodeTypes"); + + String[] nodeTypeParts = nodeType.split("\\."); + for (int i = 0; i < nodeTypeParts.length - 1; i++) { + dir = createSubdirectory(dir, nodeTypeParts[i]); + } + return dir; + } + + private Set getExistingIdsForFile(PsiDirectory dir, String fileName) { + XmlFile xmlFile = (XmlFile) dir.findFile(fileName); + if (xmlFile == null) { + return Collections.emptySet(); + } + + XmlTag rootTag = xmlFile.getRootTag(); + if (rootTag == null) { + return Collections.emptySet(); + } + + XmlTag fileTag = rootTag.findFirstSubTag("file"); + if (fileTag == null) { + return Collections.emptySet(); + } + + XmlTag bodyTag = fileTag.findFirstSubTag("body"); + if (bodyTag == null) { + return Collections.emptySet(); + } + + return getExistingIds(bodyTag); + } + + private LinkedHashMap extractNodeTypeTranslationIds(List pairs) { + LinkedHashMap nodeTypeIds = new LinkedHashMap<>(); + for (YAMLKeyValue pair : pairs) { + List path = getTranslationKeyParts(pair); + path = path.subList(1, path.size()); // removes the node type name + String id = String.join(".", path); + nodeTypeIds.put(id, pair); + } + return nodeTypeIds; + } + + private static Set getExistingIds(XmlTag body) { + Set ids = new HashSet<>(); + for (XmlTag subTag : body.findSubTags("trans-unit")) { + XmlAttribute attribute = subTag.getAttribute("id"); + if (attribute == null) { + continue; + } + + ids.add(attribute.getValue()); + } + + return ids; + } + + private static PsiDirectory createSubdirectory(PsiDirectory directory, String name) throws IncorrectOperationException { + PsiDirectory subDirectory = directory.findSubdirectory(name); + + if (subDirectory != null) { + return subDirectory; + } + + subDirectory = directory.createSubdirectory(name); + return subDirectory; + } + + private static List getTranslateablePaths(VirtualFile virtualFile, Project project) { + @NotNull PsiManager psiManager = PsiManager.getInstance(project); + PsiFile psiFile = psiManager.findFile(virtualFile); + + @NotNull Collection yamlKeyValuePairs = PsiTreeUtil.findChildrenOfType(psiFile, YAMLKeyValue.class); + + List pathsToTranslate = new Vector<>(); + for (YAMLKeyValue pair : yamlKeyValuePairs) { + String value = pair.getValueText(); + String key = pair.getKeyText(); + + if (value.equals("i18n")) { + pathsToTranslate.add(pair); + continue; + } + + // *.ui.label + if (key.equals("label")) { + YAMLKeyValue keyValue = (YAMLKeyValue) pair.getParent().getParent(); + if (keyValue.getKeyText().equals("ui")) { + pathsToTranslate.add(pair); + } + continue; + } + + // *.help.message + if (key.equals("message")) { + YAMLKeyValue keyValue = (YAMLKeyValue) pair.getParent().getParent(); + if (keyValue.getKeyText().equals("help")) { + pathsToTranslate.add(pair); + } + continue; + } + + // *.editorOptions.placeholder + if (key.equals("placeholder")) { + YAMLKeyValue keyValue = (YAMLKeyValue) pair.getParent().getParent(); + if (keyValue.getKeyText().equals("editorOptions")) { + pathsToTranslate.add(pair); + } + } + } + + return pathsToTranslate; + } + + private static List getTranslationKeyParts(YAMLKeyValue pair) { + PsiElement parent = pair.getParent(); + List pathParts = new Vector<>(); + pathParts.add(pair.getKeyText()); + + while (!(parent == null || parent instanceof YAMLDocument || parent instanceof YAMLFile)) { + if (parent instanceof YAMLKeyValue + && !(parent.getParent() instanceof YAMLDocument || parent.getParent() instanceof YAMLFile)) { + String currentKey = ((YAMLKeyValue) parent).getKeyText(); + pathParts.add(currentKey); + } + + parent = parent.getParent(); + } + + Collections.reverse(pathParts); + return pathParts; + } + + private void createOrUpdateFileInDirectory(Project project, PsiDirectory dir, String fileName, Properties properties) throws IOException { + FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate("XLIFF File"); + String content = template.getText(properties); + if (FileTypeManager.getInstance().isFileIgnored(fileName)) { + throw new IncorrectOperationException("This filename is ignored (Settings | Editor | FileDomElement Types | Ignore files and folders)"); + } + PsiFile psiFile = dir.findFile(fileName); + if (psiFile == null) { + psiFile = PsiFileFactory.getInstance(project).createFileFromText(fileName, FileTypeRegistry.getInstance().getFileTypeByFileName(fileName), content); + dir.add(psiFile); + } + } + + private void updateTranslationFileContent(Project project, PsiDirectory dir, String fileName, Set existingIds, Map yamlPairsByTransId, String locale, String sourceLocale) { + XmlFile xmlFile = (XmlFile) dir.findFile(fileName); + if (xmlFile == null) { + return; + } + + XmlTag rootTag = xmlFile.getRootTag(); + if (rootTag == null) { + return; + } + + XmlTag fileTag = rootTag.findFirstSubTag("file"); + if (fileTag == null) { + return; + } + + XmlTag bodyTag = fileTag.findFirstSubTag("body"); + + Set removedIds = new HashSet<>(existingIds); + removedIds.removeAll(yamlPairsByTransId.keySet()); + + DomManager manager = DomManager.getDomManager(project); + DomFileElement fileElement = manager.getFileElement(xmlFile, XliffDomElement.class); + + if (fileElement == null) { + return; + } + + XliffDomElement root = fileElement.getRootElement(); + removeObsoleteTranslations(root, removedIds); + addNewTranslations(bodyTag, yamlPairsByTransId, existingIds, sourceLocale, locale); + } + + private void removeObsoleteTranslations(XliffDomElement root, Set removedIds) { + root.getFiles().forEach(file -> file.getBody().getTransUnits().forEach(transUnit -> { + if (removedIds.contains(transUnit.getId().getValue())) { + if (transUnit.getXmlElement() != null) { + transUnit.getXmlElement().delete(); + } + } + })); + } + + private void addNewTranslations(XmlTag bodyTag, Map yamlPairsByTransId, Set existingIds, String sourceLocale, String locale) { + yamlPairsByTransId.forEach((id, yamlKeyValue) -> { + if (existingIds.contains(id)) { + return; + } + + XmlTag transUnit = bodyTag.createChildTag("trans-unit", null, null, false); + transUnit.setAttribute("id", id); + + String sourceValue = ""; + if (!yamlKeyValue.getValueText().equals("i18n")) { + sourceValue = yamlKeyValue.getValueText(); + } + + XmlTag sourceTag = transUnit.createChildTag("source", null, sourceValue, false); + transUnit.addSubTag(sourceTag, false); + + if (!locale.equals(sourceLocale)) { + XmlTag target = transUnit.createChildTag("target", null, "", false); + transUnit.addSubTag(target, false); + } + + bodyTag.addSubTag(transUnit, false); + }); + } +} diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/BodyDomElement.java b/src/main/java/de/vette/idea/neos/lang/xliff/BodyDomElement.java new file mode 100644 index 0000000..01a6b3f --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/BodyDomElement.java @@ -0,0 +1,9 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.util.xml.DomElement; + +import java.util.List; + +public interface BodyDomElement extends DomElement { + List getTransUnits(); +} diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/FileDomElement.java b/src/main/java/de/vette/idea/neos/lang/xliff/FileDomElement.java new file mode 100644 index 0000000..d4364da --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/FileDomElement.java @@ -0,0 +1,7 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.util.xml.DomElement; + +public interface FileDomElement extends DomElement { + BodyDomElement getBody(); +} \ No newline at end of file diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/SourceDomElement.java b/src/main/java/de/vette/idea/neos/lang/xliff/SourceDomElement.java new file mode 100644 index 0000000..823ff3a --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/SourceDomElement.java @@ -0,0 +1,7 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.util.xml.DomElement; + +public interface SourceDomElement extends DomElement { + String getValue(); +} diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/TargetDomElement.java b/src/main/java/de/vette/idea/neos/lang/xliff/TargetDomElement.java new file mode 100644 index 0000000..74f030e --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/TargetDomElement.java @@ -0,0 +1,7 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.util.xml.DomElement; + +public interface TargetDomElement extends DomElement { + String getValue(); +} diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/TransUnitDomElement.java b/src/main/java/de/vette/idea/neos/lang/xliff/TransUnitDomElement.java new file mode 100644 index 0000000..d9fb9bd --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/TransUnitDomElement.java @@ -0,0 +1,12 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.GenericAttributeValue; + +public interface TransUnitDomElement extends DomElement { + SourceDomElement getSource(); + + TargetDomElement getTarget(); + + GenericAttributeValue getId(); +} diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/XliffBundle.java b/src/main/java/de/vette/idea/neos/lang/xliff/XliffBundle.java new file mode 100644 index 0000000..a7a69fb --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/XliffBundle.java @@ -0,0 +1,23 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.DynamicBundle; +import de.vette.idea.neos.lang.fusion.FusionBundle; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.PropertyKey; + +public final class XliffBundle extends DynamicBundle { + public static final String BUNDLE = "messages.XliffBundle"; + + private static final XliffBundle INSTANCE = new XliffBundle(); + + + protected XliffBundle() { + super(BUNDLE); + } + + @NotNull + public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { + return INSTANCE.getMessage(key, params); + } +} diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/XliffDomElement.java b/src/main/java/de/vette/idea/neos/lang/xliff/XliffDomElement.java new file mode 100644 index 0000000..aa8e5be --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/XliffDomElement.java @@ -0,0 +1,11 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.util.xml.DomElement; + +import java.util.List; + +public interface XliffDomElement extends DomElement { + List getFiles(); +} + + diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/XliffDomFileDescription.java b/src/main/java/de/vette/idea/neos/lang/xliff/XliffDomFileDescription.java new file mode 100644 index 0000000..acf114a --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/XliffDomFileDescription.java @@ -0,0 +1,18 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.openapi.module.Module; +import com.intellij.psi.xml.XmlFile; +import com.intellij.util.xml.DomFileDescription; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class XliffDomFileDescription extends DomFileDescription { + public XliffDomFileDescription() { + super(XliffDomElement.class, "xliff"); + } + + @Override + public boolean isMyFile(@NotNull XmlFile file, @Nullable Module module) { + return file.getFileType() == XliffFileType.INSTANCE; + } +} diff --git a/src/main/java/de/vette/idea/neos/lang/xliff/XliffFileType.java b/src/main/java/de/vette/idea/neos/lang/xliff/XliffFileType.java index 609186f..c5ddb40 100644 --- a/src/main/java/de/vette/idea/neos/lang/xliff/XliffFileType.java +++ b/src/main/java/de/vette/idea/neos/lang/xliff/XliffFileType.java @@ -1,19 +1,14 @@ package de.vette.idea.neos.lang.xliff; +import com.intellij.ide.highlighter.DomSupportEnabled; import com.intellij.ide.highlighter.XmlLikeFileType; -import com.intellij.lang.Language; -import com.intellij.lang.html.HTMLLanguage; -import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.util.NlsContexts; -import com.intellij.openapi.util.NlsSafe; -import de.vette.idea.neos.NeosIcons; -import de.vette.idea.neos.lang.eel.EelFileType; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import javax.swing.*; -public class XliffFileType extends XmlLikeFileType { +public class XliffFileType extends XmlLikeFileType implements DomSupportEnabled { @NonNls public static final String DOT_DEFAULT_EXTENSION = ".xlf"; public static final XliffFileType INSTANCE = new XliffFileType(); @@ -22,10 +17,6 @@ private XliffFileType() { super(XliffLanguage.INSTANCE); } - protected XliffFileType(Language language) { - super(language); - } - @Override public @NonNls @NotNull String getName() { return "Neos XLIFF"; @@ -37,7 +28,7 @@ protected XliffFileType(Language language) { } @Override - public @NlsSafe @NotNull String getDefaultExtension() { + public @NotNull String getDefaultExtension() { return "xlf"; } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 355fc49..45653b2 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -34,18 +34,17 @@ - - - + + @@ -164,5 +163,9 @@ + + + + diff --git a/src/main/resources/fileTemplates/internal/XLIFF File.xlf.ft b/src/main/resources/fileTemplates/internal/XLIFF File.xlf.ft index b7f4269..c1b58fe 100644 --- a/src/main/resources/fileTemplates/internal/XLIFF File.xlf.ft +++ b/src/main/resources/fileTemplates/internal/XLIFF File.xlf.ft @@ -1,6 +1,6 @@ - + diff --git a/src/main/resources/messages/XliffBundle.properties b/src/main/resources/messages/XliffBundle.properties new file mode 100644 index 0000000..eadb43f --- /dev/null +++ b/src/main/resources/messages/XliffBundle.properties @@ -0,0 +1,2 @@ +settings.locales.label=Locales (first item in list is used as the source language) +settings.dialog.validation.error=Please enter a valid locale identifier as specified by RFC 4646 \ No newline at end of file