From d2e9b09ebcb365b8b14e2000df1dfa6128447798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=89=E8=99=8E?= Date: Mon, 28 Jul 2025 17:47:55 +0800 Subject: [PATCH] Migrate to zh-Hant / zh-Hans for Chinese language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: #41239 Signed-off-by: 秉虎 Signed-off-by: Allen Signed-off-by: Alexander Schwartz Co-authored-by: Alexander Schwartz --- .../topics/changes/changes-26_4_0.adoc | 10 +++ .../guides/ui-customization/localization.adoc | 9 +- ...properties => messages_zh_Hans.properties} | 0 ...properties => messages_zh_Hant.properties} | 0 ...properties => messages_zh_Hans.properties} | 0 .../org/keycloak/theme/ClassLoaderTheme.java | 12 +-- .../keycloak/theme/DefaultThemeManager.java | 4 +- .../org/keycloak/theme/FileBasedTheme.java | 88 +++++++++++++++++++ .../java/org/keycloak/theme/FolderTheme.java | 13 +-- ...properties => messages_zh_Hans.properties} | 0 ...properties => messages_zh_Hant.properties} | 0 ...properties => messages_zh_Hans.properties} | 0 ...properties => messages_zh_Hant.properties} | 0 ...properties => messages_zh_Hans.properties} | 0 ...properties => messages_zh_Hant.properties} | 0 ...properties => messages_zh_Hans.properties} | 0 ...properties => messages_zh_Hant.properties} | 0 17 files changed, 112 insertions(+), 24 deletions(-) rename js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/{messages_zh_CN.properties => messages_zh_Hans.properties} (100%) rename js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/{messages_zh_TW.properties => messages_zh_Hant.properties} (100%) rename js/apps/admin-ui/maven-resources-community/theme/keycloak.v2/admin/messages/{messages_zh_CN.properties => messages_zh_Hans.properties} (100%) create mode 100644 services/src/main/java/org/keycloak/theme/FileBasedTheme.java rename themes/src/main/resources-community/theme/base/account/messages/{messages_zh_CN.properties => messages_zh_Hans.properties} (100%) rename themes/src/main/resources-community/theme/base/account/messages/{messages_zh_TW.properties => messages_zh_Hant.properties} (100%) rename themes/src/main/resources-community/theme/base/admin/messages/{messages_zh_CN.properties => messages_zh_Hans.properties} (100%) rename themes/src/main/resources-community/theme/base/admin/messages/{messages_zh_TW.properties => messages_zh_Hant.properties} (100%) rename themes/src/main/resources-community/theme/base/email/messages/{messages_zh_CN.properties => messages_zh_Hans.properties} (100%) rename themes/src/main/resources-community/theme/base/email/messages/{messages_zh_TW.properties => messages_zh_Hant.properties} (100%) rename themes/src/main/resources-community/theme/base/login/messages/{messages_zh_CN.properties => messages_zh_Hans.properties} (100%) rename themes/src/main/resources-community/theme/base/login/messages/{messages_zh_TW.properties => messages_zh_Hant.properties} (100%) diff --git a/docs/documentation/upgrading/topics/changes/changes-26_4_0.adoc b/docs/documentation/upgrading/topics/changes/changes-26_4_0.adoc index 1919a771534..83237559466 100644 --- a/docs/documentation/upgrading/topics/changes/changes-26_4_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-26_4_0.adoc @@ -48,6 +48,16 @@ Starting with this release, {project_name} will cache by default only 10000 entr Use the options `cache-embedded-offline-sessions-max-count` and `cache-embedded-offline-client-sessions-max-count` to change size of the offline session caches. +=== Translation resource bundle file names + +The naming of resource bundles in classloader and folder based themes is now aligned with Java https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ResourceBundle.html#getBundle(java.lang.String,java.util.Locale,java.lang.ClassLoader)[ResourceBunndle#getBundle] file names. +For all included community languages like `de` or `pt-BR` a file is as before named `messages_de.properties` or `messages_pt_BR.properties`. +If you added custom language code, you should check if your file names are still the same. + +The languages "Chinese (traditional)" and "Chinese (simplified)" are named for historical reasons `zh-TW` and `zh-CN` in the community themes of {project_name}. +As a start to migrate to the new language codes `zh-Hant` and `zh-Hans`, the classloader and folder based themes pick up for the old language codes `zh-TW` and `zh-CN` also the files `messages_zh_Hant.properties` and `messages_zh_Hant.properties`. +Entries in `messages_zh_Hant.properties` take precedence over entries in `messages_zh_TW.properties`, and entries in `messages_zh_Hans.properties` take precedence over entries in `messages_zh_CN.properties`. + === Supported Update Email Feature The `Update Email` is now a supported feature so it is no longer needed to enable the feature during the server startup. diff --git a/docs/guides/ui-customization/localization.adoc b/docs/guides/ui-customization/localization.adoc index a31c0052cf9..14c7984ed42 100644 --- a/docs/guides/ui-customization/localization.adoc +++ b/docs/guides/ui-customization/localization.adoc @@ -35,6 +35,7 @@ Texts of these message bundles can be overwritten by realm-specific values, whic .Procedure . Create the file `/messages/messages_.properties` in the directory of your theme. +The `` follows the conventions of https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ResourceBundle.html#getBundle(java.lang.String,java.util.Locale,java.lang.ClassLoader)[ResourceBunndle#getBundle]. . Add this file to the `locales` property in `/theme.properties`. For a language to be available to users in a realm, the login, account, and email theme types must support the language, so you need to add your language for those theme types. @@ -72,8 +73,10 @@ locale_no=Norsk By default, message properties files should be encoded using UTF-8. {project_name} falls back to ISO-8859-1 handling if it cannot read the contents as UTF-8. -Unicode characters can be escaped as described in Java's documentation for https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/PropertyResourceBundle.html[PropertyResourceBundle]. -Previous versions of {project_name} had support for specifying the encoding in the first line with a comment such as `# encoding: UTF-8`, which is no longer supported. +Unicode characters can be escaped as described in Java's documentation for https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/PropertyResourceBundle.html[PropertyResourceBundle]. + +To simplify migration to the new language codes `zh-Hant` and `zh-Hans`, the classloader and folder based themes pick up for the old language codes `zh-TW` and `zh-CN` also the files `messages_zh_Hant.properties` and `messages_zh_Hant.properties`. +Entries in `messages_zh_Hant.properties` take precedence over entries in `messages_zh_TW.properties`, and entries in `messages_zh_Hans.properties` take precedence over entries in `messages_zh_CN.properties`. [role="_additional-resources"] .Additional resources @@ -97,4 +100,4 @@ CAUTION: In most cases, using realm overrides is not the recommended way to achi . Create a key/value pair from the modal dialog. Notice another subtab called *Effective message bundles*. This subtab provides a tool to query key/value pairs for a combination of theme, language, and theme type. You can use this tool to test and make sure your realm overrides took effect. - \ No newline at end of file + diff --git a/js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/messages_zh_CN.properties b/js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/messages_zh_Hans.properties similarity index 100% rename from js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/messages_zh_CN.properties rename to js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/messages_zh_Hans.properties diff --git a/js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/messages_zh_TW.properties b/js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/messages_zh_Hant.properties similarity index 100% rename from js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/messages_zh_TW.properties rename to js/apps/account-ui/maven-resources-community/theme/keycloak.v3/account/messages/messages_zh_Hant.properties diff --git a/js/apps/admin-ui/maven-resources-community/theme/keycloak.v2/admin/messages/messages_zh_CN.properties b/js/apps/admin-ui/maven-resources-community/theme/keycloak.v2/admin/messages/messages_zh_Hans.properties similarity index 100% rename from js/apps/admin-ui/maven-resources-community/theme/keycloak.v2/admin/messages/messages_zh_CN.properties rename to js/apps/admin-ui/maven-resources-community/theme/keycloak.v2/admin/messages/messages_zh_Hans.properties diff --git a/services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java b/services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java index de78d83f4dd..440bf595b17 100644 --- a/services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java +++ b/services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java @@ -31,7 +31,7 @@ import java.util.Properties; /** * @author Stian Thorgersen */ -public class ClassLoaderTheme implements Theme { +public class ClassLoaderTheme extends FileBasedTheme { private String name; @@ -116,19 +116,13 @@ public class ClassLoaderTheme implements Theme { } @Override - public Properties getMessages(String baseBundlename, Locale locale) throws IOException { - if(locale == null){ - return null; - } - Properties m = new Properties(); - - URL url = classLoader.getResource(this.messageRoot + baseBundlename + "_" + locale + ".properties"); + protected void loadBundle(String baseBundlename, Locale locale, Properties m) throws IOException { + URL url = classLoader.getResource(this.messageRoot + toBundleName(baseBundlename, locale) + ".properties"); if (url != null) { try (InputStream stream = url.openStream()) { PropertiesUtil.readCharsetAware(m, stream); } } - return m; } @Override diff --git a/services/src/main/java/org/keycloak/theme/DefaultThemeManager.java b/services/src/main/java/org/keycloak/theme/DefaultThemeManager.java index d60d93de839..726957e2b97 100755 --- a/services/src/main/java/org/keycloak/theme/DefaultThemeManager.java +++ b/services/src/main/java/org/keycloak/theme/DefaultThemeManager.java @@ -311,9 +311,9 @@ public class DefaultThemeManager implements ThemeManager { // This is mapping old locale codes to the new locale codes for Simplified and Traditional Chinese. // Once the existing locales have been moved, this code can be removed. if (l.equals("zh-CN")) { - rl = "zh-HANS"; + rl = "zh-Hans"; } else if (l.equals("zh-TW")) { - rl = "zh-HANT"; + rl = "zh-Hans"; } Locale loc = Locale.forLanguageTag(rl); label = capitalize(loc.getDisplayName(locale), locale); diff --git a/services/src/main/java/org/keycloak/theme/FileBasedTheme.java b/services/src/main/java/org/keycloak/theme/FileBasedTheme.java new file mode 100644 index 00000000000..a36b24ccb4b --- /dev/null +++ b/services/src/main/java/org/keycloak/theme/FileBasedTheme.java @@ -0,0 +1,88 @@ +/* + * Copyright 2025 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.theme; + +import java.io.IOException; +import java.util.Locale; +import java.util.Properties; + +public abstract class FileBasedTheme implements Theme { + abstract protected void loadBundle(String baseBundlename, Locale locale, Properties m) throws IOException; + + @Override + public Properties getMessages(String baseBundlename, Locale locale) throws IOException { + if(locale == null){ + return null; + } + Properties m = new Properties(); + + loadBundle(baseBundlename, locale, m); + + // Chinese locales mapping + if (locale.getLanguage().equals("zh") && !locale.getCountry().isEmpty()) { + Locale l = switch (locale.getCountry()) { + case "TW" -> Locale.forLanguageTag("zh-Hant"); + case "CN" -> Locale.forLanguageTag("zh-Hans"); + default -> null; + }; + if (l != null) { + loadBundle(baseBundlename, l, m); + } + } + + return m; + } + + // Logic as implemented by JDK's ResourceBundle + public String toBundleName(String baseName, Locale locale) { + if (locale == Locale.ROOT) { + return baseName; + } + + String language = locale.getLanguage(); + String script = locale.getScript(); + String country = locale.getCountry(); + String variant = locale.getVariant(); + + if (language.isEmpty() && country.isEmpty() && variant.isEmpty()) { + return baseName; + } + + StringBuilder sb = new StringBuilder(baseName); + sb.append('_'); + if (!script.isEmpty()) { + if (!variant.isEmpty()) { + sb.append(language).append('_').append(script).append('_').append(country).append('_').append(variant); + } else if (!country.isEmpty()) { + sb.append(language).append('_').append(script).append('_').append(country); + } else { + sb.append(language).append('_').append(script); + } + } else { + if (!variant.isEmpty()) { + sb.append(language).append('_').append(country).append('_').append(variant); + } else if (!country.isEmpty()) { + sb.append(language).append('_').append(country); + } else { + sb.append(language); + } + } + return sb.toString(); + + } +} diff --git a/services/src/main/java/org/keycloak/theme/FolderTheme.java b/services/src/main/java/org/keycloak/theme/FolderTheme.java index f22162d1d15..10914c6dd09 100644 --- a/services/src/main/java/org/keycloak/theme/FolderTheme.java +++ b/services/src/main/java/org/keycloak/theme/FolderTheme.java @@ -34,7 +34,7 @@ import java.util.regex.Pattern; /** * @author Stian Thorgersen */ -public class FolderTheme implements Theme { +public class FolderTheme extends FileBasedTheme { private String parentName; private String importName; @@ -101,14 +101,8 @@ public class FolderTheme implements Theme { private static final Pattern LEGAL_LOCALE = Pattern.compile("[a-zA-Z0-9-_#]*"); @Override - public Properties getMessages(String baseBundlename, Locale locale) throws IOException { - if (locale == null){ - return null; - } - - Properties m = new Properties(); - - String filename = baseBundlename + "_" + locale; + protected void loadBundle(String baseBundlename, Locale locale, Properties m) throws IOException { + String filename = toBundleName(baseBundlename, locale); if (!LEGAL_LOCALE.matcher(filename).matches()) { throw new RuntimeException("Found illegal characters in locale or bundle name: " + filename); @@ -120,7 +114,6 @@ public class FolderTheme implements Theme { PropertiesUtil.readCharsetAware(m, stream); } } - return m; } public Properties getEnhancedMessages(RealmModel realm, Locale locale) throws IOException { diff --git a/themes/src/main/resources-community/theme/base/account/messages/messages_zh_CN.properties b/themes/src/main/resources-community/theme/base/account/messages/messages_zh_Hans.properties similarity index 100% rename from themes/src/main/resources-community/theme/base/account/messages/messages_zh_CN.properties rename to themes/src/main/resources-community/theme/base/account/messages/messages_zh_Hans.properties diff --git a/themes/src/main/resources-community/theme/base/account/messages/messages_zh_TW.properties b/themes/src/main/resources-community/theme/base/account/messages/messages_zh_Hant.properties similarity index 100% rename from themes/src/main/resources-community/theme/base/account/messages/messages_zh_TW.properties rename to themes/src/main/resources-community/theme/base/account/messages/messages_zh_Hant.properties diff --git a/themes/src/main/resources-community/theme/base/admin/messages/messages_zh_CN.properties b/themes/src/main/resources-community/theme/base/admin/messages/messages_zh_Hans.properties similarity index 100% rename from themes/src/main/resources-community/theme/base/admin/messages/messages_zh_CN.properties rename to themes/src/main/resources-community/theme/base/admin/messages/messages_zh_Hans.properties diff --git a/themes/src/main/resources-community/theme/base/admin/messages/messages_zh_TW.properties b/themes/src/main/resources-community/theme/base/admin/messages/messages_zh_Hant.properties similarity index 100% rename from themes/src/main/resources-community/theme/base/admin/messages/messages_zh_TW.properties rename to themes/src/main/resources-community/theme/base/admin/messages/messages_zh_Hant.properties diff --git a/themes/src/main/resources-community/theme/base/email/messages/messages_zh_CN.properties b/themes/src/main/resources-community/theme/base/email/messages/messages_zh_Hans.properties similarity index 100% rename from themes/src/main/resources-community/theme/base/email/messages/messages_zh_CN.properties rename to themes/src/main/resources-community/theme/base/email/messages/messages_zh_Hans.properties diff --git a/themes/src/main/resources-community/theme/base/email/messages/messages_zh_TW.properties b/themes/src/main/resources-community/theme/base/email/messages/messages_zh_Hant.properties similarity index 100% rename from themes/src/main/resources-community/theme/base/email/messages/messages_zh_TW.properties rename to themes/src/main/resources-community/theme/base/email/messages/messages_zh_Hant.properties diff --git a/themes/src/main/resources-community/theme/base/login/messages/messages_zh_CN.properties b/themes/src/main/resources-community/theme/base/login/messages/messages_zh_Hans.properties similarity index 100% rename from themes/src/main/resources-community/theme/base/login/messages/messages_zh_CN.properties rename to themes/src/main/resources-community/theme/base/login/messages/messages_zh_Hans.properties diff --git a/themes/src/main/resources-community/theme/base/login/messages/messages_zh_TW.properties b/themes/src/main/resources-community/theme/base/login/messages/messages_zh_Hant.properties similarity index 100% rename from themes/src/main/resources-community/theme/base/login/messages/messages_zh_TW.properties rename to themes/src/main/resources-community/theme/base/login/messages/messages_zh_Hant.properties