From ffa92f4000f626c8cc966b9c47e8051b50297bbe 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 | 59 ++++ .../java/de/vette/idea/neos/Settings.java | 6 + .../java/de/vette/idea/neos/SettingsForm.java | 77 ++++- .../neos/actions/TranslateNodeTypeAction.java | 275 ++++++++++++++++++ .../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/XliffDomElement.java | 11 + .../lang/xliff/XliffDomFileDescription.java | 22 ++ .../idea/neos/lang/xliff/XliffFileType.java | 3 +- src/main/resources/META-INF/plugin.xml | 4 + 13 files changed, 496 insertions(+), 3 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/XliffDomElement.java create mode 100644 src/main/java/de/vette/idea/neos/lang/xliff/XliffDomFileDescription.java 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..86178a0 --- /dev/null +++ b/src/main/java/de/vette/idea/neos/LocaleDialogWrapper.java @@ -0,0 +1,59 @@ +package de.vette.idea.neos; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogPanel; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBPanel; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.ui.FormBuilder; +import com.intellij.util.ui.JBUI; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.beans.EventHandler; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +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("Test", 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..e0e3e57 100644 --- a/src/main/java/de/vette/idea/neos/Settings.java +++ b/src/main/java/de/vette/idea/neos/Settings.java @@ -24,6 +24,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.LinkedList; +import java.util.List; +import java.util.Vector; + @State(name = "NeosPluginSettings") public class Settings implements PersistentStateComponent { @@ -31,6 +35,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..3773acd 100644 --- a/src/main/java/de/vette/idea/neos/SettingsForm.java +++ b/src/main/java/de/vette/idea/neos/SettingsForm.java @@ -20,8 +20,16 @@ import com.intellij.ide.actions.ShowSettingsUtilImpl; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +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 org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; @@ -29,10 +37,14 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.function.Consumer; 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 +78,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 +98,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("Locales"), 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 +139,7 @@ public void apply() { getSettings().pluginEnabled = pluginEnabled.isSelected(); getSettings().excludePackageSymlinks = excludePackageSymlinks.isSelected(); + getSettings().locales = new ArrayList<>(locales.getItems()); } @Override @@ -103,6 +150,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 +162,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..429482f --- /dev/null +++ b/src/main/java/de/vette/idea/neos/actions/TranslateNodeTypeAction.java @@ -0,0 +1,275 @@ +package de.vette.idea.neos.actions; + +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypeRegistry; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.IncorrectOperationException; +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.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.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +public class TranslateNodeTypeAction extends AnAction { + + @Override + public void update(@NotNull AnActionEvent e) { + @Nullable Project project = getEventProject(e); + + if (!Settings.getInstance(project).pluginEnabled || !ActionPlaces.isPopupPlace(e.getPlace())) { + e.getPresentation().setEnabledAndVisible(false); + return; + } + + @Nullable VirtualFile virtualFile = e.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE); + NeosUtil.isNodeTypeDefinition(virtualFile); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + @Nullable VirtualFile virtualFile = e.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE); + @Nullable Project project = getEventProject(e); + + List pairs = getTranslateablePaths(virtualFile, project); + String nodeType = getTranslationKeyParts(pairs.get(0)).get(0); + String[] nodeTypeParts = nodeType.split(":"); + + if (nodeTypeParts.length == 2) { + nodeType = nodeTypeParts[1]; + } + + PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(virtualFile.getParent()); + PsiFile composerManifest = ComposerUtil.getComposerManifest(psiDirectory); + PsiDirectory packageDirectory; + if (composerManifest == null) { + return; + } + + packageDirectory = composerManifest.getContainingDirectory(); + Collection locales = Settings.getInstance(project).locales; + + nodeTypeParts = nodeType.split("\\."); + String fileName = nodeTypeParts[nodeTypeParts.length - 1]; + + String[] finalNodeTypeParts = nodeTypeParts; + + FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate("XLIFF File"); + + WriteCommandAction.runWriteCommandAction(project, () -> { + PsiDirectory dir = createSubdirectory(packageDirectory, "Resources"); + dir = createSubdirectory(dir, "Private"); + PsiDirectory translationsDir = createSubdirectory(dir, "Translations"); + + Properties properties = new Properties(); + properties.setProperty("NEOS_PACKAGE", ""); + + final String sourceLocale = String.valueOf(locales.stream().findFirst()); + for (String locale : locales) { + dir = createSubdirectory(translationsDir, locale); + dir = createSubdirectory(dir, "NodeTypes"); + properties.setProperty("SOURCE_LANGUAGE", sourceLocale); + + if (!locale.equals(sourceLocale)) { + properties.setProperty("TARGET_LANGUAGE", locale); + } + + for (String nodeTypePart : finalNodeTypeParts) { + if (nodeTypePart == fileName) { + continue; + } + + dir = createSubdirectory(dir, nodeTypePart); + } + + try { + String fileNameWExt = fileName.concat(".xlf"); + String content = template.getText(properties); + if (FileTypeManager.getInstance().isFileIgnored(fileNameWExt)) { + throw new IncorrectOperationException("This filename is ignored (Settings | Editor | FileDomElement Types | Ignore files and folders)"); + } + + try { + dir.checkCreateFile(fileNameWExt); + } catch (IncorrectOperationException exception) { + + } + + FileType type = FileTypeRegistry.getInstance().getFileTypeByFileName(fileNameWExt); + PsiFile psiFile = psiFile = dir.findFile(fileNameWExt); + + if (psiFile == null) { + psiFile = PsiFileFactory.getInstance(project).createFileFromText(fileNameWExt, type, content); + + try { + psiFile = (PsiFile) dir.add(psiFile); + } catch (IncorrectOperationException exception) {} + } + + XmlFile xmlFile = (XmlFile) psiFile; + + XmlTag fileTag = xmlFile.getRootTag().findFirstSubTag("file"); + XmlTag bodyTag = fileTag.findFirstSubTag("body"); + + Set existingIds = getExistingIds(bodyTag); + HashMap nodeTypeIds = new HashMap<>(); + + 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); + } + + Set removedIds = new HashSet<>(existingIds); + removedIds.removeAll(nodeTypeIds.keySet()); + + DomManager manager = DomManager.getDomManager(project); + XliffDomElement root = manager.getFileElement(xmlFile, XliffDomElement.class).getRootElement(); + + root.getFiles().forEach(file -> { + file.getBody().getTransUnits().forEach(transUnit -> { + if (removedIds.contains(transUnit.getId().getValue())) { + transUnit.getXmlElement().delete(); + } + }); + }); + + nodeTypeIds.forEach((id, yamlKeyValue) -> { + if (existingIds.contains(id)) { + return; + } + + String sourceValue = yamlKeyValue.getValueText(); + if (sourceValue.equals("i18n")) { + sourceValue = ""; + } + + XmlTag transUnit = bodyTag.createChildTag("trans-unit", null, null, false); + transUnit.setAttribute("id", id); + XmlTag source = transUnit.createChildTag("source", null, sourceValue, false); + transUnit.addSubTag(source, false); + + YAMLKeyValue newKeyValue = YAMLElementGenerator.getInstance(project).createYamlKeyValue(yamlKeyValue.getKeyText(), "i18n"); + yamlKeyValue.replace(newKeyValue); + + if (!locale.equals(sourceLocale)) { + XmlTag target = transUnit.createChildTag("target", null, "", false); + transUnit.addSubTag(target, false); + } + + bodyTag.addSubTag(transUnit, false); + }); + + if (template.isReformatCode()) { + CodeStyleManager.getInstance(project).scheduleReformatWhenSettingsComputed(psiFile); + } + + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + }); + } + + private static Set getExistingIds(XmlTag body) { + Set ids = new HashSet<>(); + for (XmlTag subTag : body.findSubTags("trans-unit")) { + ids.add(subTag.getAttribute("id").getValue()); + } + + return ids; + } + + private static PsiDirectory createSubdirectory(PsiDirectory directory, String name) { + PsiDirectory subDirectory = directory.findSubdirectory(name); + + if (subDirectory != null) { + return subDirectory; + } + + try { + subDirectory = directory.createSubdirectory(name); + } catch (IncorrectOperationException exception) { + System.out.println(exception.getMessage()); + } + + return subDirectory; + } + + private static List getTranslateablePaths(@Nullable VirtualFile virtualFile, @Nullable 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; + } + + if (key.equals("label")) { + YAMLKeyValue keyValue = (YAMLKeyValue) pair.getParent().getParent(); + if (keyValue.getKeyText().equals("ui")) { + 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; + } + + public void ensureParentDirectoriesExist(String pathStr) { + Path path = Paths.get(pathStr); + Path parentDir = path.getParent(); + if (parentDir != null && !Files.exists(parentDir)) { + try { + Files.createDirectories(parentDir); + } catch (IOException e) {} + } + } +} 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/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..7d78d4d --- /dev/null +++ b/src/main/java/de/vette/idea/neos/lang/xliff/XliffDomFileDescription.java @@ -0,0 +1,22 @@ +package de.vette.idea.neos.lang.xliff; + +import com.intellij.openapi.module.Module; +import com.intellij.psi.xml.XmlFile; +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.DomFileDescription; +import com.intellij.util.xml.GenericAttributeValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +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..7049379 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,5 +1,6 @@ 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; @@ -13,7 +14,7 @@ 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(); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 355fc49..1106775 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -46,6 +46,7 @@ + @@ -164,5 +165,8 @@ + + +