feat(i18n): allow selecting message XML file via file browser
- Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser (TextFieldWithBrowseButton). - Modified I18nUtils to load message files via absolute paths from project settings, improving reliability. - Bumped plugin version to 3.2.2 and updated change notes.
This commit is contained in:
@@ -5,7 +5,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "com.sdk.dynform.tools"
|
group = "com.sdk.dynform.tools"
|
||||||
version = "3.2.0"
|
version = "3.2.2"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -39,6 +39,15 @@ intellijPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
changeNotes = """
|
changeNotes = """
|
||||||
|
<h2>[3.2.2]</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>UI/UX Improvement:</strong> Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.</li>
|
||||||
|
<li><strong>Core Enhancement:</strong> Modified I18nUtils to load message files via absolute paths from project settings, increasing reliability across different project structures.</li>
|
||||||
|
</ul>
|
||||||
|
<h2>[3.2.1]</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Bug Fix:</strong> Resolved a <code>ConcurrentModificationException</code> in the I18n cache mechanism caused by concurrent background thread access.</li>
|
||||||
|
</ul>
|
||||||
<h2>[3.2.0]</h2>
|
<h2>[3.2.0]</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Schema Validation:</strong> Introduced project-level automatic XSD registration. Configure a target folder and namespace prefix to seamlessly map <code>.xsd</code> files to the <code>ExternalResourceManager</code>.</li>
|
<li><strong>Schema Validation:</strong> Introduced project-level automatic XSD registration. Configure a target folder and namespace prefix to seamlessly map <code>.xsd</code> files to the <code>ExternalResourceManager</code>.</li>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.sdk.dynform.tools.config;
|
package com.sdk.dynform.tools.config;
|
||||||
|
|
||||||
|
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
|
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
|
||||||
import com.intellij.openapi.options.Configurable;
|
import com.intellij.openapi.options.Configurable;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.ui.TextComponentAccessor;
|
import com.intellij.openapi.ui.TextComponentAccessor;
|
||||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||||
import com.intellij.openapi.util.NlsContexts;
|
import com.intellij.openapi.util.NlsContexts;
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.sdk.dynform.tools.dynform.DynFormXsdScanner;
|
import com.sdk.dynform.tools.dynform.DynFormXsdScanner;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -20,6 +22,7 @@ public class DynFormConfigurable implements Configurable {
|
|||||||
private JRadioButton inlayBtn;
|
private JRadioButton inlayBtn;
|
||||||
private JRadioButton disabledBtn;
|
private JRadioButton disabledBtn;
|
||||||
private JCheckBox showIconCheck;
|
private JCheckBox showIconCheck;
|
||||||
|
private TextFieldWithBrowseButton i18nMessageFileField;
|
||||||
private TextFieldWithBrowseButton xsdFolderField;
|
private TextFieldWithBrowseButton xsdFolderField;
|
||||||
private JTextField xsdPrefixField;
|
private JTextField xsdPrefixField;
|
||||||
|
|
||||||
@@ -43,27 +46,58 @@ public class DynFormConfigurable implements Configurable {
|
|||||||
gbc.insets = new Insets(5, 5, 5, 5);
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
|
|
||||||
// --- I18n Settings Group ---
|
// --- I18n Settings Group ---
|
||||||
JPanel i18nPanel = new JPanel();
|
JPanel i18nPanel = new JPanel(new GridBagLayout());
|
||||||
i18nPanel.setLayout(new BoxLayout(i18nPanel, BoxLayout.Y_AXIS));
|
|
||||||
i18nPanel.setBorder(BorderFactory.createTitledBorder("Internationalization (i18n)"));
|
i18nPanel.setBorder(BorderFactory.createTitledBorder("Internationalization (i18n)"));
|
||||||
|
|
||||||
i18nPanel.add(new JLabel("Message Display Mode:"));
|
GridBagConstraints i18nGbc = new GridBagConstraints();
|
||||||
|
i18nGbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
i18nGbc.insets = new Insets(2, 2, 2, 2);
|
||||||
|
i18nGbc.gridx = 0;
|
||||||
|
i18nGbc.gridy = 0;
|
||||||
|
i18nGbc.weightx = 0.0;
|
||||||
|
|
||||||
|
i18nPanel.add(new JLabel("Message Display Mode:"), i18nGbc);
|
||||||
|
|
||||||
|
i18nGbc.gridy++;
|
||||||
foldingBtn = new JRadioButton("Folding (Hide key, show translation)");
|
foldingBtn = new JRadioButton("Folding (Hide key, show translation)");
|
||||||
|
i18nPanel.add(foldingBtn, i18nGbc);
|
||||||
|
|
||||||
|
i18nGbc.gridy++;
|
||||||
inlayBtn = new JRadioButton("Inlay Hints (Show translation next to key)");
|
inlayBtn = new JRadioButton("Inlay Hints (Show translation next to key)");
|
||||||
|
i18nPanel.add(inlayBtn, i18nGbc);
|
||||||
|
|
||||||
|
i18nGbc.gridy++;
|
||||||
disabledBtn = new JRadioButton("Disabled");
|
disabledBtn = new JRadioButton("Disabled");
|
||||||
|
i18nPanel.add(disabledBtn, i18nGbc);
|
||||||
|
|
||||||
ButtonGroup group = new ButtonGroup();
|
ButtonGroup group = new ButtonGroup();
|
||||||
group.add(foldingBtn);
|
group.add(foldingBtn);
|
||||||
group.add(inlayBtn);
|
group.add(inlayBtn);
|
||||||
group.add(disabledBtn);
|
group.add(disabledBtn);
|
||||||
|
|
||||||
i18nPanel.add(foldingBtn);
|
i18nGbc.gridy++;
|
||||||
i18nPanel.add(inlayBtn);
|
|
||||||
i18nPanel.add(disabledBtn);
|
|
||||||
|
|
||||||
i18nPanel.add(Box.createVerticalStrut(10));
|
|
||||||
showIconCheck = new JCheckBox("Show icon in code completion");
|
showIconCheck = new JCheckBox("Show icon in code completion");
|
||||||
i18nPanel.add(showIconCheck);
|
i18nPanel.add(showIconCheck, i18nGbc);
|
||||||
|
|
||||||
|
i18nGbc.gridy++;
|
||||||
|
i18nGbc.weightx = 0.0;
|
||||||
|
i18nPanel.add(new JLabel("Message Bundle File:"), i18nGbc);
|
||||||
|
|
||||||
|
i18nGbc.gridx = 1;
|
||||||
|
i18nGbc.weightx = 1.0;
|
||||||
|
i18nMessageFileField = new TextFieldWithBrowseButton();
|
||||||
|
|
||||||
|
FileChooserDescriptor xmlDescriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
|
||||||
|
@Override
|
||||||
|
public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
|
||||||
|
return super.isFileVisible(file, showHiddenFiles) && (file.isDirectory() || "xml".equalsIgnoreCase(file.getExtension()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
i18nMessageFileField.addBrowseFolderListener("Select Message XML File", "Select the main message bundle XML file",
|
||||||
|
project, xmlDescriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
|
||||||
|
|
||||||
|
i18nPanel.add(i18nMessageFileField, i18nGbc);
|
||||||
|
|
||||||
mainPanel.add(i18nPanel, gbc);
|
mainPanel.add(i18nPanel, gbc);
|
||||||
|
|
||||||
@@ -121,6 +155,7 @@ public class DynFormConfigurable implements Configurable {
|
|||||||
DynFormSettings settings = DynFormSettings.getInstance(project);
|
DynFormSettings settings = DynFormSettings.getInstance(project);
|
||||||
return settings.displayMode != getCurrentModeFromUI() ||
|
return settings.displayMode != getCurrentModeFromUI() ||
|
||||||
settings.showIcon != showIconCheck.isSelected() ||
|
settings.showIcon != showIconCheck.isSelected() ||
|
||||||
|
!settings.i18nMessageFile.equals(i18nMessageFileField.getText()) ||
|
||||||
!settings.xsdFolderPath.equals(xsdFolderField.getText()) ||
|
!settings.xsdFolderPath.equals(xsdFolderField.getText()) ||
|
||||||
!settings.xsdPrefix.equals(xsdPrefixField.getText());
|
!settings.xsdPrefix.equals(xsdPrefixField.getText());
|
||||||
}
|
}
|
||||||
@@ -130,6 +165,7 @@ public class DynFormConfigurable implements Configurable {
|
|||||||
DynFormSettings settings = DynFormSettings.getInstance(project);
|
DynFormSettings settings = DynFormSettings.getInstance(project);
|
||||||
settings.displayMode = getCurrentModeFromUI();
|
settings.displayMode = getCurrentModeFromUI();
|
||||||
settings.showIcon = showIconCheck.isSelected();
|
settings.showIcon = showIconCheck.isSelected();
|
||||||
|
settings.i18nMessageFile = i18nMessageFileField.getText();
|
||||||
settings.xsdFolderPath = xsdFolderField.getText();
|
settings.xsdFolderPath = xsdFolderField.getText();
|
||||||
settings.xsdPrefix = xsdPrefixField.getText();
|
settings.xsdPrefix = xsdPrefixField.getText();
|
||||||
|
|
||||||
@@ -155,6 +191,7 @@ public class DynFormConfigurable implements Configurable {
|
|||||||
case DISABLED: disabledBtn.setSelected(true); break;
|
case DISABLED: disabledBtn.setSelected(true); break;
|
||||||
}
|
}
|
||||||
showIconCheck.setSelected(settings.showIcon);
|
showIconCheck.setSelected(settings.showIcon);
|
||||||
|
i18nMessageFileField.setText(settings.i18nMessageFile);
|
||||||
xsdFolderField.setText(settings.xsdFolderPath);
|
xsdFolderField.setText(settings.xsdFolderPath);
|
||||||
xsdPrefixField.setText(settings.xsdPrefix);
|
xsdPrefixField.setText(settings.xsdPrefix);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public class DynFormSettings implements PersistentStateComponent<DynFormSettings
|
|||||||
|
|
||||||
public DisplayMode displayMode = DisplayMode.FOLDING;
|
public DisplayMode displayMode = DisplayMode.FOLDING;
|
||||||
public boolean showIcon = true;
|
public boolean showIcon = true;
|
||||||
|
public String i18nMessageFile = "message.xml";
|
||||||
public String xsdFolderPath = "";
|
public String xsdFolderPath = "";
|
||||||
public String xsdPrefix = "/dynf";
|
public String xsdPrefix = "/dynf";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.sdk.dynform.tools.i18n;
|
package com.sdk.dynform.tools.i18n;
|
||||||
|
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
@@ -9,31 +10,50 @@ import com.intellij.psi.search.FilenameIndex;
|
|||||||
import com.intellij.psi.search.GlobalSearchScope;
|
import com.intellij.psi.search.GlobalSearchScope;
|
||||||
import com.intellij.psi.xml.XmlFile;
|
import com.intellij.psi.xml.XmlFile;
|
||||||
import com.intellij.psi.xml.XmlTag;
|
import com.intellij.psi.xml.XmlTag;
|
||||||
|
import com.sdk.dynform.tools.config.DynFormSettings;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class I18nUtils {
|
public class I18nUtils {
|
||||||
|
|
||||||
private static final List<String> MESSAGE_FILENAMES = Arrays.asList("message.xml", "message_th.xml", "message_en.xml");
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static List<XmlFile> findMessageFiles(@NotNull Project project) {
|
public static List<XmlFile> findMessageFiles(@NotNull Project project) {
|
||||||
|
DynFormSettings settings = DynFormSettings.getInstance(project);
|
||||||
|
String filePath = settings.i18nMessageFile.trim();
|
||||||
|
|
||||||
List<XmlFile> result = new ArrayList<>();
|
List<XmlFile> result = new ArrayList<>();
|
||||||
for (String filename : MESSAGE_FILENAMES) {
|
if (filePath.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the file using its absolute path
|
||||||
|
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(filePath);
|
||||||
|
|
||||||
|
// If not found by absolute path, fallback to searching by filename in the project
|
||||||
|
if (file == null) {
|
||||||
|
String filename = new java.io.File(filePath).getName();
|
||||||
Collection<VirtualFile> files = FilenameIndex.getVirtualFilesByName(filename, GlobalSearchScope.everythingScope(project));
|
Collection<VirtualFile> files = FilenameIndex.getVirtualFilesByName(filename, GlobalSearchScope.everythingScope(project));
|
||||||
for (VirtualFile file : files) {
|
for (VirtualFile vFile : files) {
|
||||||
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
|
addXmlFile(project, vFile, result);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addXmlFile(project, file, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addXmlFile(Project project, VirtualFile virtualFile, List<XmlFile> result) {
|
||||||
|
PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
|
||||||
if (psiFile instanceof XmlFile) {
|
if (psiFile instanceof XmlFile) {
|
||||||
result.add((XmlFile) psiFile);
|
result.add((XmlFile) psiFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Map<String, Map<String, String>> cache = new LinkedHashMap<>();
|
private static final Map<String, Map<String, String>> cache = new ConcurrentHashMap<>();
|
||||||
private static long lastCacheUpdate = 0;
|
private static long lastCacheUpdate = 0;
|
||||||
private static final long CACHE_TIMEOUT = 5000; // 5 seconds
|
private static final long CACHE_TIMEOUT = 5000; // 5 seconds
|
||||||
|
|
||||||
@@ -138,7 +158,7 @@ public class I18nUtils {
|
|||||||
return result.toString().trim();
|
return result.toString().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateCache(@NotNull Project project) {
|
private static synchronized void updateCache(@NotNull Project project) {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
if (currentTime - lastCacheUpdate < CACHE_TIMEOUT && !cache.isEmpty()) {
|
if (currentTime - lastCacheUpdate < CACHE_TIMEOUT && !cache.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -34,6 +34,15 @@
|
|||||||
]]></description>
|
]]></description>
|
||||||
|
|
||||||
<change-notes><![CDATA[
|
<change-notes><![CDATA[
|
||||||
|
<h2>[3.2.2]</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>UI/UX Improvement:</strong> Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.</li>
|
||||||
|
<li><strong>Core Enhancement:</strong> Modified I18nUtils to load message files via absolute paths from project settings, increasing reliability across different project structures.</li>
|
||||||
|
</ul>
|
||||||
|
<h2>[3.2.1]</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Bug Fix:</strong> Resolved a <code>ConcurrentModificationException</code> in the I18n cache mechanism caused by concurrent background thread access.</li>
|
||||||
|
</ul>
|
||||||
<h2>[3.2.0]</h2>
|
<h2>[3.2.0]</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Schema Validation:</strong> Introduced project-level automatic XSD registration. Configure a target folder and namespace prefix to seamlessly map <code>.xsd</code> files to the <code>ExternalResourceManager</code>.</li>
|
<li><strong>Schema Validation:</strong> Introduced project-level automatic XSD registration. Configure a target folder and namespace prefix to seamlessly map <code>.xsd</code> files to the <code>ExternalResourceManager</code>.</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user