Merge pull request #345 from JPilson/feature/LocalizeSelectedString
feat(Localize Selected): add LocalizeItAction for localizing selected…
This commit is contained in:
commit
8ddc7ff6ac
@ -0,0 +1,77 @@
|
||||
package de.marhali.easyi18n.action;
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import de.marhali.easyi18n.dialog.AddDialog;
|
||||
import de.marhali.easyi18n.model.KeyPath;
|
||||
import de.marhali.easyi18n.settings.ProjectSettingsService;
|
||||
import de.marhali.easyi18n.util.DocumentUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents an action to localize text in the editor.
|
||||
*/
|
||||
class LocalizeItAction extends AnAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
|
||||
DataContext dataContext = anActionEvent.getDataContext();
|
||||
Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
|
||||
if (editor == null) return;
|
||||
String text = editor.getSelectionModel().getSelectedText();
|
||||
|
||||
if (text == null || text.isEmpty()) return;
|
||||
|
||||
|
||||
if ((text.startsWith("\"") && text.endsWith("\"")) || (text.startsWith("'") && text.endsWith("'"))) {
|
||||
text = text.substring(1);
|
||||
text = text.substring(0, text.length() - 1);
|
||||
|
||||
}
|
||||
Project project = anActionEvent.getProject();
|
||||
if (project == null) {
|
||||
throw new RuntimeException("Project is null!");
|
||||
}
|
||||
|
||||
AddDialog dialog = new AddDialog(project, new KeyPath(text), text, (key) -> replaceSelectedText(project, editor, key));
|
||||
dialog.showAndHandle();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the selected text in the editor with a new text generated from the provided key.
|
||||
*
|
||||
* @param project the project where the editor belongs
|
||||
* @param editor the editor where the text is selected
|
||||
* @param key the key used to generate the replacement text
|
||||
*/
|
||||
private void replaceSelectedText(Project project, @NotNull Editor editor, @NotNull String key) {
|
||||
int selectionStart = editor.getSelectionModel().getSelectionStart();
|
||||
int selectionEnd = editor.getSelectionModel().getSelectionEnd();
|
||||
String flavorTemplate = ProjectSettingsService.get(project).getState().getFlavorTemplate();
|
||||
DocumentUtil documentUtil = new DocumentUtil(editor.getDocument());
|
||||
String replacement = buildReplacement(flavorTemplate, key, documentUtil);
|
||||
WriteCommandAction.runWriteCommandAction(editor.getProject(), () -> documentUtil.getDocument().replaceString(selectionStart, selectionEnd, replacement));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a replacement string based on the provided flavor template, key, and document util.
|
||||
*
|
||||
* @param flavorTemplate the flavor template string
|
||||
* @param key the key used to generate the replacement text
|
||||
* @param documentUtil the document util object used to determine the document type
|
||||
* @return the built replacement string
|
||||
*/
|
||||
private String buildReplacement(String flavorTemplate, String key, DocumentUtil documentUtil) {
|
||||
if (documentUtil.isVue() || documentUtil.isJsOrTs()) return flavorTemplate + "('" + key + "')";
|
||||
|
||||
return flavorTemplate + "(\"" + key + "\")";
|
||||
}
|
||||
}
|
@ -13,6 +13,8 @@ import de.marhali.easyi18n.settings.ProjectSettingsService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Dialog to create a new translation with all associated locale values.
|
||||
* Supports optional prefill technique for translation key or locale value.
|
||||
@ -20,6 +22,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public class AddDialog extends TranslationDialog {
|
||||
|
||||
private Consumer<String> onCreated;
|
||||
|
||||
/**
|
||||
* Constructs a new create dialog with prefilled fields
|
||||
* @param project Opened project
|
||||
@ -35,6 +39,16 @@ public class AddDialog extends TranslationDialog {
|
||||
|
||||
setTitle(bundle.getString("action.add"));
|
||||
}
|
||||
public AddDialog(@NotNull Project project, @Nullable KeyPath prefillKey, @Nullable String prefillLocale,Consumer<String> onCreated) {
|
||||
super(project, new Translation(prefillKey != null ? prefillKey : new KeyPath(),
|
||||
prefillLocale != null
|
||||
? new TranslationValue(ProjectSettingsService.get(project).getState().getPreviewLocale(), prefillLocale)
|
||||
: null)
|
||||
);
|
||||
|
||||
this.onCreated = onCreated;
|
||||
setTitle(bundle.getString("action.add"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new create dialog without prefilled fields.
|
||||
@ -47,6 +61,7 @@ public class AddDialog extends TranslationDialog {
|
||||
@Override
|
||||
protected @Nullable TranslationUpdate handleExit(int exitCode) {
|
||||
if(exitCode == DialogWrapper.OK_EXIT_CODE) {
|
||||
if(onCreated != null) onCreated.accept(this.getKeyField().getText());
|
||||
return new TranslationCreate(getState());
|
||||
}
|
||||
return null;
|
||||
|
@ -37,6 +37,10 @@ abstract class TranslationDialog extends DialogWrapper {
|
||||
protected final @NotNull KeyPathConverter converter;
|
||||
protected final @NotNull Translation origin;
|
||||
|
||||
public JTextField getKeyField() {
|
||||
return keyField;
|
||||
}
|
||||
|
||||
protected final JTextField keyField;
|
||||
protected final Map<String, JTextField> localeValueFields;
|
||||
|
||||
|
@ -33,4 +33,5 @@ public interface ProjectSettings {
|
||||
|
||||
// Experimental Configuration
|
||||
boolean isAlwaysFold();
|
||||
String getFlavorTemplate();
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* Configuration panel with all possible options for this plugin.
|
||||
*
|
||||
* @author marhali
|
||||
*/
|
||||
public class ProjectSettingsComponent extends ProjectSettingsComponentState {
|
||||
@ -64,10 +65,12 @@ public class ProjectSettingsComponent extends ProjectSettingsComponentState {
|
||||
.addVerticalGap(24)
|
||||
.addComponent(new TitledSeparator(bundle.getString("settings.experimental.title")))
|
||||
.addComponent(constructAlwaysFoldField())
|
||||
.addLabeledComponent(bundle.getString("settings.experimental.flavor-template"), constructFlavorTemplate(), 1, false)
|
||||
.addComponentFillVertically(new JPanel(), 0)
|
||||
.getPanel();
|
||||
}
|
||||
|
||||
|
||||
private JComponent constructPresetField() {
|
||||
preset = new ComboBox<>(Preset.values());
|
||||
preset.setToolTipText(bundle.getString("settings.preset.tooltip"));
|
||||
@ -219,9 +222,15 @@ public class ProjectSettingsComponent extends ProjectSettingsComponentState {
|
||||
return alwaysFold;
|
||||
}
|
||||
|
||||
private JComponent constructFlavorTemplate() {
|
||||
flavorTemplate = new ExtendableTextField(20);
|
||||
flavorTemplate.setToolTipText(bundle.getString("settings.experimental.flavor-template-tooltip"));
|
||||
return flavorTemplate;
|
||||
}
|
||||
|
||||
private ItemListener handleParserChange() {
|
||||
return e -> {
|
||||
if(e.getStateChange() == ItemEvent.SELECTED) {
|
||||
if (e.getStateChange() == ItemEvent.SELECTED) {
|
||||
// Automatically suggest file pattern option on parser change
|
||||
ParserStrategyType newStrategy = ParserStrategyType.fromIndex(parserStrategy.getSelectedIndex());
|
||||
filePattern.setText(newStrategy.getExampleFilePattern());
|
||||
|
@ -40,6 +40,8 @@ public class ProjectSettingsComponentState {
|
||||
// Experimental configuration
|
||||
protected JCheckBox alwaysFold;
|
||||
|
||||
protected JTextField flavorTemplate;
|
||||
|
||||
protected ProjectSettingsState getState() {
|
||||
// Every field needs to provide its state
|
||||
ProjectSettingsState state = new ProjectSettingsState();
|
||||
@ -63,6 +65,7 @@ public class ProjectSettingsComponentState {
|
||||
state.setAssistance(assistance.isSelected());
|
||||
|
||||
state.setAlwaysFold(alwaysFold.isSelected());
|
||||
state.setFlavorTemplate(flavorTemplate.getText());
|
||||
|
||||
return state;
|
||||
}
|
||||
@ -88,5 +91,7 @@ public class ProjectSettingsComponentState {
|
||||
assistance.setSelected(state.isAssistance());
|
||||
|
||||
alwaysFold.setSelected(state.isAlwaysFold());
|
||||
|
||||
flavorTemplate.setText(state.getFlavorTemplate());
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,15 @@ public class ProjectSettingsState implements ProjectSettings {
|
||||
// Experimental configuration
|
||||
@Property private Boolean alwaysFold;
|
||||
|
||||
/**
|
||||
* The `flavorTemplate` specifies the format used for replacing strings with their i18n (internationalization) counterparts.
|
||||
* For example:
|
||||
* In many situations, the default representation for i18n follows the `$i18n.t('key')` pattern. However, this can vary depending on
|
||||
* the specific framework or developers' preferences for handling i18n. The ability to dynamically change this template adds flexibility and customization
|
||||
* to cater to different i18n handling methods.
|
||||
*/
|
||||
@Property private String flavorTemplate;
|
||||
|
||||
public ProjectSettingsState() {
|
||||
this(new DefaultPreset());
|
||||
}
|
||||
@ -65,6 +74,7 @@ public class ProjectSettingsState implements ProjectSettings {
|
||||
this.assistance = defaults.isAssistance();
|
||||
|
||||
this.alwaysFold = defaults.isAlwaysFold();
|
||||
this.flavorTemplate = defaults.getFlavorTemplate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -143,6 +153,11 @@ public class ProjectSettingsState implements ProjectSettings {
|
||||
return alwaysFold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFlavorTemplate() {
|
||||
return this.flavorTemplate;
|
||||
}
|
||||
|
||||
public void setLocalesDirectory(String localesDirectory) {
|
||||
this.localesDirectory = localesDirectory;
|
||||
}
|
||||
@ -202,6 +217,9 @@ public class ProjectSettingsState implements ProjectSettings {
|
||||
public void setAlwaysFold(Boolean alwaysFold) {
|
||||
this.alwaysFold = alwaysFold;
|
||||
}
|
||||
public void setFlavorTemplate(String flavorTemplate){
|
||||
this.flavorTemplate = flavorTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
@ -222,7 +240,8 @@ public class ProjectSettingsState implements ProjectSettings {
|
||||
&& Objects.equals(previewLocale, that.previewLocale)
|
||||
&& Objects.equals(nestedKeys, that.nestedKeys)
|
||||
&& Objects.equals(assistance, that.assistance)
|
||||
&& Objects.equals(alwaysFold, that.alwaysFold);
|
||||
&& Objects.equals(alwaysFold, that.alwaysFold)
|
||||
&& Objects.equals(flavorTemplate,that.flavorTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -230,7 +249,7 @@ public class ProjectSettingsState implements ProjectSettings {
|
||||
return Objects.hash(
|
||||
localesDirectory, folderStrategy, parserStrategy, filePattern, includeSubDirs,
|
||||
sorting, namespaceDelimiter, sectionDelimiter, contextDelimiter, pluralDelimiter,
|
||||
defaultNamespace, previewLocale, nestedKeys, assistance, alwaysFold
|
||||
defaultNamespace, previewLocale, nestedKeys, assistance, alwaysFold,flavorTemplate
|
||||
);
|
||||
}
|
||||
|
||||
@ -252,6 +271,7 @@ public class ProjectSettingsState implements ProjectSettings {
|
||||
", nestedKeys=" + nestedKeys +
|
||||
", assistance=" + assistance +
|
||||
", alwaysFold=" + alwaysFold +
|
||||
", flavorTemplate=" + flavorTemplate +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -86,4 +86,9 @@ public class DefaultPreset implements ProjectSettings {
|
||||
public boolean isAlwaysFold() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFlavorTemplate() {
|
||||
return "$i18n.t";
|
||||
}
|
||||
}
|
||||
|
@ -86,4 +86,9 @@ public class ReactI18NextPreset implements ProjectSettings {
|
||||
public boolean isAlwaysFold() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFlavorTemplate() {
|
||||
return "$i18n.t";
|
||||
}
|
||||
}
|
||||
|
@ -85,4 +85,9 @@ public class VueI18nPreset implements ProjectSettings {
|
||||
public boolean isAlwaysFold() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFlavorTemplate() {
|
||||
return "$i18n.t";
|
||||
}
|
||||
}
|
||||
|
38
src/main/java/de/marhali/easyi18n/util/DocumentUtil.java
Normal file
38
src/main/java/de/marhali/easyi18n/util/DocumentUtil.java
Normal file
@ -0,0 +1,38 @@
|
||||
package de.marhali.easyi18n.util;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
|
||||
|
||||
public class DocumentUtil {
|
||||
protected Document document;
|
||||
FileType fileType;
|
||||
|
||||
public Document getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
public void setDocument(Document document) {
|
||||
this.document = document;
|
||||
FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
|
||||
VirtualFile virtualFile = fileDocumentManager.getFile(document);
|
||||
if (virtualFile != null) {
|
||||
fileType = virtualFile.getFileType();
|
||||
}
|
||||
}
|
||||
|
||||
public DocumentUtil(Document document) {
|
||||
setDocument(document);
|
||||
}
|
||||
|
||||
public boolean isJsOrTs() {
|
||||
return (fileType.getDefaultExtension().contains("js") || fileType.getDescription().contains("ts"));
|
||||
}
|
||||
|
||||
public boolean isVue() {
|
||||
return fileType.getDefaultExtension().contains("vue");
|
||||
}
|
||||
|
||||
}
|
@ -22,6 +22,13 @@
|
||||
>
|
||||
<add-to-group group-id="NewGroup"/>
|
||||
</action>
|
||||
<action id="de.marhali.easyi18n.action.LocalizeItAction"
|
||||
class="de.marhali.easyi18n.action.LocalizeItAction"
|
||||
text="Localize It"
|
||||
description="Apply localization to the selected string"
|
||||
icon="/icons/translate13.svg">
|
||||
<add-to-group group-id="EditorPopupMenu" anchor="last"/>
|
||||
</action>
|
||||
</actions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
|
@ -60,6 +60,8 @@ settings.editor.assistance.tooltip=Activates editor support to reference, auto-c
|
||||
settings.experimental.title=Experimental Configuration
|
||||
settings.experimental.always-fold.title=Always fold translation keys
|
||||
settings.experimental.always-fold.tooltip=Forces the editor to always display the value behind a translation key. The value cannot be unfolded when this function is active.
|
||||
settings.experimental.flavor-template =I18n flavor template
|
||||
settings.experimental.flavor-template-tooltip = Specify How to replace strings with i18n representation.
|
||||
error.io=An error occurred while processing translation files. \n\
|
||||
Config: {0} => {1} ({2}) \n\
|
||||
Path: {3} \n\
|
||||
|
Loading…
x
Reference in New Issue
Block a user