From 9dfaf7436fa82352a30036f5aa973bcaaf0ff881 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 1 Mar 2026 17:12:02 +0200 Subject: [PATCH 1/6] Run initializr localization tests from build.sh --- scripts/initializr/build.sh | 35 +++++ .../initializr/model/GeneratorModel.java | 35 +++-- ...neratorModelLocalizationPackagingTest.java | 143 ++++++++++++++++++ .../model/GeneratorModelMatrixTest.java | 9 +- 4 files changed, 208 insertions(+), 14 deletions(-) create mode 100644 scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelLocalizationPackagingTest.java diff --git a/scripts/initializr/build.sh b/scripts/initializr/build.sh index 8a99f56684..b356c698ba 100755 --- a/scripts/initializr/build.sh +++ b/scripts/initializr/build.sh @@ -1,16 +1,37 @@ #!/bin/bash set -e MVNW="./mvnw" +RUN_TESTS_DEFAULT="true" + +function should_run_tests { + if [ "$INITIALIZR_RUN_TESTS" == "" ]; then + [ "$RUN_TESTS_DEFAULT" == "true" ] + return + fi + [ "$INITIALIZR_RUN_TESTS" == "true" ] +} + +function run_common_tests { + if should_run_tests; then + "$MVNW" "-pl" "common" "-am" "test" "-DskipTests=false" "-DfailIfNoTests=false" "-Dtest=GeneratorModelMatrixTest,GeneratorModelLocalizationPackagingTest" "-U" "-e" + fi +} function mac_desktop { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=javase" "-Dcodename1.buildTarget=mac-os-x-desktop" "-U" "-e" } function windows_desktop { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=javase" "-Dcodename1.buildTarget=windows-desktop" "-U" "-e" } function windows_device { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=win" "-Dcodename1.buildTarget=windows-device" "-U" "-e" } @@ -19,14 +40,20 @@ function uwp { "windows_device" } function javascript { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=javascript" "-Dcodename1.buildTarget=javascript" "-U" "-e" } function android { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=android" "-Dcodename1.buildTarget=android-device" "-U" "-e" } function xcode { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=ios" "-Dcodename1.buildTarget=ios-source" "-U" "-e" } @@ -34,18 +61,26 @@ function ios_source { "xcode" } function android_source { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=android" "-Dcodename1.buildTarget=android-source" "-U" "-e" } function ios { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=ios" "-Dcodename1.buildTarget=ios-device" "-U" "-e" } function ios_release { + run_common_tests + "$MVNW" "package" "-DskipTests" "-Dcodename1.platform=ios" "-Dcodename1.buildTarget=ios-device-release" "-U" "-e" } function jar { + run_common_tests + "$MVNW" "-Pexecutable-jar" "package" "-Dcodename1.platform=javase" "-DskipTests" "-U" "-e" } diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java index e555b41095..ac7aa39209 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java @@ -90,25 +90,24 @@ private void addLocalizationEntries(Map mergedEntries) throws IO if (!isBareTemplate() || !options.includeLocalizationBundles) { return; } - copySingleTextEntryToMap( - "common/src/main/resources/messages.properties", - readResourceToString("/messages.properties"), - mergedEntries, - ZipEntryType.COMMON - ); + addLocalizationEntry(mergedEntries, "messages.properties"); for (ProjectOptions.PreviewLanguage language : ProjectOptions.PreviewLanguage.values()) { if (language == ProjectOptions.PreviewLanguage.ENGLISH) { continue; } - copySingleTextEntryToMap( - "common/src/main/resources/messages_" + language.bundleSuffix + ".properties", - readResourceToString("/messages_" + language.bundleSuffix + ".properties"), - mergedEntries, - ZipEntryType.COMMON - ); + addLocalizationEntry(mergedEntries, "messages_" + language.bundleSuffix + ".properties"); } } + private void addLocalizationEntry(Map mergedEntries, String fileName) throws IOException { + copySingleTextEntryToMap( + "common/src/main/resources/" + fileName, + readRequiredResourceToString("/" + fileName), + mergedEntries, + ZipEntryType.COMMON + ); + } + private void copyZipEntriesToMap(String zipResource, Map mergedEntries, ZipEntryType zipType) throws IOException { try(ZipInputStream zis = new ZipInputStream(getResourceAsStream(zipResource))) { ZipEntry entry = zis.getNextEntry(); @@ -462,6 +461,18 @@ private static String readResourceToString(String resourcePath) throws IOExcepti } } + static String readRequiredResourceToString(String resourcePath) throws IOException { + InputStream inputStream = getResourceAsStream(resourcePath); + if (inputStream == null) { + throw new IOException("Missing required resource " + resourcePath); + } + try { + return readToStringNoClose(inputStream); + } finally { + inputStream.close(); + } + } + private static String readToStringNoClose(InputStream is) throws IOException { return StringUtil.newString(readToBytesNoClose(is)); } diff --git a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelLocalizationPackagingTest.java b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelLocalizationPackagingTest.java new file mode 100644 index 0000000000..e07f32e781 --- /dev/null +++ b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelLocalizationPackagingTest.java @@ -0,0 +1,143 @@ +package com.codename1.initializr.model; + +import com.codename1.io.Util; +import com.codename1.testing.AbstractTest; +import net.sf.zipme.ZipEntry; +import net.sf.zipme.ZipInputStream; + +import javax.tools.JavaCompiler; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; + +public class GeneratorModelLocalizationPackagingTest extends AbstractTest { + @Override + public boolean runTest() throws Exception { + byte[] zip = createProjectZip(); + Map entries = readZipEntries(zip); + + File classesRoot = Files.createTempDirectory("cn1-localization-packaging").toFile(); + try { + compileDisplayClass(classesRoot); + writeGeneratedLocalizationResources(entries, classesRoot); + assertResourcesLoadFromDisplayClass(classesRoot); + } finally { + Util.cleanup(classesRoot.getAbsolutePath()); + } + return true; + } + + private static byte[] createProjectZip() throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + GeneratorModel.create( + IDE.INTELLIJ, + Template.BAREBONES, + "PackagingProbeApp", + "com.example.packaging" + ).writeProjectZip(output); + return output.toByteArray(); + } + + private static Map readZipEntries(byte[] zipData) throws IOException { + Map entries = new HashMap(); + ByteArrayInputStream input = new ByteArrayInputStream(zipData); + ZipInputStream zis = new ZipInputStream(input); + try { + ZipEntry entry = zis.getNextEntry(); + while (entry != null) { + if (!entry.isDirectory()) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + Util.copyNoClose(zis, bos, 8192); + entries.put(entry.getName(), bos.toByteArray()); + bos.close(); + } + zis.closeEntry(); + entry = zis.getNextEntry(); + } + } finally { + zis.close(); + input.close(); + } + return entries; + } + + private static void compileDisplayClass(File classesRoot) throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + throw new IOException("JDK compiler is required to run packaging test"); + } + File sourceRoot = new File(classesRoot, "src"); + File packageDir = new File(sourceRoot, "com/example/packaging"); + packageDir.mkdirs(); + + File javaFile = new File(packageDir, "DisplayClass.java"); + String source = "package com.example.packaging; public class DisplayClass {}"; + Files.write(javaFile.toPath(), source.getBytes(StandardCharsets.UTF_8)); + + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + try { + fileManager.setLocation(StandardLocation.CLASS_OUTPUT, java.util.Collections.singletonList(classesRoot)); + Boolean ok = compiler.getTask(null, fileManager, null, null, null, + fileManager.getJavaFileObjectsFromFiles(java.util.Collections.singletonList(javaFile))).call(); + if (!Boolean.TRUE.equals(ok)) { + throw new IOException("Failed to compile display class for packaging test"); + } + } finally { + fileManager.close(); + Util.cleanup(sourceRoot.getAbsolutePath()); + } + } + + private static void writeGeneratedLocalizationResources(Map entries, File classesRoot) throws IOException { + writeResource(entries, classesRoot, "messages.properties"); + for (ProjectOptions.PreviewLanguage language : ProjectOptions.PreviewLanguage.values()) { + if (language == ProjectOptions.PreviewLanguage.ENGLISH) { + continue; + } + writeResource(entries, classesRoot, "messages_" + language.bundleSuffix + ".properties"); + } + } + + private static void writeResource(Map entries, File classesRoot, String resourceName) throws IOException { + byte[] data = entries.get("common/src/main/resources/" + resourceName); + if (data == null) { + throw new IOException("Generated project is missing localization resource " + resourceName); + } + try (FileOutputStream fos = new FileOutputStream(new File(classesRoot, resourceName))) { + fos.write(data); + } + } + + private void assertResourcesLoadFromDisplayClass(File classesRoot) throws Exception { + URLClassLoader loader = new URLClassLoader(new URL[]{classesRoot.toURI().toURL()}, null); + try { + Class displayClass = Class.forName("com.example.packaging.DisplayClass", true, loader); + assertNotNull(displayClass.getResourceAsStream("/messages.properties"), "Default bundle should resolve from display class"); + for (ProjectOptions.PreviewLanguage language : ProjectOptions.PreviewLanguage.values()) { + if (language == ProjectOptions.PreviewLanguage.ENGLISH) { + continue; + } + String resource = "/messages_" + language.bundleSuffix + ".properties"; + InputStream in = displayClass.getResourceAsStream(resource); + assertNotNull(in, "Bundle should resolve from display class: " + resource); + if (in != null) { + in.close(); + } + } + } finally { + loader.close(); + } + } +} diff --git a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java index df6d831423..540d969526 100644 --- a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java +++ b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java @@ -155,8 +155,13 @@ private void assertMainSourceFile(Map entries, Template template private void assertLocalizationBundles(Map entries, Template template) { if (template == Template.BAREBONES || template == Template.KOTLIN) { assertNotNull(entries.get("common/src/main/resources/messages.properties"), "Barebones templates should include default localization bundle"); - assertNotNull(entries.get("common/src/main/resources/messages_ar.properties"), "Barebones templates should include Arabic localization bundle"); - assertNotNull(entries.get("common/src/main/resources/messages_he.properties"), "Barebones templates should include Hebrew localization bundle"); + for (ProjectOptions.PreviewLanguage language : ProjectOptions.PreviewLanguage.values()) { + if (language == ProjectOptions.PreviewLanguage.ENGLISH) { + continue; + } + String path = "common/src/main/resources/messages_" + language.bundleSuffix + ".properties"; + assertNotNull(entries.get(path), "Missing expected localization bundle for " + language.bundleSuffix); + } return; } assertNull(entries.get("common/src/main/resources/messages.properties"), "Non-bare templates should not receive default localization bundle"); From 9d7eca8d46df8dd0af9d98e7364d55ff5f9b950c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 1 Mar 2026 17:34:06 +0200 Subject: [PATCH 2/6] Fix JS localization fallback when locale bundle is absent --- .../initializr/model/GeneratorModel.java | 30 ++++++++++++++-- .../initializr/ui/TemplatePreviewPanel.java | 36 +++++++++++-------- .../model/GeneratorModelMatrixTest.java | 2 ++ 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java index ac7aa39209..fa2136da89 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java @@ -215,7 +215,20 @@ private String injectJavaLocalizationBootstrap(String content) { String method = "\n @Override\n" + " public void init(Object context) {\n" + " String language = L10NManager.getInstance().getLanguage();\n" - + " Hashtable bundle = Resources.getGlobalResources().getL10N(\"messages\", language);\n" + + " Hashtable bundle = null;\n" + + " try {\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language);\n" + + " } catch (RuntimeException ignored) {}\n" + + " if (bundle == null && language != null && language.indexOf('_') > 0) {\n" + + " try {\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')));\n" + + " } catch (RuntimeException ignored) {}\n" + + " }\n" + + " if (bundle == null) {\n" + + " try {\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"\");\n" + + " } catch (RuntimeException ignored) {}\n" + + " }\n" + " UIManager.getInstance().setBundle(bundle);\n" + " }\n\n"; int firstBrace = content.indexOf('{'); @@ -232,7 +245,20 @@ private String injectKotlinLocalizationBootstrap(String content) { content = StringUtil.replaceAll(content, "import com.codename1.system.Lifecycle\n", "import com.codename1.system.Lifecycle\nimport com.codename1.l10n.L10NManager\nimport com.codename1.ui.plaf.UIManager\nimport com.codename1.ui.util.Resources\nimport java.util.Hashtable\n"); String method = "\n override fun init(context: Any?) {\n" + " val language = L10NManager.getInstance().language\n" - + " val bundle: Hashtable? = Resources.getGlobalResources().getL10N(\"messages\", language)\n" + + " var bundle: Hashtable? = null\n" + + " try {\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language)\n" + + " } catch (ignored: RuntimeException) {}\n" + + " if (bundle == null && language != null && language.indexOf('_') > 0) {\n" + + " try {\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')))\n" + + " } catch (ignored: RuntimeException) {}\n" + + " }\n" + + " if (bundle == null) {\n" + + " try {\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"\")\n" + + " } catch (ignored: RuntimeException) {}\n" + + " }\n" + " UIManager.getInstance().setBundle(bundle)\n" + " }\n\n"; int firstBrace = content.indexOf('{'); diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java index 647e1ebdc6..751bf044a2 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java @@ -3,7 +3,6 @@ import com.codename1.components.ImageViewer; import com.codename1.initializr.model.ProjectOptions; import com.codename1.initializr.model.Template; -import com.codename1.io.Log; import com.codename1.io.Properties; import com.codename1.ui.Button; import com.codename1.ui.Component; @@ -102,20 +101,20 @@ private Hashtable findBundle(ProjectOptions.PreviewLanguage lang } Resources resources = Resources.getGlobalResources(); if (resources != null) { - try { - Hashtable exact = resources.getL10N("messages", language.bundleSuffix); - if (exact != null) { - return exact; - } - int split = language.bundleSuffix.indexOf('_'); - if (split > 0) { - Hashtable languageOnly = resources.getL10N("messages", language.bundleSuffix.substring(0, split)); - if (languageOnly != null) { - return languageOnly; - } + Hashtable bundle = tryGetBundle(resources, language.bundleSuffix); + if (bundle != null) { + return bundle; + } + int split = language.bundleSuffix.indexOf('_'); + if (split > 0) { + bundle = tryGetBundle(resources, language.bundleSuffix.substring(0, split)); + if (bundle != null) { + return bundle; } - } catch (RuntimeException err) { - Log.e(err); + } + bundle = tryGetBundle(resources, ""); + if (bundle != null) { + return bundle; } } @@ -132,6 +131,14 @@ private Hashtable findBundle(ProjectOptions.PreviewLanguage lang return null; } + + private Hashtable tryGetBundle(Resources resources, String localeSuffix) { + try { + return resources.getL10N("messages", localeSuffix); + } catch (RuntimeException err) { + return null; + } + } private Hashtable loadBundleProperties(String resourcePath) { try (InputStream input = getResourceAsStream(resourcePath)) { if (input == null) { @@ -146,7 +153,6 @@ private Hashtable loadBundleProperties(String resourcePath) { } return out; } catch (Exception err) { - Log.e(err); return null; } } diff --git a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java index 540d969526..9b1e4bd944 100644 --- a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java +++ b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java @@ -139,6 +139,8 @@ private void assertMainSourceFile(Map entries, Template template if (template == Template.BAREBONES || template == Template.KOTLIN) { assertContains(mainSource, "setBundle", "Barebones starter should install localization bundle"); assertContains(mainSource, "messages", "Barebones starter should load i18n messages properties"); + assertContains(mainSource, "getL10N(\"messages\", \"\")", "Barebones starter should fallback to default bundle when locale-specific bundle is missing"); + assertContains(mainSource, "RuntimeException", "Barebones starter should guard against runtime lookup failures in localization loading"); } if (template == Template.GRUB) { String grubModel = getText(entries, "common/src/main/java/" + packagePath + "/models/AccountModel.java"); From 9c78e79a9a1bfab868dac20cec6ec0d2619950d4 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 1 Mar 2026 17:34:11 +0200 Subject: [PATCH 3/6] Add English localization bundle alias instead of swallowing lookup errors --- .../initializr/model/GeneratorModel.java | 32 +++++++------------ .../initializr/ui/TemplatePreviewPanel.java | 16 +++------- .../src/main/resources/messages_en.properties | 7 ++++ ...neratorModelLocalizationPackagingTest.java | 2 ++ .../model/GeneratorModelMatrixTest.java | 2 +- 5 files changed, 26 insertions(+), 33 deletions(-) create mode 100644 scripts/initializr/common/src/main/resources/messages_en.properties diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java index fa2136da89..b946d8b73b 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java @@ -91,6 +91,12 @@ private void addLocalizationEntries(Map mergedEntries) throws IO return; } addLocalizationEntry(mergedEntries, "messages.properties"); + copySingleTextEntryToMap( + "common/src/main/resources/messages_en.properties", + readRequiredResourceToString("/messages.properties"), + mergedEntries, + ZipEntryType.COMMON + ); for (ProjectOptions.PreviewLanguage language : ProjectOptions.PreviewLanguage.values()) { if (language == ProjectOptions.PreviewLanguage.ENGLISH) { continue; @@ -215,19 +221,12 @@ private String injectJavaLocalizationBootstrap(String content) { String method = "\n @Override\n" + " public void init(Object context) {\n" + " String language = L10NManager.getInstance().getLanguage();\n" - + " Hashtable bundle = null;\n" - + " try {\n" - + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language);\n" - + " } catch (RuntimeException ignored) {}\n" + + " Hashtable bundle = Resources.getGlobalResources().getL10N(\"messages\", language);\n" + " if (bundle == null && language != null && language.indexOf('_') > 0) {\n" - + " try {\n" - + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')));\n" - + " } catch (RuntimeException ignored) {}\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')));\n" + " }\n" + " if (bundle == null) {\n" - + " try {\n" - + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"\");\n" - + " } catch (RuntimeException ignored) {}\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"\");\n" + " }\n" + " UIManager.getInstance().setBundle(bundle);\n" + " }\n\n"; @@ -245,19 +244,12 @@ private String injectKotlinLocalizationBootstrap(String content) { content = StringUtil.replaceAll(content, "import com.codename1.system.Lifecycle\n", "import com.codename1.system.Lifecycle\nimport com.codename1.l10n.L10NManager\nimport com.codename1.ui.plaf.UIManager\nimport com.codename1.ui.util.Resources\nimport java.util.Hashtable\n"); String method = "\n override fun init(context: Any?) {\n" + " val language = L10NManager.getInstance().language\n" - + " var bundle: Hashtable? = null\n" - + " try {\n" - + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language)\n" - + " } catch (ignored: RuntimeException) {}\n" + + " var bundle: Hashtable? = Resources.getGlobalResources().getL10N(\"messages\", language)\n" + " if (bundle == null && language != null && language.indexOf('_') > 0) {\n" - + " try {\n" - + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')))\n" - + " } catch (ignored: RuntimeException) {}\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')))\n" + " }\n" + " if (bundle == null) {\n" - + " try {\n" - + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"\")\n" - + " } catch (ignored: RuntimeException) {}\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"\")\n" + " }\n" + " UIManager.getInstance().setBundle(bundle)\n" + " }\n\n"; diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java index 751bf044a2..013eca9bf7 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java @@ -101,18 +101,18 @@ private Hashtable findBundle(ProjectOptions.PreviewLanguage lang } Resources resources = Resources.getGlobalResources(); if (resources != null) { - Hashtable bundle = tryGetBundle(resources, language.bundleSuffix); + Hashtable bundle = resources.getL10N("messages", language.bundleSuffix); if (bundle != null) { return bundle; } int split = language.bundleSuffix.indexOf('_'); if (split > 0) { - bundle = tryGetBundle(resources, language.bundleSuffix.substring(0, split)); + bundle = resources.getL10N("messages", language.bundleSuffix.substring(0, split)); if (bundle != null) { return bundle; } } - bundle = tryGetBundle(resources, ""); + bundle = resources.getL10N("messages", ""); if (bundle != null) { return bundle; } @@ -131,14 +131,6 @@ private Hashtable findBundle(ProjectOptions.PreviewLanguage lang return null; } - - private Hashtable tryGetBundle(Resources resources, String localeSuffix) { - try { - return resources.getL10N("messages", localeSuffix); - } catch (RuntimeException err) { - return null; - } - } private Hashtable loadBundleProperties(String resourcePath) { try (InputStream input = getResourceAsStream(resourcePath)) { if (input == null) { @@ -153,7 +145,7 @@ private Hashtable loadBundleProperties(String resourcePath) { } return out; } catch (Exception err) { - return null; + throw new RuntimeException("Failed to load localization bundle " + resourcePath, err); } } diff --git a/scripts/initializr/common/src/main/resources/messages_en.properties b/scripts/initializr/common/src/main/resources/messages_en.properties new file mode 100644 index 0000000000..4f24396858 --- /dev/null +++ b/scripts/initializr/common/src/main/resources/messages_en.properties @@ -0,0 +1,7 @@ +Hi\ World=Hi World +Hello\ World=Hello World +Hello\ Command=Hello Command +Hello\ Codename\ One=Hello Codename One +Welcome\ to\ Codename\ One=Welcome to Codename One +OK=OK +Side\ menu\ is\ not\ available\ in\ embedded\ preview\ mode.=Side menu is not available in embedded preview mode. diff --git a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelLocalizationPackagingTest.java b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelLocalizationPackagingTest.java index e07f32e781..f19957784b 100644 --- a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelLocalizationPackagingTest.java +++ b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelLocalizationPackagingTest.java @@ -102,6 +102,7 @@ private static void compileDisplayClass(File classesRoot) throws IOException { private static void writeGeneratedLocalizationResources(Map entries, File classesRoot) throws IOException { writeResource(entries, classesRoot, "messages.properties"); + writeResource(entries, classesRoot, "messages_en.properties"); for (ProjectOptions.PreviewLanguage language : ProjectOptions.PreviewLanguage.values()) { if (language == ProjectOptions.PreviewLanguage.ENGLISH) { continue; @@ -125,6 +126,7 @@ private void assertResourcesLoadFromDisplayClass(File classesRoot) throws Except try { Class displayClass = Class.forName("com.example.packaging.DisplayClass", true, loader); assertNotNull(displayClass.getResourceAsStream("/messages.properties"), "Default bundle should resolve from display class"); + assertNotNull(displayClass.getResourceAsStream("/messages_en.properties"), "English bundle alias should resolve from display class"); for (ProjectOptions.PreviewLanguage language : ProjectOptions.PreviewLanguage.values()) { if (language == ProjectOptions.PreviewLanguage.ENGLISH) { continue; diff --git a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java index 9b1e4bd944..e5fc7d1d07 100644 --- a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java +++ b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java @@ -140,7 +140,6 @@ private void assertMainSourceFile(Map entries, Template template assertContains(mainSource, "setBundle", "Barebones starter should install localization bundle"); assertContains(mainSource, "messages", "Barebones starter should load i18n messages properties"); assertContains(mainSource, "getL10N(\"messages\", \"\")", "Barebones starter should fallback to default bundle when locale-specific bundle is missing"); - assertContains(mainSource, "RuntimeException", "Barebones starter should guard against runtime lookup failures in localization loading"); } if (template == Template.GRUB) { String grubModel = getText(entries, "common/src/main/java/" + packagePath + "/models/AccountModel.java"); @@ -157,6 +156,7 @@ private void assertMainSourceFile(Map entries, Template template private void assertLocalizationBundles(Map entries, Template template) { if (template == Template.BAREBONES || template == Template.KOTLIN) { assertNotNull(entries.get("common/src/main/resources/messages.properties"), "Barebones templates should include default localization bundle"); + assertNotNull(entries.get("common/src/main/resources/messages_en.properties"), "Barebones templates should include English localization bundle alias for JS lookup"); for (ProjectOptions.PreviewLanguage language : ProjectOptions.PreviewLanguage.values()) { if (language == ProjectOptions.PreviewLanguage.ENGLISH) { continue; From 53f3a2292675127cc12978592dbd4196833360a5 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:32:55 +0200 Subject: [PATCH 4/6] Use English fallback bundle to fix JS load without masking errors --- .../java/com/codename1/initializr/model/GeneratorModel.java | 4 ++-- .../com/codename1/initializr/ui/TemplatePreviewPanel.java | 2 +- .../codename1/initializr/model/GeneratorModelMatrixTest.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java index b946d8b73b..f0ba025a03 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java @@ -226,7 +226,7 @@ private String injectJavaLocalizationBootstrap(String content) { + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')));\n" + " }\n" + " if (bundle == null) {\n" - + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"\");\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"en\");\n" + " }\n" + " UIManager.getInstance().setBundle(bundle);\n" + " }\n\n"; @@ -249,7 +249,7 @@ private String injectKotlinLocalizationBootstrap(String content) { + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')))\n" + " }\n" + " if (bundle == null) {\n" - + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"\")\n" + + " bundle = Resources.getGlobalResources().getL10N(\"messages\", \"en\")\n" + " }\n" + " UIManager.getInstance().setBundle(bundle)\n" + " }\n\n"; diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java index 013eca9bf7..5f4940d1ad 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java @@ -112,7 +112,7 @@ private Hashtable findBundle(ProjectOptions.PreviewLanguage lang return bundle; } } - bundle = resources.getL10N("messages", ""); + bundle = resources.getL10N("messages", "en"); if (bundle != null) { return bundle; } diff --git a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java index e5fc7d1d07..d8764d6ba8 100644 --- a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java +++ b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java @@ -139,7 +139,7 @@ private void assertMainSourceFile(Map entries, Template template if (template == Template.BAREBONES || template == Template.KOTLIN) { assertContains(mainSource, "setBundle", "Barebones starter should install localization bundle"); assertContains(mainSource, "messages", "Barebones starter should load i18n messages properties"); - assertContains(mainSource, "getL10N(\"messages\", \"\")", "Barebones starter should fallback to default bundle when locale-specific bundle is missing"); + assertContains(mainSource, "getL10N(\"messages\", \"en\")", "Barebones starter should fallback to English bundle when locale-specific bundle is missing"); } if (template == Template.GRUB) { String grubModel = getText(entries, "common/src/main/java/" + packagePath + "/models/AccountModel.java"); From 39586e10ffdc1e71061aeb6e15a3279a988d92c8 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:01:06 +0200 Subject: [PATCH 5/6] Default null locale to English in generated localization bootstrap --- .../com/codename1/initializr/model/GeneratorModel.java | 8 +++++++- .../initializr/model/GeneratorModelMatrixTest.java | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java index f0ba025a03..0b295528f7 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java @@ -221,6 +221,9 @@ private String injectJavaLocalizationBootstrap(String content) { String method = "\n @Override\n" + " public void init(Object context) {\n" + " String language = L10NManager.getInstance().getLanguage();\n" + + " if (language == null || language.length() == 0) {\n" + + " language = \"en\";\n" + + " }\n" + " Hashtable bundle = Resources.getGlobalResources().getL10N(\"messages\", language);\n" + " if (bundle == null && language != null && language.indexOf('_') > 0) {\n" + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')));\n" @@ -243,7 +246,10 @@ private String injectKotlinLocalizationBootstrap(String content) { } content = StringUtil.replaceAll(content, "import com.codename1.system.Lifecycle\n", "import com.codename1.system.Lifecycle\nimport com.codename1.l10n.L10NManager\nimport com.codename1.ui.plaf.UIManager\nimport com.codename1.ui.util.Resources\nimport java.util.Hashtable\n"); String method = "\n override fun init(context: Any?) {\n" - + " val language = L10NManager.getInstance().language\n" + + " var language = L10NManager.getInstance().language\n" + + " if (language == null || language.length == 0) {\n" + + " language = \"en\"\n" + + " }\n" + " var bundle: Hashtable? = Resources.getGlobalResources().getL10N(\"messages\", language)\n" + " if (bundle == null && language != null && language.indexOf('_') > 0) {\n" + " bundle = Resources.getGlobalResources().getL10N(\"messages\", language.substring(0, language.indexOf('_')))\n" diff --git a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java index d8764d6ba8..340f4eb8a2 100644 --- a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java +++ b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java @@ -140,6 +140,7 @@ private void assertMainSourceFile(Map entries, Template template assertContains(mainSource, "setBundle", "Barebones starter should install localization bundle"); assertContains(mainSource, "messages", "Barebones starter should load i18n messages properties"); assertContains(mainSource, "getL10N(\"messages\", \"en\")", "Barebones starter should fallback to English bundle when locale-specific bundle is missing"); + assertContains(mainSource, "language == null", "Barebones starter should default language to English when locale is unavailable"); } if (template == Template.GRUB) { String grubModel = getText(entries, "common/src/main/java/" + packagePath + "/models/AccountModel.java"); From c38db02c3b63844d600f6e4b6187b687a55aa002 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:01:11 +0200 Subject: [PATCH 6/6] Avoid Resources.getL10N in initializr preview bundle loading --- .../initializr/ui/TemplatePreviewPanel.java | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java index 5f4940d1ad..fb57ed77e4 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/ui/TemplatePreviewPanel.java @@ -99,28 +99,18 @@ private Hashtable findBundle(ProjectOptions.PreviewLanguage lang if (language == null) { return null; } - Resources resources = Resources.getGlobalResources(); - if (resources != null) { - Hashtable bundle = resources.getL10N("messages", language.bundleSuffix); - if (bundle != null) { - return bundle; - } - int split = language.bundleSuffix.indexOf('_'); - if (split > 0) { - bundle = resources.getL10N("messages", language.bundleSuffix.substring(0, split)); - if (bundle != null) { - return bundle; - } - } - bundle = resources.getL10N("messages", "en"); - if (bundle != null) { - return bundle; - } - } - String[] candidates = language.bundleSuffix.indexOf('_') > 0 - ? new String[]{"/messages_" + language.bundleSuffix + ".properties", "/messages_" + language.bundleSuffix.substring(0, language.bundleSuffix.indexOf('_')) + ".properties", "/messages.properties"} - : new String[]{"/messages_" + language.bundleSuffix + ".properties", "/messages.properties"}; + ? new String[]{ + "/messages_" + language.bundleSuffix + ".properties", + "/messages_" + language.bundleSuffix.substring(0, language.bundleSuffix.indexOf('_')) + ".properties", + "/messages_en.properties", + "/messages.properties" + } + : new String[]{ + "/messages_" + language.bundleSuffix + ".properties", + "/messages_en.properties", + "/messages.properties" + }; for (String path : candidates) { Hashtable loaded = loadBundleProperties(path);