Skip to content

Commit c799090

Browse files
committed
initial twig translation string extraction action #213
1 parent 9f65fc6 commit c799090

9 files changed

+694
-49
lines changed

META-INF/plugin.xml

+5
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,11 @@
397397
<add-to-group group-id="ProjectViewPopupMenu" anchor="last"/>
398398
</action>
399399

400+
<action id="Symfony.TwigLanugage" class="fr.adrienbrault.idea.symfony2plugin.action.TwigExtractLanguageAction">
401+
<add-to-group group-id="EditorPopupMenu"/>
402+
<add-to-group group-id="ProjectViewPopupMenu" anchor="last"/>
403+
</action>
404+
400405
<group id="Symfony2Group" text="Symfony2" popup="true" icon="SymfonyIcons.Symfony">
401406
<action id="Symfony2NewControllerService" class="fr.adrienbrault.idea.symfony2plugin.action.NewControllerAction"/>
402407
<action id="Symfony2NewXmlService" class="fr.adrienbrault.idea.symfony2plugin.action.NewXmlServiceAction"/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package fr.adrienbrault.idea.symfony2plugin.action;
2+
3+
import com.intellij.openapi.actionSystem.AnActionEvent;
4+
import com.intellij.openapi.actionSystem.DataKey;
5+
import com.intellij.openapi.actionSystem.PlatformDataKeys;
6+
import com.intellij.openapi.application.ApplicationManager;
7+
import com.intellij.openapi.editor.Editor;
8+
import com.intellij.openapi.project.DumbAwareAction;
9+
import com.intellij.openapi.project.Project;
10+
import com.intellij.openapi.util.TextRange;
11+
import com.intellij.patterns.PlatformPatterns;
12+
import com.intellij.psi.PsiDocumentManager;
13+
import com.intellij.psi.PsiElement;
14+
import com.intellij.psi.PsiFile;
15+
import com.intellij.psi.tree.IElementType;
16+
import com.intellij.psi.xml.XmlTokenType;
17+
import com.jetbrains.twig.TwigFile;
18+
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
19+
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
20+
import fr.adrienbrault.idea.symfony2plugin.action.comparator.PsiWeightListComparator;
21+
import fr.adrienbrault.idea.symfony2plugin.action.dict.TranslationFileModel;
22+
import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil;
23+
import fr.adrienbrault.idea.symfony2plugin.translation.form.TranslatorKeyExtractorDialog;
24+
import fr.adrienbrault.idea.symfony2plugin.translation.util.TranslationInsertUtil;
25+
import fr.adrienbrault.idea.symfony2plugin.util.SymfonyBundleUtil;
26+
import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle;
27+
28+
import java.awt.*;
29+
import java.util.*;
30+
import java.util.List;
31+
32+
public class TwigExtractLanguageAction extends DumbAwareAction {
33+
34+
public TwigExtractLanguageAction() {
35+
super("Extract Translation", "Extract Translation Key", Symfony2Icons.SYMFONY);
36+
}
37+
38+
39+
public void update(AnActionEvent event) {
40+
Project project = event.getData(PlatformDataKeys.PROJECT);
41+
42+
if (project == null || !Symfony2ProjectComponent.isEnabled(project)) {
43+
this.setStatus(event, false);
44+
return;
45+
}
46+
47+
// only since phpstorm 7.1; PlatformDataKeys.PSI_FILE
48+
Object psiFile = event.getData(DataKey.create("psi.File"));
49+
50+
if(!(psiFile instanceof TwigFile)) {
51+
this.setStatus(event, false);
52+
return;
53+
}
54+
55+
Editor editor = event.getData(PlatformDataKeys.EDITOR);
56+
if(editor == null) {
57+
this.setStatus(event, false);
58+
return;
59+
}
60+
61+
PsiElement psiElement = ((TwigFile) psiFile).findElementAt(editor.getCaretModel().getOffset());
62+
if(psiElement == null) {
63+
this.setStatus(event, false);
64+
return;
65+
}
66+
67+
// <a title="TEXT">TEXT</a>
68+
IElementType elementType = psiElement.getNode().getElementType();
69+
if(elementType == XmlTokenType.XML_DATA_CHARACTERS || elementType == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) {
70+
this.setStatus(event, true);
71+
} else {
72+
this.setStatus(event, false);
73+
}
74+
75+
}
76+
77+
private void setStatus(AnActionEvent event, boolean status) {
78+
event.getPresentation().setVisible(status);
79+
event.getPresentation().setEnabled(status);
80+
}
81+
82+
public void actionPerformed(AnActionEvent event) {
83+
84+
final Editor editor = event.getData(PlatformDataKeys.EDITOR);
85+
if(editor == null) {
86+
return;
87+
}
88+
89+
Object psiFile = event.getData(DataKey.create("psi.File"));
90+
if(!(psiFile instanceof TwigFile)) {
91+
return;
92+
}
93+
94+
final PsiElement psiElement = ((TwigFile) psiFile).findElementAt(editor.getCaretModel().getOffset());
95+
if(psiElement == null) {
96+
return;
97+
}
98+
99+
IElementType elementType = psiElement.getNode().getElementType();
100+
if(!(elementType == XmlTokenType.XML_DATA_CHARACTERS || elementType == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN)) {
101+
return;
102+
}
103+
104+
final String translationText = psiElement.getText();
105+
106+
SymfonyBundleUtil symfonyBundleUtil = new SymfonyBundleUtil(psiElement.getProject());
107+
final SymfonyBundle symfonyBundle = symfonyBundleUtil.getContainingBundle(psiElement.getContainingFile());
108+
109+
final List<PsiFile> psiFiles = TranslationUtil.getAllTranslationFiles(psiElement.getProject());
110+
111+
final List<TranslationFileModel> psiFilesSorted = new ArrayList<TranslationFileModel>();
112+
for(PsiFile psiFile1: psiFiles) {
113+
TranslationFileModel psiWeightList = new TranslationFileModel(psiFile1);
114+
115+
if(symfonyBundle != null && symfonyBundle.isInBundle(psiFile1)) {
116+
psiWeightList.setSymfonyBundle(symfonyBundle);
117+
psiWeightList.addWeight(2);
118+
} else {
119+
psiWeightList.setSymfonyBundle(symfonyBundleUtil.getContainingBundle(psiFile1));
120+
}
121+
122+
String relativePath = psiWeightList.getRelativePath();
123+
if(relativePath != null && (relativePath.startsWith("src") || relativePath.startsWith("app"))) {
124+
psiWeightList.addWeight(1);
125+
}
126+
127+
psiFilesSorted.add(psiWeightList);
128+
}
129+
130+
Collections.sort(psiFilesSorted, new PsiWeightListComparator());
131+
132+
TranslatorKeyExtractorDialog extractorDialog = new TranslatorKeyExtractorDialog(psiFilesSorted, new TranslatorKeyExtractorDialog.OnOkCallback() {
133+
@Override
134+
public void onClick(List<TranslationFileModel> files, final String keyName) {
135+
136+
PsiDocumentManager.getInstance(psiElement.getProject()).doPostponedOperationsAndUnblockDocument(editor.getDocument());
137+
final TextRange range = psiElement.getTextRange();
138+
139+
String domainName = files.get(0).getPsiFile().getName();
140+
int indexOfPoint = domainName.indexOf(".");
141+
if(indexOfPoint > 0) {
142+
domainName = domainName.substring(0, indexOfPoint);
143+
}
144+
145+
final String finalDomainName = domainName;
146+
ApplicationManager.getApplication().runWriteAction(new Runnable() {
147+
@Override
148+
public void run() {
149+
editor.getDocument().replaceString(range.getStartOffset(), range.getEndOffset(), String.format("{{ '%s'|trans({}, '%s') }}", keyName, finalDomainName));
150+
editor.getCaretModel().moveToOffset(range.getStartOffset());
151+
}
152+
});
153+
154+
for(TranslationFileModel transPsiFile: files) {
155+
TranslationInsertUtil.invokeTranslation(keyName, translationText, transPsiFile.getPsiFile(), false);
156+
}
157+
158+
}
159+
});
160+
161+
extractorDialog.setTitle("Extract Key");
162+
extractorDialog.setMinimumSize(new Dimension(600, 300));
163+
extractorDialog.pack();
164+
extractorDialog.setLocationRelativeTo(null);
165+
extractorDialog.setVisible(true);
166+
167+
168+
}
169+
170+
}
171+
172+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package fr.adrienbrault.idea.symfony2plugin.action.comparator;
2+
3+
import fr.adrienbrault.idea.symfony2plugin.action.dict.TranslationFileModel;
4+
5+
import java.util.Comparator;
6+
7+
/**
8+
* Created by daniel on 17.05.14.
9+
*/
10+
public class PsiWeightListComparator implements Comparator<TranslationFileModel> {
11+
@Override
12+
public int compare(TranslationFileModel o1, TranslationFileModel o2) {
13+
if(o1.getWeight() > o2.getWeight()) {
14+
return -1;
15+
}
16+
17+
if(o1.getWeight() < o2.getWeight()) {
18+
return 1;
19+
}
20+
21+
String relativePathO1 = o1.getRelativePath();
22+
String relativePathO2 = o2.getRelativePath();
23+
if(relativePathO1 == null || relativePathO2 == null) {
24+
return 0;
25+
}
26+
27+
return relativePathO1.compareTo(relativePathO2);
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package fr.adrienbrault.idea.symfony2plugin.action.dict;
2+
3+
import com.intellij.openapi.vfs.VfsUtil;
4+
import com.intellij.psi.PsiFile;
5+
import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle;
6+
import org.jetbrains.annotations.Nullable;
7+
8+
public class TranslationFileModel {
9+
10+
final private PsiFile psiFile;
11+
private int weight = 0;
12+
private boolean enabled = false;
13+
private SymfonyBundle symfonyBundle;
14+
15+
public TranslationFileModel(PsiFile psiFile) {
16+
this.psiFile = psiFile;
17+
}
18+
19+
public PsiFile getPsiFile() {
20+
return psiFile;
21+
}
22+
23+
public int getWeight() {
24+
return weight;
25+
}
26+
27+
public void addWeight(int weight) {
28+
this.weight += weight;
29+
}
30+
31+
@Nullable
32+
public String getRelativePath() {
33+
return VfsUtil.getRelativePath(psiFile.getVirtualFile(), psiFile.getProject().getBaseDir(), '/');
34+
}
35+
36+
public void setEnabled(boolean enabled) {
37+
this.enabled = enabled;
38+
}
39+
40+
public boolean isEnabled() {
41+
return enabled;
42+
}
43+
44+
@Nullable
45+
public SymfonyBundle getSymfonyBundle() {
46+
return symfonyBundle;
47+
}
48+
49+
public void setSymfonyBundle(@Nullable SymfonyBundle symfonyBundle) {
50+
this.symfonyBundle = symfonyBundle;
51+
}
52+
53+
}

src/fr/adrienbrault/idea/symfony2plugin/translation/TranslationKeyIntentionAction.java

+3-49
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.intellij.psi.util.PsiTreeUtil;
1616
import com.intellij.util.IncorrectOperationException;
1717
import fr.adrienbrault.idea.symfony2plugin.toolwindow.Symfony2SearchForm;
18+
import fr.adrienbrault.idea.symfony2plugin.translation.util.TranslationInsertUtil;
1819
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
1920
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlKeyFinder;
2021
import org.apache.commons.lang.StringUtils;
@@ -96,8 +97,8 @@ public void run() {
9697
}
9798

9899
// search indent and EOL value
99-
String indent = findIndent(goToPsi.getYamlKeyValue());
100-
String eol = findEol(goToPsi.getYamlKeyValue());
100+
String indent = TranslationInsertUtil.findIndent(goToPsi.getYamlKeyValue());
101+
String eol = TranslationInsertUtil.findEol(goToPsi.getYamlKeyValue());
101102

102103
String currentIndentOffset = "";
103104
PsiElement lastKnownPsiElement = goToPsi.getYamlKeyValue();
@@ -146,51 +147,4 @@ public void run() {
146147
});
147148
}
148149

