Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.33.0,4.0.0)",
org.eclipse.emf.ecore.xmi;bundle-version="2.11.0",
org.eclipse.e4.core.di.extensions;bundle-version="0.13.0",
org.eclipse.swt;bundle-version="[3.133.0,4.0.0)"
Import-Package: com.ibm.icu.util,
jakarta.annotation;version="[2.0.0,4.0.0)",
Import-Package: jakarta.annotation;version="[2.0.0,4.0.0)",
jakarta.inject;version="[2.0.0,3.0.0)",
javax.xml.parsers,
org.eclipse.e4.core.commands,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@

package org.eclipse.ui.internal;

import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -47,6 +45,8 @@
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Locale.Category;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -584,11 +584,7 @@ public static int createAndRunWorkbench(final Display display, final WorkbenchAd
boolean showProgress = PrefUtil.getAPIPreferenceStore()
.getBoolean(IWorkbenchPreferenceConstants.SHOW_PROGRESS_ON_STARTUP);

final String nlExtensions = Platform.getNLExtensions();
if (nlExtensions.length() > 0) {
ULocale.setDefault(Category.FORMAT,
new ULocale(ULocale.getDefault(Category.FORMAT).getBaseName() + nlExtensions));
}
applyNlExtensions(Platform.getNLExtensions());

System.setProperty(org.eclipse.e4.ui.workbench.IWorkbench.XMI_URI_ARG,
"org.eclipse.ui.workbench/LegacyIDE.e4xmi"); //$NON-NLS-1$
Expand Down Expand Up @@ -1902,10 +1898,71 @@ private void initializeGlobalization() {
private void initializeNLExtensions() {
IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore();
if (!store.isDefault(IPreferenceConstants.NL_EXTENSIONS)) {
String nlExtensions = store.getString(IPreferenceConstants.NL_EXTENSIONS);
ULocale.setDefault(Category.FORMAT,
new ULocale(ULocale.getDefault(Category.FORMAT).getBaseName() + nlExtensions));
applyNlExtensions(store.getString(IPreferenceConstants.NL_EXTENSIONS));
}
}

/**
* Mapping from ICU-style long keyword names (used in legacy
* {@code @key=value} locale extension strings) to the corresponding two-letter
* BCP 47 Unicode locale extension keys.
*/
private static final Map<String, String> ICU_TO_BCP47_KEY = Map.ofEntries(
Map.entry("calendar", "ca"), //$NON-NLS-1$ //$NON-NLS-2$
Map.entry("collation", "co"), //$NON-NLS-1$ //$NON-NLS-2$
Map.entry("currency", "cu"), //$NON-NLS-1$ //$NON-NLS-2$
Map.entry("numbers", "nu"), //$NON-NLS-1$ //$NON-NLS-2$
Map.entry("timezone", "tz")); //$NON-NLS-1$ //$NON-NLS-2$
Comment on lines +1910 to +1915
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The mapping of timezone to the BCP 47 key tz may result in a loss of functionality for many existing configurations.

Legacy ICU-style locale extensions often use long IANA timezone IDs (e.g., @timezone=America/New_York). However, BCP 47 Unicode locale extensions (the -u- section used by java.util.Locale) strictly require short CLDR identifiers (e.g., usnyc for America/New_York).

Since Locale.forLanguageTag is used here for a best-effort approach, values containing slashes or exceeding the 8-character subtag limit will be considered ill-formed and likely ignored by the JVM. This is a regression compared to the previous ULocale implementation which could resolve long IDs. If maintaining support for long timezone IDs is required, a manual mapping or a lookup via java.time.ZoneId might be necessary to find the corresponding BCP 47 short ID.


/**
* Applies an ICU-style locale extension string (e.g. {@code @calendar=hebrew})
* to the default {@link Category#FORMAT} locale by translating it to a BCP 47
* Unicode locale extension and re-parsing the resulting language tag.
* <p>
* Supports both ICU long keyword names ({@code calendar}, {@code numbers},
* ...) and two-letter BCP 47 keys. Underscores in values are normalized to
* hyphens so legacy compound values such as {@code islamic_civil} are accepted.
* Unknown keys and ill-formed values are silently dropped, matching the
* previous best-effort behavior of {@code com.ibm.icu.util.ULocale}.
* </p>
* <p>
* Note: long IANA timezone IDs (e.g. {@code @timezone=America/New_York}) are
* not translated. BCP 47 Unicode extensions only accept short CLDR timezone
* identifiers (e.g. {@code @tz=usnyc}), and the JDK does not expose the
* IANA-to-CLDR mapping. Such values are dropped.
* </p>
*/
private static void applyNlExtensions(String nlExtensions) {
if (nlExtensions == null || nlExtensions.isEmpty()) {
return;
}
String body = nlExtensions.startsWith("@") ? nlExtensions.substring(1) : nlExtensions; //$NON-NLS-1$
StringBuilder uExtension = new StringBuilder();
for (String pair : body.split(";")) { //$NON-NLS-1$
int eq = pair.indexOf('=');
if (eq <= 0) {
continue;
}
String rawKey = pair.substring(0, eq).trim().toLowerCase(Locale.ROOT);
String rawValue = pair.substring(eq + 1).trim().toLowerCase(Locale.ROOT).replace('_', '-');
if (rawValue.isEmpty()) {
continue;
}
String key = ICU_TO_BCP47_KEY.getOrDefault(rawKey, rawKey.length() == 2 ? rawKey : null);
if (key == null) {
continue;
}
if (uExtension.length() > 0) {
uExtension.append('-');
}
uExtension.append(key).append('-').append(rawValue);
}
Comment on lines +1941 to +1959
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current parsing logic assumes that the rawValue from the ICU-style string is directly compatible with BCP 47 subtags.

ICU keywords often used underscores (e.g., calendar=islamic_civil), whereas BCP 47 Unicode extensions require hyphens and specific subtag lengths (2-8 alphanumeric characters). While Locale.forLanguageTag is lenient and will simply ignore invalid subtags, users might find their preferences silently failing if they contain underscores or other non-BCP 47 characters.

Consider replacing underscores with hyphens in the rawValue to improve compatibility with legacy ICU strings.

Suggested change
for (String pair : body.split(";")) { //$NON-NLS-1$
int eq = pair.indexOf('=');
if (eq <= 0) {
continue;
}
String rawKey = pair.substring(0, eq).trim().toLowerCase(Locale.ROOT);
String rawValue = pair.substring(eq + 1).trim().toLowerCase(Locale.ROOT);
if (rawValue.isEmpty()) {
continue;
}
String key = ICU_TO_BCP47_KEY.getOrDefault(rawKey, rawKey.length() == 2 ? rawKey : null);
if (key == null) {
continue;
}
if (uExtension.length() > 0) {
uExtension.append('-');
}
uExtension.append(key).append('-').append(rawValue);
}
for (String pair : body.split(";")) { //$NON-NLS-1$
int eq = pair.indexOf('=');
if (eq <= 0) {
continue;
}
String rawKey = pair.substring(0, eq).trim().toLowerCase(Locale.ROOT);
String rawValue = pair.substring(eq + 1).trim().toLowerCase(Locale.ROOT).replace('_', '-');
if (rawValue.isEmpty()) {
continue;
}
String key = ICU_TO_BCP47_KEY.getOrDefault(rawKey, rawKey.length() == 2 ? rawKey : null);
if (key == null) {
continue;
}
if (uExtension.length() > 0) {
uExtension.append('-');
}
uExtension.append(key).append('-').append(rawValue);
}

if (uExtension.length() == 0) {
return;
}
String baseTag = Locale.getDefault(Category.FORMAT).stripExtensions().toLanguageTag();
Locale newDefault = Locale.forLanguageTag(baseTag + "-u-" + uExtension); //$NON-NLS-1$
Locale.setDefault(Category.FORMAT, newDefault);
}

/*
Expand Down
Loading