Skip to content

Commit 907508d

Browse files
committed
[Twig] add generator for translation #506
1 parent 254bab8 commit 907508d

File tree

7 files changed

+167
-12
lines changed

7 files changed

+167
-12
lines changed

META-INF/plugin.xml

+4
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,10 @@
673673
<add-to-group group-id="GenerateGroup" anchor="last" />
674674
</action>
675675

676+
<action id="Symfony.TwigTranslationGenerator" class="fr.adrienbrault.idea.symfony2plugin.templating.TwigTranslationGeneratorAction" text="Translation" icon="SymfonyIcons.Translation">
677+
<add-to-group group-id="GenerateGroup" anchor="last" />
678+
</action>
679+
676680
<group id="Symfony2Group" text="Symfony" popup="false">
677681
<group id="Symfony2GroupService" class="com.intellij.ide.actions.NonTrivialActionGroup" text="Service" popup="true" icon="SymfonyIcons.Symfony">
678682
<action id="Symfony2NewXmlService" class="fr.adrienbrault.idea.symfony2plugin.action.NewXmlServiceAction"/>

src/fr/adrienbrault/idea/symfony2plugin/action/TwigExtractLanguageAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public void actionPerformed(AnActionEvent event) {
179179

180180
}
181181

182-
private TreeMap<String, Integer> getPossibleDomainTreeMap(TwigFile psiFile, final Set<String> domainNames) {
182+
public static TreeMap<String, Integer> getPossibleDomainTreeMap(TwigFile psiFile, final Set<String> domainNames) {
183183

184184
final Map<String, Integer> found = new HashMap<>();
185185

src/fr/adrienbrault/idea/symfony2plugin/templating/TranslationTagCompletionRegistrar.java

+1-10
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,12 @@ private PsiElementPattern.Capture<XmlToken> getTranslationTagValuePattern() {
5656
.withLanguage(XMLLanguage.INSTANCE);
5757
}
5858

59-
/**
60-
* Resolve html language injection
61-
*/
62-
private static PsiElement getElementOnTwigViewProvider(@NotNull PsiElement element) {
63-
PsiFile file = element.getContainingFile();
64-
TextRange textRange = element.getTextRange();
65-
return file.getViewProvider().findElementAt(textRange.getStartOffset(), TwigLanguage.INSTANCE);
66-
}
67-
6859
/**
6960
* Find trans tag as this is
7061
* <caret>{% endtrans %}
7162
*/
7263
private static TwigCompositeElement getTagOnTwigViewProvider(@NotNull PsiElement element) {
73-
PsiElement psiElement = getElementOnTwigViewProvider(element);
64+
PsiElement psiElement = TwigUtil.getElementOnTwigViewProvider(element);
7465
if(psiElement == null) {
7566
return null;
7667
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package fr.adrienbrault.idea.symfony2plugin.templating;
2+
3+
import com.intellij.codeInsight.CodeInsightActionHandler;
4+
import com.intellij.codeInsight.actions.CodeInsightAction;
5+
import com.intellij.codeInsight.lookup.LookupElement;
6+
import com.intellij.openapi.command.WriteCommandAction;
7+
import com.intellij.openapi.editor.Editor;
8+
import com.intellij.openapi.project.Project;
9+
import com.intellij.openapi.ui.popup.JBPopupFactory;
10+
import com.intellij.psi.PsiElement;
11+
import com.intellij.psi.PsiFile;
12+
import com.intellij.psi.impl.source.html.HtmlFileImpl;
13+
import com.intellij.ui.components.JBList;
14+
import com.jetbrains.php.completion.insert.PhpInsertHandlerUtil;
15+
import com.jetbrains.twig.TwigFile;
16+
import fr.adrienbrault.idea.symfony2plugin.action.TwigExtractLanguageAction;
17+
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
18+
import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil;
19+
import org.apache.commons.lang.StringUtils;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.jetbrains.annotations.Nullable;
22+
23+
import java.util.List;
24+
import java.util.Set;
25+
import java.util.TreeMap;
26+
import java.util.TreeSet;
27+
import java.util.stream.Collectors;
28+
29+
/**
30+
* @author Daniel Espendiller <daniel@espendiller.net>
31+
*/
32+
public class TwigTranslationGeneratorAction extends CodeInsightAction {
33+
@Override
34+
protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
35+
return file instanceof TwigFile
36+
|| file instanceof HtmlFileImpl && file.getName().toLowerCase().endsWith(".twig")
37+
|| getInjectedTwigElement(file, editor) != null;
38+
39+
}
40+
41+
@NotNull
42+
@Override
43+
protected CodeInsightActionHandler getHandler() {
44+
return new MyCodeInsightActionHandler();
45+
}
46+
47+
@Nullable
48+
private static PsiElement getInjectedTwigElement(@NotNull PsiFile psiFile, @NotNull Editor editor) {
49+
int caretOffset = editor.getCaretModel().getOffset();
50+
if(caretOffset <= 0) {
51+
return null;
52+
}
53+
54+
PsiElement psiElement = psiFile.findElementAt(caretOffset);
55+
if(psiElement == null) {
56+
return null;
57+
}
58+
59+
return TwigUtil.getElementOnTwigViewProvider(psiElement);
60+
}
61+
62+
private static class MyCodeInsightActionHandler implements CodeInsightActionHandler {
63+
@Override
64+
public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile psiFile) {
65+
int caretOffset = editor.getCaretModel().getOffset();
66+
if(caretOffset <= 0) {
67+
return;
68+
}
69+
70+
PsiElement psiElement = getInjectedTwigElement(psiFile, editor);
71+
if(psiElement == null) {
72+
return;
73+
}
74+
75+
PsiFile containingFile = psiElement.getContainingFile();
76+
if(!(containingFile instanceof TwigFile)) {
77+
return;
78+
}
79+
80+
String defaultDomain = TwigUtil.getTransDefaultDomainOnScope(psiElement);
81+
if(defaultDomain == null) {
82+
defaultDomain = "messages";
83+
}
84+
85+
final Set<String> domainNames = TranslationUtil.getTranslationDomainLookupElements(project).stream()
86+
.map(LookupElement::getLookupString)
87+
.collect(Collectors.toCollection(TreeSet::new));
88+
89+
TreeMap<String, Integer> sortedMap = TwigExtractLanguageAction.getPossibleDomainTreeMap((TwigFile) containingFile, domainNames);
90+
91+
// we want to have mostly used domain preselected
92+
String domain = defaultDomain;
93+
if(sortedMap.size() > 0) {
94+
domain = sortedMap.firstKey();
95+
}
96+
97+
List<String> collect = TranslationUtil.getTranslationLookupElementsOnDomain(project, domain)
98+
.stream()
99+
.map(LookupElement::getLookupString)
100+
.sorted()
101+
.collect(Collectors.toList());
102+
103+
final JBList<String> list = new JBList<>(collect);
104+
105+
String finalDefaultDomain = defaultDomain;
106+
String finalDomain = domain;
107+
108+
JBPopupFactory.getInstance().createListPopupBuilder(list)
109+
.setTitle(String.format("Symfony: Translations \"%s\"", StringUtils.abbreviate(domain, 20)))
110+
.setItemChoosenCallback(() -> {
111+
String selectedValue = list.getSelectedValue();
112+
113+
new WriteCommandAction.Simple(editor.getProject(), String.format("Symfony: Add Translation \"%s\"", StringUtils.abbreviate(selectedValue, 20))) {
114+
@Override
115+
protected void run() {
116+
String s;
117+
118+
if (!finalDomain.equals(finalDefaultDomain)) {
119+
s = String.format("{{ '%s'|trans({}, '%s') }}", selectedValue, finalDomain);
120+
} else {
121+
s = String.format("{{ '%s'|trans }}", selectedValue);
122+
}
123+
124+
PhpInsertHandlerUtil.insertStringAtCaret(editor, s);
125+
}
126+
}.execute();
127+
})
128+
.createPopup()
129+
.showInBestPositionFor(editor);
130+
}
131+
}
132+
}

src/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.intellij.openapi.project.Project;
44
import com.intellij.openapi.util.Pair;
5+
import com.intellij.openapi.util.TextRange;
56
import com.intellij.openapi.vfs.VfsUtil;
67
import com.intellij.openapi.vfs.VirtualFile;
78
import com.intellij.patterns.PlatformPatterns;
@@ -25,6 +26,7 @@
2526
import com.jetbrains.php.phpunit.PhpUnitUtil;
2627
import com.jetbrains.twig.TwigFile;
2728
import com.jetbrains.twig.TwigFileType;
29+
import com.jetbrains.twig.TwigLanguage;
2830
import com.jetbrains.twig.TwigTokenTypes;
2931
import com.jetbrains.twig.elements.*;
3032
import fr.adrienbrault.idea.symfony2plugin.TwigHelper;
@@ -251,7 +253,7 @@ public static String getTransDefaultDomainOnScopeOrInjectedElement(@NotNull PsiE
251253
}
252254

253255
/**
254-
* Html in Twig is injected trx to find an real Twig element
256+
* Html in Twig is injected try to find an real Twig element
255257
* TODO: there must be some nicer solution
256258
*
257259
* {% block %}<html/>{% endblock %}
@@ -1302,4 +1304,13 @@ public static Collection<PhpClass> getTwigExtensionClasses(@NotNull Project proj
13021304
.filter(phpClass -> !PhpUnitUtil.isPhpUnitTestFile(phpClass.getContainingFile()))
13031305
.collect(Collectors.toCollection(HashSet::new));
13041306
}
1307+
1308+
/**
1309+
* Resolve html language injection
1310+
*/
1311+
public static PsiElement getElementOnTwigViewProvider(@NotNull PsiElement element) {
1312+
PsiFile file = element.getContainingFile();
1313+
TextRange textRange = element.getTextRange();
1314+
return file.getViewProvider().findElementAt(textRange.getStartOffset(), TwigLanguage.INSTANCE);
1315+
}
13051316
}

src/icons/SymfonyIcons.java

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
public class SymfonyIcons {
77
public static final Icon Symfony = Symfony2Icons.SYMFONY;
88
public static final Icon FormType = Symfony2Icons.FORM_TYPE;
9+
public static final Icon Translation = Symfony2Icons.TRANSLATION;
910
public static final Icon SymfonyToolWindow = Symfony2Icons.SYMFONY_TOOL_WINDOW;
1011
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package fr.adrienbrault.idea.symfony2plugin.tests.templating;
2+
3+
import fr.adrienbrault.idea.symfony2plugin.templating.TwigTranslationGeneratorAction;
4+
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
5+
6+
/**
7+
* @author Daniel Espendiller <daniel@espendiller.net>
8+
* @see fr.adrienbrault.idea.symfony2plugin.templating.TwigTranslationGeneratorAction
9+
*/
10+
public class TwigTranslationGeneratorActionTest extends SymfonyLightCodeInsightFixtureTestCase {
11+
public void testActionAvailableForFileScope() {
12+
myFixture.configureByText("foo.html.twig", "{{ foo }}");
13+
14+
assertTrue(myFixture.testAction(new TwigTranslationGeneratorAction()).isEnabledAndVisible());
15+
}
16+
}

0 commit comments

Comments
 (0)