149-
150-
151-
private String findIndent(PsiElement psiElement) {
152-
153-
YAMLKeyValue parentYamlKey = PsiTreeUtil.getParentOfType(psiElement, YAMLKeyValue.class);
154-
if(parentYamlKey != null) {
155-
return parentYamlKey.getValueIndent();
156-
}
157-
158-
PsiElement[] indentPsiElements = PsiTreeUtil.collectElements(psiElement.getContainingFile(), new PsiElementFilter() {
159-
@Override
160-
public boolean isAccepted(PsiElement element) {
161-
return PlatformPatterns.psiElement(YAMLTokenTypes.INDENT).accepts(element);
162-
}
163-
});
164-
165-
if(indentPsiElements.length > 0) {
166-
return indentPsiElements[0].getText();
167-
}
168-
169-
// file without indent; how get default one?
170-
return " ";
171-
172-
}
173-
174-
private String findEol(PsiElement psiElement) {
175-
176-
for(PsiElement child: YamlHelper.getChildrenFix(psiElement)) {
177-
if(PlatformPatterns.psiElement(YAMLTokenTypes.EOL).accepts(child)) {
178-
return child.getText();
179-
}
180-
}
181-
182-
PsiElement[] indentPsiElements = PsiTreeUtil.collectElements(psiElement.getContainingFile(), new PsiElementFilter() {
183-
@Override
184-
public boolean isAccepted(PsiElement element) {
185-
return PlatformPatterns.psiElement(YAMLTokenTypes.EOL).accepts(element);
186-
}
187-
});
188-
189-
if(indentPsiElements.length > 0) {
190-
return indentPsiElements[0].getText();
191-
}
192-
193-
return "\n";
194-
}
195-
196150
}

src/fr/adrienbrault/idea/symfony2plugin/translation/dict/TranslationUtil.java

+15
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,21 @@ static public PsiElement[] getDomainFilePsiElements(Project project, String doma
5151
return psiElements.toArray(new PsiElement[psiElements.size()]);
5252
}
5353

54+
static public List<PsiFile> getAllTranslationFiles(Project project) {
55+
56+
DomainMappings domainMappings = ServiceXmlParserFactory.getInstance(project, DomainMappings.class);
57+
List<PsiFile> psiElements = new ArrayList<PsiFile>();
58+
59+
for(DomainFileMap domain: domainMappings.getDomainFileMaps()) {
60+
PsiFile psiFile = domain.getPsiFile(project);
61+
if(psiFile instanceof YAMLFile) {
62+
psiElements.add(psiFile);
63+
}
64+
}
65+
66+
return psiElements;
67+
}
68+
5469

5570
public static PsiElement[] getTranslationPsiElements(final Project project, final String translationKey, String domain) {
5671

0 commit comments

Comments
 (0)