diff --git a/CHANGELOG.md b/CHANGELOG.md index 41298bc..b934305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Added - Key delimiters (namespace / section) can be configured - Extract translation intention +- Full language support for Java, Kotlin, JavaScript / TypeScript, Vue and PHP - Individual icon for tool-window and lookup items - Dedicated configuration file (easy-i18n.xml) inside .idea folder diff --git a/gradle.properties b/gradle.properties index b630b41..6c5ffae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ platformVersion = 2021.3 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 -platformPlugins = org.jetbrains.kotlin, JavaScriptLanguage, org.jetbrains.plugins.vue:213.5744.223 +platformPlugins = org.jetbrains.kotlin, JavaScriptLanguage, org.jetbrains.plugins.vue:213.5744.223, com.jetbrains.php:213.5744.279 # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 javaVersion = 11 diff --git a/src/main/java/de/marhali/easyi18n/assistance/completion/JavaCompletionContributor.java b/src/main/java/de/marhali/easyi18n/assistance/completion/JavaCompletionContributor.java index 8ebc10f..3d2b705 100644 --- a/src/main/java/de/marhali/easyi18n/assistance/completion/JavaCompletionContributor.java +++ b/src/main/java/de/marhali/easyi18n/assistance/completion/JavaCompletionContributor.java @@ -3,14 +3,15 @@ package de.marhali.easyi18n.assistance.completion; import com.intellij.codeInsight.completion.CompletionContributor; import com.intellij.codeInsight.completion.CompletionType; import com.intellij.patterns.PlatformPatterns; -import com.intellij.psi.PsiLiteralValue; +import com.intellij.psi.PsiLiteralExpression; /** - * Java specific completion contributor + * Java specific completion contributor. * @author marhali */ public class JavaCompletionContributor extends CompletionContributor { public JavaCompletionContributor() { - extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralValue.class), new KeyCompletionProvider()); + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralExpression.class), + new KeyCompletionProvider()); } } diff --git a/src/main/java/de/marhali/easyi18n/assistance/completion/JsCompletionContributor.java b/src/main/java/de/marhali/easyi18n/assistance/completion/JsCompletionContributor.java new file mode 100644 index 0000000..3154937 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/completion/JsCompletionContributor.java @@ -0,0 +1,17 @@ +package de.marhali.easyi18n.assistance.completion; + +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.lang.javascript.psi.JSLiteralExpression; +import com.intellij.patterns.PlatformPatterns; + +/** + * JavaScript specific completion contributor. + * @author marhali + */ +public class JsCompletionContributor extends CompletionContributor { + public JsCompletionContributor() { + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(JSLiteralExpression.class), + new KeyCompletionProvider()); + } +} \ No newline at end of file diff --git a/src/main/java/de/marhali/easyi18n/assistance/completion/PhpCompletionContributor.java b/src/main/java/de/marhali/easyi18n/assistance/completion/PhpCompletionContributor.java new file mode 100644 index 0000000..f6c76b9 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/completion/PhpCompletionContributor.java @@ -0,0 +1,17 @@ +package de.marhali.easyi18n.assistance.completion; + +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.patterns.PlatformPatterns; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; + +/** + * Php specific completion contributor. + * @author marhali + */ +public class PhpCompletionContributor extends CompletionContributor { + public PhpCompletionContributor() { + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(StringLiteralExpression.class), + new KeyCompletionProvider()); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/AbstractFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/AbstractFoldingBuilder.java index 5637da7..a4387bf 100644 --- a/src/main/java/de/marhali/easyi18n/assistance/folding/AbstractFoldingBuilder.java +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/AbstractFoldingBuilder.java @@ -2,12 +2,17 @@ package de.marhali.easyi18n.assistance.folding; import com.intellij.lang.ASTNode; import com.intellij.lang.folding.FoldingBuilderEx; -import com.intellij.openapi.editor.FoldingGroup; +import com.intellij.lang.folding.FoldingDescriptor; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; import de.marhali.easyi18n.DataStore; import de.marhali.easyi18n.InstanceManager; import de.marhali.easyi18n.assistance.OptionalAssistance; +import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationValue; import de.marhali.easyi18n.settings.ProjectSettingsService; import de.marhali.easyi18n.util.KeyPathConverter; @@ -15,25 +20,67 @@ import de.marhali.easyi18n.util.KeyPathConverter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; + /** - * Language specific folding of translation key with preferred locale value. + * Language specific translation key folding with representative locale value. * @author marhali */ abstract class AbstractFoldingBuilder extends FoldingBuilderEx implements OptionalAssistance { - - protected static final FoldingGroup group = FoldingGroup.newGroup("EasyI18n key folding"); + /** + * Extract all relevant folding regions for the desired root element. + * The implementation does not need to verify if the character literal is a valid translation. + * @param root Root element + * @return found regions + */ + abstract @NotNull List> extractRegions(@NotNull PsiElement root); /** - * Constructs the folding text for the provided text. - * @param project Opened project - * @param text Designated translation key - * @return Preferred locale value or null if translation does not exists + * Extract the text from the given node. + * @param node Node + * @return extracted text or null if not applicable */ - protected @Nullable String getPlaceholderText(@NotNull Project project, @Nullable String text) { + abstract @Nullable String extractText(@NotNull ASTNode node); + + @Override + public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) { + + if(quick || !isAssistance(root.getProject())) { + return FoldingDescriptor.EMPTY; + } + + List descriptors = new ArrayList<>(); + + TranslationData data = InstanceManager.get(root.getProject()).store().getData(); + KeyPathConverter converter = new KeyPathConverter(root.getProject()); + + for(Pair region : extractRegions(root)) { + if(data.getTranslation(converter.fromString(region.first)) == null) { + continue; + } + + TextRange range = new TextRange(region.second.getTextRange().getStartOffset() + 1, + region.second.getTextRange().getEndOffset() - 1); + + // Some language implementations like [Vue Template] does not support FoldingGroup's + FoldingDescriptor descriptor = new FoldingDescriptor(region.second.getNode(), range); + + descriptors.add(descriptor); + } + + return descriptors.toArray(new FoldingDescriptor[0]); + } + + @Override + public @Nullable String getPlaceholderText(@NotNull ASTNode node) { + String text = extractText(node); + if(text == null) { return null; } + Project project = node.getPsi().getProject(); DataStore store = InstanceManager.get(project).store(); KeyPathConverter converter = new KeyPathConverter(project); TranslationValue localeValues = store.getData().getTranslation(converter.fromString(text)); diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/JavaFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/JavaFoldingBuilder.java index cac89f3..e4904a5 100644 --- a/src/main/java/de/marhali/easyi18n/assistance/folding/JavaFoldingBuilder.java +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/JavaFoldingBuilder.java @@ -1,66 +1,32 @@ package de.marhali.easyi18n.assistance.folding; import com.intellij.lang.ASTNode; -import com.intellij.lang.folding.FoldingDescriptor; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiLiteralExpression; import com.intellij.psi.util.PsiTreeUtil; -import de.marhali.easyi18n.DataStore; -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.util.KeyPathConverter; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * Java specific translation key folding. * @author marhali */ public class JavaFoldingBuilder extends AbstractFoldingBuilder { - @Override - public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) { - List descriptors = new ArrayList<>(); - - if(!isAssistance(root.getProject())) { - return FoldingDescriptor.EMPTY; - } - - Collection literalExpressions = - PsiTreeUtil.findChildrenOfType(root, PsiLiteralExpression.class); - - DataStore store = InstanceManager.get(root.getProject()).store(); - KeyPathConverter converter = new KeyPathConverter(root.getProject()); - - for(final PsiLiteralExpression literalExpression : literalExpressions) { - String value = literalExpression.getValue() instanceof String - ? (String) literalExpression.getValue() : null; - - if(value == null || store.getData().getTranslation(converter.fromString(value)) == null) { - continue; - } - - TextRange range = literalExpression.getTextRange(); - FoldingDescriptor descriptor = new FoldingDescriptor(literalExpression.getNode(), - new TextRange(range.getStartOffset() + 1, range.getEndOffset() - 1), group); - - descriptors.add(descriptor); - } - - return descriptors.toArray(new FoldingDescriptor[0]); + @NotNull List> extractRegions(@NotNull PsiElement root) { + return PsiTreeUtil.findChildrenOfType(root, PsiLiteralExpression.class).stream().map(literalExpression -> + Pair.pair(String.valueOf(literalExpression.getValue()), (PsiElement) literalExpression)) + .collect(Collectors.toList()); } @Override - public @Nullable String getPlaceholderText(@NotNull ASTNode node) { + @Nullable String extractText(@NotNull ASTNode node) { PsiLiteralExpression literalExpression = node.getPsi(PsiLiteralExpression.class); - String value = literalExpression.getValue() instanceof String ? (String) literalExpression.getValue() : null; - return getPlaceholderText(literalExpression.getProject(), value); + return String.valueOf(literalExpression.getValue()); } } diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/JsFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/JsFoldingBuilder.java new file mode 100644 index 0000000..e96b53a --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/JsFoldingBuilder.java @@ -0,0 +1,32 @@ +package de.marhali.easyi18n.assistance.folding; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.javascript.psi.JSLiteralExpression; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * JavaScript specific translation key folding. + * @author marhali + */ +public class JsFoldingBuilder extends AbstractFoldingBuilder { + @Override + @NotNull List> extractRegions(@NotNull PsiElement root) { + return PsiTreeUtil.findChildrenOfType(root, JSLiteralExpression.class).stream().map(literalExpression -> + Pair.pair(literalExpression.getStringValue(), (PsiElement) literalExpression)) + .collect(Collectors.toList()); + } + + @Override + @Nullable String extractText(@NotNull ASTNode node) { + JSLiteralExpression literalExpression = node.getPsi(JSLiteralExpression.class); + return literalExpression.getStringValue(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/KtFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/KtFoldingBuilder.java index 014bfa3..bd95f16 100644 --- a/src/main/java/de/marhali/easyi18n/assistance/folding/KtFoldingBuilder.java +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/KtFoldingBuilder.java @@ -1,22 +1,16 @@ package de.marhali.easyi18n.assistance.folding; import com.intellij.lang.ASTNode; -import com.intellij.lang.folding.FoldingDescriptor; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; -import de.marhali.easyi18n.DataStore; -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.util.KeyPathConverter; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.psi.KtStringTemplateEntry; +import org.jetbrains.kotlin.psi.KtStringTemplateExpression; import java.util.ArrayList; -import java.util.Collection; import java.util.List; /** @@ -25,39 +19,27 @@ import java.util.List; */ public class KtFoldingBuilder extends AbstractFoldingBuilder { @Override - public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) { - List descriptors = new ArrayList<>(); + @NotNull List> extractRegions(@NotNull PsiElement root) { + List> regions = new ArrayList<>(); - if(!isAssistance(root.getProject())) { - return FoldingDescriptor.EMPTY; - } - - Collection templateEntries = - PsiTreeUtil.findChildrenOfType(root, KtStringTemplateEntry.class); - - DataStore store = InstanceManager.get(root.getProject()).store(); - KeyPathConverter converter = new KeyPathConverter(root.getProject()); - - for (KtStringTemplateEntry templateEntry : templateEntries) { - String value = templateEntry.getText(); - - if(value == null || store.getData().getTranslation(converter.fromString(value)) == null) { - continue; + for (KtStringTemplateExpression templateExpression : PsiTreeUtil.findChildrenOfType(root, KtStringTemplateExpression.class)) { + for (KtStringTemplateEntry entry : templateExpression.getEntries()) { + regions.add(Pair.pair(entry.getText(), templateExpression)); + break; } - - TextRange range = templateEntry.getTextRange(); - FoldingDescriptor descriptor = new FoldingDescriptor(templateEntry.getNode(), - new TextRange(range.getStartOffset(), range.getEndOffset()), group); - - descriptors.add(descriptor); } - return descriptors.toArray(new FoldingDescriptor[0]); + return regions; } @Override - public @Nullable String getPlaceholderText(@NotNull ASTNode node) { - KtStringTemplateEntry templateEntry = node.getPsi(KtStringTemplateEntry.class); - return getPlaceholderText(templateEntry.getProject(), templateEntry.getText()); + @Nullable String extractText(@NotNull ASTNode node) { + KtStringTemplateExpression templateExpression = node.getPsi(KtStringTemplateExpression.class); + + for (KtStringTemplateEntry entry : templateExpression.getEntries()) { + return entry.getText(); + } + + return null; } } diff --git a/src/main/java/de/marhali/easyi18n/assistance/folding/PhpFoldingBuilder.java b/src/main/java/de/marhali/easyi18n/assistance/folding/PhpFoldingBuilder.java new file mode 100644 index 0000000..f694b3b --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/folding/PhpFoldingBuilder.java @@ -0,0 +1,31 @@ +package de.marhali.easyi18n.assistance.folding; + +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Php specific translation key folding. + * @author marhali + */ +public class PhpFoldingBuilder extends AbstractFoldingBuilder { + @Override + @NotNull List> extractRegions(@NotNull PsiElement root) { + return PsiTreeUtil.findChildrenOfType(root, StringLiteralExpression.class).stream().map(literalExpression -> + Pair.pair(literalExpression.getContents(), (PsiElement) literalExpression)) + .collect(Collectors.toList()); + } + + @Override + @Nullable String extractText(@NotNull ASTNode node) { + StringLiteralExpression literalExpression = node.getPsi(StringLiteralExpression.class); + return literalExpression.getContents(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractExtractIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractExtractIntention.java deleted file mode 100644 index 2f7c2bf..0000000 --- a/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractExtractIntention.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.marhali.easyi18n.assistance.intention; - -import com.intellij.codeInsight.intention.FileModifier; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.editor.Caret; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiEditorUtil; - -import com.siyeh.ipp.base.MutablyNamedIntention; - -import de.marhali.easyi18n.InstanceManager; -import de.marhali.easyi18n.assistance.OptionalAssistance; -import de.marhali.easyi18n.dialog.AddDialog; -import de.marhali.easyi18n.model.KeyPath; -import de.marhali.easyi18n.model.TranslationData; -import de.marhali.easyi18n.settings.ProjectSettings; -import de.marhali.easyi18n.settings.ProjectSettingsService; -import de.marhali.easyi18n.util.KeyPathConverter; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ResourceBundle; - -/** - * Intention for extracting a translation. Either by translation key or preferred locale value - * @author marhali - */ -abstract class AbstractExtractIntention extends MutablyNamedIntention implements OptionalAssistance { - - protected static final ResourceBundle bundle = ResourceBundle.getBundle("messages"); - - @Override - public @NotNull String getFamilyName() { - return "EasyI18n"; - } - - @Override - public boolean startInWriteAction() { - return false; - } - - protected @NotNull String getTextForElement(@NotNull Project project, @Nullable String key) { - KeyPathConverter converter = new KeyPathConverter(project); - TranslationData data = InstanceManager.get(project).store().getData(); - - return key != null && data.getTranslation(converter.fromString(key)) != null - ? bundle.getString("action.edit") - : bundle.getString("action.extract"); - } - - protected void extractTranslation(@NotNull Project project, @NotNull String text, PsiElement psi) { - ProjectSettings settings = ProjectSettingsService.get(project).getState(); - KeyPathConverter converter = new KeyPathConverter(settings); - - // Extract translation key - // We assume that a text is a translation-key if it contains section delimiters and does not end with them - if(text.contains(settings.getSectionDelimiter()) && !text.endsWith(settings.getSectionDelimiter())) { - new AddDialog(project, converter.fromString(text), null).showAndHandle(); - - } else { // Extract translation value (here preview locale value) - AddDialog dialog = new AddDialog(project, new KeyPath(), text); - - // Replace editor caret with chosen translation key - dialog.registerCallback(translationUpdate -> { - Editor editor = PsiEditorUtil.findEditor(psi); - - if(editor != null) { - Document doc = editor.getDocument(); - Caret caret = editor.getCaretModel().getPrimaryCaret(); - int start = psi.getTextOffset() + 1; - int end = start + text.length(); - - WriteCommandAction.runWriteCommandAction(project, () -> - doc.replaceString(start, end, converter.toString(translationUpdate.getChange().getKey()))); - - caret.removeSelection(); - } - }); - - dialog.showAndHandle();; - } - } - - @Override - public @Nullable FileModifier getFileModifierForPreview(@NotNull PsiFile target) { - return this; - } -} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractTranslationIntention.java new file mode 100644 index 0000000..99be4c2 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/AbstractTranslationIntention.java @@ -0,0 +1,138 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.codeInsight.intention.BaseElementAtCaretIntentionAction; +import com.intellij.codeInspection.util.IntentionFamilyName; +import com.intellij.codeInspection.util.IntentionName; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.util.IncorrectOperationException; + +import de.marhali.easyi18n.InstanceManager; +import de.marhali.easyi18n.assistance.OptionalAssistance; +import de.marhali.easyi18n.dialog.AddDialog; +import de.marhali.easyi18n.dialog.EditDialog; +import de.marhali.easyi18n.model.KeyPath; +import de.marhali.easyi18n.model.Translation; +import de.marhali.easyi18n.model.TranslationData; +import de.marhali.easyi18n.model.TranslationValue; +import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.settings.ProjectSettingsService; +import de.marhali.easyi18n.util.KeyPathConverter; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ResourceBundle; + +/** + * Intention for translation related use-cases. + * Can be used to extract (create) translations or to edit existing ones. + * @author marhali + */ +abstract class AbstractTranslationIntention extends BaseElementAtCaretIntentionAction implements OptionalAssistance { + + protected static final ResourceBundle bundle = ResourceBundle.getBundle("messages"); + + private boolean existingTranslation = false; + + @Override + public @IntentionName @NotNull String getText() { + return existingTranslation + ? bundle.getString("action.edit") + : bundle.getString("action.extract"); + } + + @Override + public @NotNull @IntentionFamilyName String getFamilyName() { + return "EasyI18n"; + } + + @Override + public boolean startInWriteAction() { + return false; + } + + /** + * This is the only method a language-specific translation intention needs to implement. + * The implementation needs to verify element type and extract the relevant key literal or value. + * @param element Element at caret + * @return extract translation key (not verified!) or null if intention is not applicable for this element + */ + protected abstract @Nullable String extractText(@NotNull PsiElement element); + + @NotNull TextRange convertRange(@NotNull TextRange input) { + return new TextRange(input.getStartOffset(), input.getEndOffset()); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { + if(!isAssistance(project)) { + return false; + } + + String text = extractText(element); + + if(text != null) { + KeyPathConverter converter = new KeyPathConverter(project); + existingTranslation = InstanceManager.get(project).store().getData() + .getTranslation(converter.fromString(text)) != null; + } + + return text != null; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) + throws IncorrectOperationException { + + ProjectSettings settings = ProjectSettingsService.get(project).getState(); + KeyPathConverter converter = new KeyPathConverter(settings); + + String text = extractText(element); + + if(text == null) { + throw new IncorrectOperationException("Cannot extract translation intention at caret"); + } + + TranslationData data = InstanceManager.get(project).store().getData(); + KeyPath path = converter.fromString(text); + TranslationValue existingTranslation = data.getTranslation(path); + + // Existing translation - edit dialog + if(existingTranslation != null) { + new EditDialog(project, new Translation(path, existingTranslation)).showAndHandle(); + return; + } + + // Extract translation by key + // We assume that a text is a translation-key if it contains section delimiters and does not end with them + if(text.contains(settings.getSectionDelimiter()) && !text.endsWith(settings.getSectionDelimiter())) { + new AddDialog(project, path, null).showAndHandle(); + return; + } + + // Extract translation by preview locale value + AddDialog dialog = new AddDialog(project, new KeyPath(), text); + + dialog.registerCallback(translationUpdate -> { // Replace text at caret with chosen translation key + if(editor != null) { + Document doc = editor.getDocument(); + Caret caret = editor.getCaretModel().getPrimaryCaret(); + TextRange range = convertRange(element.getTextRange()); + + WriteCommandAction.runWriteCommandAction(project, () -> + doc.replaceString(range.getStartOffset(), range.getEndOffset(), + converter.toString(translationUpdate.getChange().getKey()))); + + caret.removeSelection(); + } + }); + + dialog.showAndHandle(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/JavaExtractIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/JavaExtractIntention.java deleted file mode 100644 index d074d4f..0000000 --- a/src/main/java/de/marhali/easyi18n/assistance/intention/JavaExtractIntention.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.marhali.easyi18n.assistance.intention; - -import com.intellij.codeInspection.util.IntentionName; -import com.intellij.psi.*; -import com.siyeh.ipp.base.PsiElementPredicate; -import org.jetbrains.annotations.NotNull; - -/** - * Java specific translation key intention. - * @author marhali - */ -public class JavaExtractIntention extends AbstractExtractIntention { - - @Override - protected @IntentionName String getTextForElement(PsiElement element) { - return getTextForElement(element.getProject(), (String) ((PsiLiteralExpression) element).getValue()); - } - - @Override - protected void processIntention(@NotNull PsiElement element) { - System.out.println("proci2"); - if(!(element instanceof PsiLiteralExpression) - || !(((PsiLiteralExpression) element).getValue() instanceof String)) { - return; - } - - extractTranslation(element.getProject(), (String) ((PsiLiteralExpression) element).getValue(), element); - } - - @Override - protected @NotNull PsiElementPredicate getElementPredicate() { - System.out.println("predi2"); - return element -> element instanceof PsiLiteralExpression; - } -} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/JavaTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/JavaTranslationIntention.java new file mode 100644 index 0000000..aed1b98 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/JavaTranslationIntention.java @@ -0,0 +1,27 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Java specific translation intention. + * @author marhali + */ +public class JavaTranslationIntention extends AbstractTranslationIntention { + @Override + protected @Nullable String extractText(@NotNull PsiElement element) { + if(!(element.getParent() instanceof PsiLiteralExpression)) { + return null; + } + + return String.valueOf(((PsiLiteralExpression) element.getParent()).getValue()); + } + + @Override + @NotNull TextRange convertRange(@NotNull TextRange input) { + return new TextRange(input.getStartOffset() + 1, input.getEndOffset() - 1); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/JsExtractIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/JsExtractIntention.java deleted file mode 100644 index 5496802..0000000 --- a/src/main/java/de/marhali/easyi18n/assistance/intention/JsExtractIntention.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.marhali.easyi18n.assistance.intention; - -import com.intellij.codeInspection.util.IntentionName; -import com.intellij.lang.javascript.psi.JSLiteralExpression; -import com.intellij.psi.PsiElement; -import com.siyeh.ipp.base.PsiElementPredicate; -import org.jetbrains.annotations.NotNull; - -/** - * JavaScript specific translation key intention. - * @author marhali - */ -// TODO: current implementation does not support other languages than Java -public class JsExtractIntention extends AbstractExtractIntention { - - @Override - public @NotNull String getFamilyName() { - return "JavaScript"; - } - - @Override - protected @IntentionName String getTextForElement(PsiElement element) { - return "HILFE"; - //return getTextForElement(element.getProject(), ((JSLiteralExpression) element).getStringValue()); - } - - @Override - protected void processIntention(@NotNull PsiElement element) { - if(!(element instanceof JSLiteralExpression) || ((JSLiteralExpression) element).getStringValue() == null) { - return; - } - - extractTranslation(element.getProject(), ((JSLiteralExpression) element).getStringValue(), element); - } - - @Override - protected @NotNull PsiElementPredicate getElementPredicate() { - return element -> true; - } -} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/JsTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/JsTranslationIntention.java new file mode 100644 index 0000000..da526a5 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/JsTranslationIntention.java @@ -0,0 +1,28 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.lang.javascript.psi.JSLiteralExpression; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * JavaScript specific translation key intention. + * @author marhali + */ +public class JsTranslationIntention extends AbstractTranslationIntention { + @Override + protected @Nullable String extractText(@NotNull PsiElement element) { + if(!(element.getParent() instanceof JSLiteralExpression)) { + return null; + } + + return ((JSLiteralExpression) element.getParent()).getStringValue(); + } + + @Override + @NotNull TextRange convertRange(@NotNull TextRange input) { + return new TextRange(input.getStartOffset() + 1, input.getEndOffset() - 1); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/KtExtractIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/KtExtractIntention.java deleted file mode 100644 index bdaf628..0000000 --- a/src/main/java/de/marhali/easyi18n/assistance/intention/KtExtractIntention.java +++ /dev/null @@ -1,41 +0,0 @@ -package de.marhali.easyi18n.assistance.intention; - -import com.intellij.codeInspection.util.IntentionName; -import com.intellij.psi.PsiElement; -import com.siyeh.ipp.base.PsiElementPredicate; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry; -import org.jetbrains.kotlin.psi.KtStringTemplateExpression; - -/** - * Kotlin specific translation key intention. - * @author marhali - */ -// TODO: kotlin impl does not work - no action -public class KtExtractIntention extends AbstractExtractIntention { - @Override - protected @IntentionName String getTextForElement(PsiElement element) { - return "hallo"; - //return getTextForElement(element.getProject(), element.getText()); - } - - @Override - protected void processIntention(@NotNull PsiElement element) { - System.out.println("Hallo"); - - if(!(element instanceof KtStringTemplateExpression)) { - System.out.println(element.getClass()); - return; - } - - System.out.println("hallo"); - - extractTranslation(element.getProject() , element.getText(), element); - } - - @Override - protected @NotNull PsiElementPredicate getElementPredicate() { - System.out.println("predi"); - return element -> element instanceof KtLiteralStringTemplateEntry; - } -} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/KtTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/KtTranslationIntention.java new file mode 100644 index 0000000..7ed3f31 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/KtTranslationIntention.java @@ -0,0 +1,23 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.psi.PsiElement; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry; + +/** + * Kotlin specific translation key intention. + * @author marhali + */ +public class KtTranslationIntention extends AbstractTranslationIntention { + @Override + protected @Nullable String extractText(@NotNull PsiElement element) { + if(!(element.getParent() instanceof KtLiteralStringTemplateEntry)) { + return null; + } + + KtLiteralStringTemplateEntry expression = (KtLiteralStringTemplateEntry) element.getParent(); + return expression.getText(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/intention/PhpTranslationIntention.java b/src/main/java/de/marhali/easyi18n/assistance/intention/PhpTranslationIntention.java new file mode 100644 index 0000000..76693a3 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/intention/PhpTranslationIntention.java @@ -0,0 +1,23 @@ +package de.marhali.easyi18n.assistance.intention; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Php specific translation intention + * @author marhali + */ +public class PhpTranslationIntention extends AbstractTranslationIntention { + @Override + protected @Nullable String extractText(@NotNull PsiElement element) { + if(!(element.getParent() instanceof StringLiteralExpression)) { + return null; + } + + return ((StringLiteralExpression) element.getParent()).getContents(); + } +} diff --git a/src/main/java/de/marhali/easyi18n/assistance/reference/JavaKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/assistance/reference/JavaKeyReferenceContributor.java index bff2307..7d80e66 100644 --- a/src/main/java/de/marhali/easyi18n/assistance/reference/JavaKeyReferenceContributor.java +++ b/src/main/java/de/marhali/easyi18n/assistance/reference/JavaKeyReferenceContributor.java @@ -4,6 +4,7 @@ import com.intellij.openapi.project.Project; import com.intellij.patterns.PlatformPatterns; import com.intellij.psi.*; + import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; @@ -14,12 +15,10 @@ import org.jetbrains.annotations.NotNull; */ public class JavaKeyReferenceContributor extends AbstractKeyReferenceContributor { - // TODO: why not PsiLiteralExpression? - @Override public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { registrar.registerReferenceProvider( - PlatformPatterns.psiElement(PsiLiteralValue.class), + PlatformPatterns.psiElement(PsiLiteralExpression.class), getProvider()); } @@ -30,8 +29,10 @@ public class JavaKeyReferenceContributor extends AbstractKeyReferenceContributor @NotNull PsiElement element, @NotNull ProcessingContext context) { Project project = element.getProject(); - PsiLiteralValue literalValue = (PsiLiteralValue) element; - String value = literalValue.getValue() instanceof String ? (String) literalValue.getValue() : null; + PsiLiteralExpression literalExpression = (PsiLiteralExpression) element; + String value = literalExpression.getValue() instanceof String + ? (String) literalExpression.getValue() + : null; return getReferences(project, element, value); } diff --git a/src/main/java/de/marhali/easyi18n/assistance/reference/PhpKeyReferenceContributor.java b/src/main/java/de/marhali/easyi18n/assistance/reference/PhpKeyReferenceContributor.java new file mode 100644 index 0000000..0d11599 --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/assistance/reference/PhpKeyReferenceContributor.java @@ -0,0 +1,37 @@ +package de.marhali.easyi18n.assistance.reference; + +import com.intellij.openapi.project.Project; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceProvider; +import com.intellij.psi.PsiReferenceRegistrar; +import com.intellij.util.ProcessingContext; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; + +import org.jetbrains.annotations.NotNull; + +/** + * Php specific key reference binding + */ +public class PhpKeyReferenceContributor extends AbstractKeyReferenceContributor { + @Override + public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { + registrar.registerReferenceProvider( + PlatformPatterns.psiElement(StringLiteralExpression.class), + getProvider()); + } + + private PsiReferenceProvider getProvider() { + return new PsiReferenceProvider() { + @Override + public PsiReference @NotNull [] getReferencesByElement( + @NotNull PsiElement element, @NotNull ProcessingContext context) { + + Project project = element.getProject(); + StringLiteralExpression literalExpression = (StringLiteralExpression) element; + return getReferences(project, element, literalExpression.getContents()); + } + }; + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/de.marhali.easyi18n-java.xml b/src/main/resources/META-INF/de.marhali.easyi18n-java.xml index 43a9382..e172b2b 100644 --- a/src/main/resources/META-INF/de.marhali.easyi18n-java.xml +++ b/src/main/resources/META-INF/de.marhali.easyi18n-java.xml @@ -1,7 +1,7 @@ - de.marhali.easyi18n.assistance.intention.JavaExtractIntention + de.marhali.easyi18n.assistance.intention.JavaTranslationIntention + + + + + + + + + + + - de.marhali.easyi18n.assistance.intention.JsExtractIntention + de.marhali.easyi18n.assistance.intention.JsTranslationIntention + + \ No newline at end of file diff --git a/src/main/resources/META-INF/de.marhali.easyi18n-kotlin.xml b/src/main/resources/META-INF/de.marhali.easyi18n-kotlin.xml index b1b898b..7e5c8c4 100644 --- a/src/main/resources/META-INF/de.marhali.easyi18n-kotlin.xml +++ b/src/main/resources/META-INF/de.marhali.easyi18n-kotlin.xml @@ -1,7 +1,7 @@ - de.marhali.easyi18n.assistance.intention.KtExtractIntention + de.marhali.easyi18n.assistance.intention.KtTranslationIntention + + + de.marhali.easyi18n.assistance.intention.PhpTranslationIntention + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/de.marhali.easyi18n-vue.xml b/src/main/resources/META-INF/de.marhali.easyi18n-vue.xml index 68c3fa0..79ed051 100644 --- a/src/main/resources/META-INF/de.marhali.easyi18n-vue.xml +++ b/src/main/resources/META-INF/de.marhali.easyi18n-vue.xml @@ -1,6 +1,15 @@ - + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index b1274f6..4db7d2f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -13,6 +13,7 @@ JavaScript com.intellij.java org.jetbrains.plugins.vue + com.jetbrains.php