From cb364965e3079afdaeed03912494f29add55ae85 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 15:28:44 +0800 Subject: [PATCH 01/35] update --- .gitignore | 3 +++ .../l10n/ParseLanguageSubtagRegistry.java | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java diff --git a/.gitignore b/.gitignore index 2316f1890cc..3ba71482801 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ hmcl-exported-logs-* /.local/ /.cache/ +# IANA Language Subtag Registry +language-subtag-registry + # gradle build /build/ /HMCL/build/ diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java new file mode 100644 index 00000000000..025fa4c4ecc --- /dev/null +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -0,0 +1,25 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.gradle.l10n; + +import org.gradle.api.DefaultTask; + +/// @author Glavo +/// @see [language-subtag-registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) +public abstract class ParseLanguageSubtagRegistry extends DefaultTask { +} From 3ef7965a887e36e881a9ff4578bf6f01c54192de Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 15:59:42 +0800 Subject: [PATCH 02/35] update --- build.gradle.kts | 4 + .../l10n/ParseLanguageSubtagRegistry.java | 114 ++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index b3b3fe7caac..c1f0ca1922b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ import org.jackhuang.hmcl.gradle.ci.CheckUpdate import org.jackhuang.hmcl.gradle.docs.UpdateDocuments +import org.jackhuang.hmcl.gradle.l10n.ParseLanguageSubtagRegistry plugins { id("checkstyle") @@ -76,6 +77,9 @@ org.jackhuang.hmcl.gradle.javafx.JavaFXUtils.register(rootProject) defaultTasks("clean", "build") +tasks.register("parseLanguageSubtagRegistry") { + languageSubtagRegistryFile.set(layout.projectDirectory.file("language-subtag-registry")) +} tasks.register("updateDocuments") { documentsDir.set(layout.projectDirectory.dir("docs")) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 025fa4c4ecc..a47ad1b1136 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -18,8 +18,122 @@ package org.jackhuang.hmcl.gradle.l10n; import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.internal.impldep.org.apache.commons.lang3.text.StrBuilder; +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /// @author Glavo /// @see [language-subtag-registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) public abstract class ParseLanguageSubtagRegistry extends DefaultTask { + + @InputFile + public abstract RegularFileProperty getLanguageSubtagRegistryFile(); + + @TaskAction + public void run() throws IOException { + List items; + + try (var reader = Files.newBufferedReader(getLanguageSubtagRegistryFile().getAsFile().get().toPath())) { + var builder = new ItemsBuilder(); + builder.parse(reader); + items = builder.items; + } + + // TODO + + } + + private static final class Item { + final Map> values = new LinkedHashMap<>(); + + public @NotNull List allValues(String name) { + return values.getOrDefault(name, List.of()); + } + + public @NotNull Optional firstValue(String name) { + return Optional.ofNullable(values.get(name)).map(it -> it.get(0)); + } + + public void put(String name, String value) { + values.computeIfAbsent(name, ignored -> new ArrayList<>(1)).add(value); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner("\n"); + + values.forEach((name, values) -> { + for (String value : values) { + joiner.add(name + ": " + value); + } + }); + + return joiner.toString(); + } + } + + private static final class ItemsBuilder { + private final List items = new ArrayList<>(1024); + private Item current = new Item(); + private String currentName = null; + private String currentValue = null; + + private void updateCurrent() { + if (currentName != null) { + current.put(currentName, currentValue); + currentName = null; + currentValue = null; + } + } + + void parse(BufferedReader reader) throws IOException { + Pattern linePattern = Pattern.compile("^(?[A-Za-z\\-]+): (?.*)$"); + + String line; + while ((line = reader.readLine()) != null) { + if (line.isBlank()) { + continue; + } else if (line.equals("%%")) { + updateCurrent(); + + items.add(current); + current = new Item(); + } else if (line.startsWith(" ")) { + if (currentValue != null) { + currentValue = currentValue + " " + line; + } else { + throw new IOException("Invalid line: " + line); + } + } else { + updateCurrent(); + + Matcher matcher = linePattern.matcher(line); + if (matcher.matches()) { + currentName = matcher.group("name"); + currentValue = matcher.group("value"); + } else { + throw new IOException("Invalid line: " + line); + } + } + } + + updateCurrent(); + if (!current.values.isEmpty()) { + items.add(current); + current = new Item(); + } + } + } + } From 64ca89c88bf23adcf8b05ed4333b07ae5730f87c Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:01:43 +0800 Subject: [PATCH 03/35] update --- .../hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index a47ad1b1136..1277fd6735f 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -25,6 +25,7 @@ import org.gradle.api.tasks.TaskAction; import org.gradle.internal.impldep.org.apache.commons.lang3.text.StrBuilder; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.IOException; @@ -50,8 +51,9 @@ public void run() throws IOException { items = builder.items; } - // TODO + for (Item item : items) { + } } private static final class Item { @@ -65,6 +67,10 @@ private static final class Item { return Optional.ofNullable(values.get(name)).map(it -> it.get(0)); } + public @Nullable String firstValueOrNull(String name) { + return firstValue(name).orElse(null); + } + public void put(String name, String value) { values.computeIfAbsent(name, ignored -> new ArrayList<>(1)).add(value); } From 4e4ac985ef1e05bad10c9d99f50749da3850ba51 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:07:19 +0800 Subject: [PATCH 04/35] update --- .../l10n/ParseLanguageSubtagRegistry.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 1277fd6735f..cecc53b10f6 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -52,7 +52,7 @@ public void run() throws IOException { } for (Item item : items) { - + String type = item.firstValueOrThrow("Type"); } } @@ -71,6 +71,10 @@ private static final class Item { return firstValue(name).orElse(null); } + public @NotNull String firstValueOrThrow(String name) { + return firstValue(name).orElseThrow(() -> new IllegalStateException("No value found for " + name + " in " + this)); + } + public void put(String name, String value) { values.computeIfAbsent(name, ignored -> new ArrayList<>(1)).add(value); } @@ -103,6 +107,25 @@ private void updateCurrent() { } } + private void updateItems() throws IOException { + updateCurrent(); + + if (current.values.isEmpty()) + return; + + if (current.firstValue("Type").isEmpty()) { + if (current.firstValue("File-Date").isPresent()) { + current.values.clear(); + return; + } else { + throw new IOException("Invalid item: " + current); + } + } + + items.add(current); + current = new Item(); + } + void parse(BufferedReader reader) throws IOException { Pattern linePattern = Pattern.compile("^(?[A-Za-z\\-]+): (?.*)$"); @@ -111,10 +134,7 @@ void parse(BufferedReader reader) throws IOException { if (line.isBlank()) { continue; } else if (line.equals("%%")) { - updateCurrent(); - - items.add(current); - current = new Item(); + updateItems(); } else if (line.startsWith(" ")) { if (currentValue != null) { currentValue = currentValue + " " + line; @@ -134,11 +154,7 @@ void parse(BufferedReader reader) throws IOException { } } - updateCurrent(); - if (!current.values.isEmpty()) { - items.add(current); - current = new Item(); - } + updateItems(); } } From 766bea889569f3e2f05f7a2083cc277ec16da326 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:08:14 +0800 Subject: [PATCH 05/35] update --- .../jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index cecc53b10f6..7667d74e83c 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -38,6 +38,8 @@ /// @see [language-subtag-registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) public abstract class ParseLanguageSubtagRegistry extends DefaultTask { + private static final Logger LOGGER = Logging.getLogger(ParseLanguageSubtagRegistry.class); + @InputFile public abstract RegularFileProperty getLanguageSubtagRegistryFile(); From 495b8a5990b7c2cac476e91db6546552905ae80d Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:10:42 +0800 Subject: [PATCH 06/35] update --- .../l10n/ParseLanguageSubtagRegistry.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 7667d74e83c..3c6d966de1e 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.gradle.l10n; import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; @@ -53,9 +54,22 @@ public void run() throws IOException { items = builder.items; } + Set set = new HashSet<>(); + for (Item item : items) { String type = item.firstValueOrThrow("Type"); + + switch (type) { + + + case "grandfathered", "redundant" -> { + // ignored + } + default -> throw new GradleException(String.format("Unknown subtag type: %s", type)); + } } + + LOGGER.quiet(">>> " + set); } private static final class Item { @@ -74,7 +88,7 @@ private static final class Item { } public @NotNull String firstValueOrThrow(String name) { - return firstValue(name).orElseThrow(() -> new IllegalStateException("No value found for " + name + " in " + this)); + return firstValue(name).orElseThrow(() -> new GradleException("No value found for " + name + " in " + this)); } public void put(String name, String value) { @@ -120,7 +134,7 @@ private void updateItems() throws IOException { current.values.clear(); return; } else { - throw new IOException("Invalid item: " + current); + throw new GradleException("Invalid item: " + current); } } @@ -141,7 +155,7 @@ void parse(BufferedReader reader) throws IOException { if (currentValue != null) { currentValue = currentValue + " " + line; } else { - throw new IOException("Invalid line: " + line); + throw new GradleException("Invalid line: " + line); } } else { updateCurrent(); @@ -151,7 +165,7 @@ void parse(BufferedReader reader) throws IOException { currentName = matcher.group("name"); currentValue = matcher.group("value"); } else { - throw new IOException("Invalid line: " + line); + throw new GradleException("Invalid line: " + line); } } } From 725c772cb121655cb310e637107d6e1f87faa256 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:14:26 +0800 Subject: [PATCH 07/35] update --- .../hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 3c6d966de1e..a2598bcdb27 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -60,9 +60,10 @@ public void run() throws IOException { String type = item.firstValueOrThrow("Type"); switch (type) { + case "language", "extlang" -> { - - case "grandfathered", "redundant" -> { + } + case "grandfathered", "redundant", "region", "script", "variant" -> { // ignored } default -> throw new GradleException(String.format("Unknown subtag type: %s", type)); From 968913c78c3bd27013cb98b8cc65b4279f249b84 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:20:33 +0800 Subject: [PATCH 08/35] update --- .../l10n/ParseLanguageSubtagRegistry.java | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index a2598bcdb27..78029e11a7b 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -54,23 +54,46 @@ public void run() throws IOException { items = builder.items; } - Set set = new HashSet<>(); + Map variantToScript = new LinkedHashMap<>(); for (Item item : items) { String type = item.firstValueOrThrow("Type"); + String subtag = item.firstValueOrThrow("Subtag"); + mainSwitch: switch (type) { case "language", "extlang" -> { } - case "grandfathered", "redundant", "region", "script", "variant" -> { + case "variant" -> { + List prefixes = item.allValues("Prefix"); + String defaultScript = null; + for (String prefix : prefixes) { + String script = Locale.forLanguageTag(prefix).getScript(); + if (script.isEmpty()) { + break mainSwitch; + } + + if (defaultScript == null) { + defaultScript = script; + } else { + if (!defaultScript.equals(script)) { + break mainSwitch; + } + } + } + + if (defaultScript != null) { + variantToScript.put(subtag, defaultScript); + } + } + case "grandfathered", "redundant", "region", "script" -> { // ignored } default -> throw new GradleException(String.format("Unknown subtag type: %s", type)); } } - LOGGER.quiet(">>> " + set); } private static final class Item { From 4a22ae2307affe454c4806ed444c22713ea253ec Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:21:33 +0800 Subject: [PATCH 09/35] update --- .../hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 78029e11a7b..04e3d9651de 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -58,6 +58,9 @@ public void run() throws IOException { for (Item item : items) { String type = item.firstValueOrThrow("Type"); + if (type.equals("grandfathered") || type.equals("redundant")) + continue; + String subtag = item.firstValueOrThrow("Subtag"); mainSwitch: From 6acd799179faa03f47894ef7fd14300522c8fe2e Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:24:15 +0800 Subject: [PATCH 10/35] update --- .../hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 04e3d9651de..89e2d9d9072 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -54,7 +54,7 @@ public void run() throws IOException { items = builder.items; } - Map variantToScript = new LinkedHashMap<>(); + Map> scriptToVariants = new LinkedHashMap<>(); for (Item item : items) { String type = item.firstValueOrThrow("Type"); @@ -87,10 +87,11 @@ public void run() throws IOException { } if (defaultScript != null) { - variantToScript.put(subtag, defaultScript); + scriptToVariants.computeIfAbsent(defaultScript, k -> new ArrayList<>(1)) + .add(subtag); } } - case "grandfathered", "redundant", "region", "script" -> { + case "region", "script" -> { // ignored } default -> throw new GradleException(String.format("Unknown subtag type: %s", type)); From 473d58559341bb76c9fe455b9da98f2a4a6f4ce7 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:28:55 +0800 Subject: [PATCH 11/35] update --- .../hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 89e2d9d9072..3b14ace9c1d 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -58,7 +58,8 @@ public void run() throws IOException { for (Item item : items) { String type = item.firstValueOrThrow("Type"); - if (type.equals("grandfathered") || type.equals("redundant")) + if (type.equals("grandfathered") || type.equals("redundant") + || !item.allValues("Deprecated").isEmpty()) continue; String subtag = item.firstValueOrThrow("Subtag"); From 49fce66c0403971f48be938fc41ce43895b2667d Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:30:44 +0800 Subject: [PATCH 12/35] update --- .../hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 3b14ace9c1d..ffb2dccdbd4 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -55,6 +55,7 @@ public void run() throws IOException { } Map> scriptToVariants = new LinkedHashMap<>(); + Map> langToMarcoLang = new LinkedHashMap<>(); for (Item item : items) { String type = item.firstValueOrThrow("Type"); @@ -67,7 +68,9 @@ public void run() throws IOException { mainSwitch: switch (type) { case "language", "extlang" -> { - + item.firstValue("Macrolanguage").ifPresent(macroLang -> + langToMarcoLang.computeIfAbsent(macroLang, k -> new ArrayList<>()) + .add(subtag)); } case "variant" -> { List prefixes = item.allValues("Prefix"); @@ -88,7 +91,7 @@ public void run() throws IOException { } if (defaultScript != null) { - scriptToVariants.computeIfAbsent(defaultScript, k -> new ArrayList<>(1)) + scriptToVariants.computeIfAbsent(defaultScript, k -> new ArrayList<>()) .add(subtag); } } From 2a608f661bef11d5536714b97b30a5ab0c5ff4dc Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:31:35 +0800 Subject: [PATCH 13/35] update --- .../hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index ffb2dccdbd4..63c22040086 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -55,7 +55,7 @@ public void run() throws IOException { } Map> scriptToVariants = new LinkedHashMap<>(); - Map> langToMarcoLang = new LinkedHashMap<>(); + Map> macroLangToLangs = new LinkedHashMap<>(); for (Item item : items) { String type = item.firstValueOrThrow("Type"); @@ -69,7 +69,7 @@ public void run() throws IOException { switch (type) { case "language", "extlang" -> { item.firstValue("Macrolanguage").ifPresent(macroLang -> - langToMarcoLang.computeIfAbsent(macroLang, k -> new ArrayList<>()) + macroLangToLangs.computeIfAbsent(macroLang, k -> new ArrayList<>()) .add(subtag)); } case "variant" -> { From cc4e6e49b2440e7fd3295176755d699930b154cb Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:44:13 +0800 Subject: [PATCH 14/35] update --- .../resources/assets/lang/sublanguages.csv | 64 ++++++++++++++++++- .../assets/lang/variant_to_script.csv | 1 + build.gradle.kts | 3 + .../l10n/ParseLanguageSubtagRegistry.java | 40 ++++++++++-- 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 HMCLCore/src/main/resources/assets/lang/variant_to_script.csv diff --git a/HMCLCore/src/main/resources/assets/lang/sublanguages.csv b/HMCLCore/src/main/resources/assets/lang/sublanguages.csv index 5c8217c0e77..0f466e29f48 100644 --- a/HMCLCore/src/main/resources/assets/lang/sublanguages.csv +++ b/HMCLCore/src/main/resources/assets/lang/sublanguages.csv @@ -1 +1,63 @@ -zh,cdo,cjy,cmn,cnp,cpx,csp,czh,czo,gan,hak,hnm,hsn,luh,lzh,mnp,nan,sjc,wuu,yue \ No newline at end of file +ak,fat,tw +ar,aao,abh,abv,acm,acq,acw,acx,acy,adf,aeb,aec,afb,apc,apd,arb,arq,ars,ary,arz,auz,avl,ayh,ayl,ayn,ayp,pga,shu,ssh +ay,ayc,ayr +az,azb,azj +bal,bcc,bgn,bgp +bik,bcl,bln,bto,cts,fbl,lbl,rbl,ubl +bnc,ebk,lbk,obk,rbk,vbk +bua,bxm,bxr,bxu +chm,mhr,mrj +cr,crj,crk,crl,crm,csw,cwd +del,umu,unm +den,scs,xsl +din,dib,dik,dip,diw,dks +doi,dgo,xnr +et,ekk,vro +fa,pes,prs +ff,ffm,fub,fuc,fue,fuf,fuh,fui,fuq,fuv +gba,bdt,gbp,gbq,gmm,gso,gya +gn,gnw,gug,gui,gun,nhd +gon,esg,gno,wsg +grb,gbo,gec,grj,grv,gry +hai,hax,hdn +hmn,cqd,hea,hma,hmc,hmd,hme,hmg,hmh,hmi,hmj,hml,hmm,hmp,hmq,hms,hmw,hmy,hmz,hnj,hrm,huj,mmr,muq,mww,sfm +ik,esi,esk +iu,ike,ikt +jrb,aju,jye,yhd,yud +kg,kng,kwy,ldi +kln,enb,eyo,niq,oki,pko,sgc,spy,tec,tuy +kok,gom,knn +kpe,gkp,xpe +kr,kby,knc,krt +ku,ckb,kmr,sdh +kv,koi,kpv +lah,hnd,hno,jat,phr,pnb,skr,xhe +luy,bxk,ida,lkb,lko,lks,lri,lrm,lsm,lto,lts,lwg,nle,nyd,rag +lv,ltg,lvs +man,emk,mku,mlq,mnk,msc,mwk +mg,bhr,bmm,bzc,msh,plt,skg,tdx,tkg,txy,xmv,xmw +mn,khk,mvf +ms,bjn,btj,bve,bvu,coa,dup,hji,id,jak,jax,kvb,kvr,kxd,lce,lcf,liw,max,meo,mfa,mfb,min,mqg,msi,mui,orn,ors,pel,pse,tmw,urk,vkk,vkt,xmm,zlm,zmi,zsm +mwr,dhd,mtr,mve,rwr,swv,wry +ne,dty,npi +no,nb,nn +oj,ciw,ojb,ojc,ojg,ojs,ojw,otw +om,gax,gaz,hae,orc +or,ory,spv +ps,pbt,pbu,pst +qu,qub,qud,quf,qug,quh,quk,qul,qup,qur,qus,quw,qux,quy,quz,qva,qvc,qve,qvh,qvi,qvj,qvl,qvm,qvn,qvo,qvp,qvs,qvw,qvz,qwa,qwc,qwh,qws,qxa,qxc,qxh,qxl,qxn,qxo,qxp,qxr,qxt,qxu,qxw +raj,bgq,gda,gju,hoj,mup,wbr +rom,rmc,rmf,rml,rmn,rmo,rmw,rmy +sa,cls,vsn +sc,sdc,sdn,src,sro +sh,bs,cnr,hr,sr +sq,aae,aat,aln,als +sw,swc,swh +syr,aii,cld +tmh,taq,thv,thz,ttq +uz,uzn,uzs +yi,ydd,yih +za,zch,zeh,zgb,zgm,zgn,zhd,zhn,zlj,zln,zlq,zqe,zyb,zyg,zyj,zyn,zzj +zap,zaa,zab,zac,zad,zae,zaf,zai,zam,zao,zaq,zar,zas,zat,zav,zaw,zax,zca,zcd,zoo,zpa,zpb,zpc,zpd,zpe,zpf,zpg,zph,zpi,zpj,zpk,zpl,zpm,zpn,zpo,zpp,zpq,zpr,zps,zpt,zpu,zpv,zpw,zpx,zpy,zpz,zsr,zte,ztg,ztl,ztm,ztn,ztp,ztq,zts,ztt,ztu,ztx,zty +zh,cdo,cjy,cmn,cnp,cpx,csp,czh,czo,gan,hak,hnm,hsn,luh,lzh,mnp,nan,sjc,wuu,yue +zza,diq,kiu diff --git a/HMCLCore/src/main/resources/assets/lang/variant_to_script.csv b/HMCLCore/src/main/resources/assets/lang/variant_to_script.csv new file mode 100644 index 00000000000..d61b855a24a --- /dev/null +++ b/HMCLCore/src/main/resources/assets/lang/variant_to_script.csv @@ -0,0 +1 @@ +Latn,hepburn,pehoeji,pinyin,tailo,tongyong,wadegile diff --git a/build.gradle.kts b/build.gradle.kts index c1f0ca1922b..5d23d0e3b07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -79,6 +79,9 @@ defaultTasks("clean", "build") tasks.register("parseLanguageSubtagRegistry") { languageSubtagRegistryFile.set(layout.projectDirectory.file("language-subtag-registry")) + + sublanguagesFile.set(layout.projectDirectory.file("HMCLCore/src/main/resources/assets/lang/sublanguages.csv")) + variantToScriptFile.set(layout.projectDirectory.file("HMCLCore/src/main/resources/assets/lang/variant_to_script.csv")) } tasks.register("updateDocuments") { diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 63c22040086..772de70a525 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -23,14 +23,16 @@ import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; -import org.gradle.internal.impldep.org.apache.commons.lang3.text.StrBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.IOException; +import java.io.PrintWriter; import java.nio.file.Files; +import java.nio.file.StandardOpenOption; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,6 +46,13 @@ public abstract class ParseLanguageSubtagRegistry extends DefaultTask { @InputFile public abstract RegularFileProperty getLanguageSubtagRegistryFile(); + @OutputFile + public abstract RegularFileProperty getSublanguagesFile(); + + /// CSV file storing the mapping from variants to their default scripts. + @OutputFile + public abstract RegularFileProperty getVariantToScriptFile(); + @TaskAction public void run() throws IOException { List items; @@ -54,8 +63,8 @@ public void run() throws IOException { items = builder.items; } - Map> scriptToVariants = new LinkedHashMap<>(); - Map> macroLangToLangs = new LinkedHashMap<>(); + Map> scriptToVariants = new TreeMap<>(); + Map> macroLangToLangs = new TreeMap<>(); for (Item item : items) { String type = item.firstValueOrThrow("Type"); @@ -69,7 +78,7 @@ public void run() throws IOException { switch (type) { case "language", "extlang" -> { item.firstValue("Macrolanguage").ifPresent(macroLang -> - macroLangToLangs.computeIfAbsent(macroLang, k -> new ArrayList<>()) + macroLangToLangs.computeIfAbsent(macroLang, k -> new TreeSet<>()) .add(subtag)); } case "variant" -> { @@ -91,7 +100,7 @@ public void run() throws IOException { } if (defaultScript != null) { - scriptToVariants.computeIfAbsent(defaultScript, k -> new ArrayList<>()) + scriptToVariants.computeIfAbsent(defaultScript, k -> new TreeSet<>()) .add(subtag); } } @@ -102,6 +111,27 @@ public void run() throws IOException { } } + try (var writer = new PrintWriter(Files.newBufferedWriter(getSublanguagesFile().getAsFile().get().toPath(), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING))) { + macroLangToLangs.forEach((k, v) -> { + writer.println(k + "," + String.join(",", v)); + }); + + if (writer.checkError()) + throw new GradleException(); + } + + try (var writer = new PrintWriter(Files.newBufferedWriter(getVariantToScriptFile().getAsFile().get().toPath(), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING))) { + scriptToVariants.forEach((k, v) -> { + writer.println(k + "," + String.join(",", v)); + }); + + if (writer.checkError()) + throw new GradleException(); + } } private static final class Item { From 58d3355f089f2bdc9bf3cf937d389016064433c0 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:46:24 +0800 Subject: [PATCH 15/35] update --- .../resources/assets/lang/sublanguages.csv | 60 +++++++++---------- .../l10n/ParseLanguageSubtagRegistry.java | 10 +++- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/HMCLCore/src/main/resources/assets/lang/sublanguages.csv b/HMCLCore/src/main/resources/assets/lang/sublanguages.csv index 0f466e29f48..4a4a3e362ed 100644 --- a/HMCLCore/src/main/resources/assets/lang/sublanguages.csv +++ b/HMCLCore/src/main/resources/assets/lang/sublanguages.csv @@ -1,44 +1,22 @@ -ak,fat,tw +ak,tw,fat ar,aao,abh,abv,acm,acq,acw,acx,acy,adf,aeb,aec,afb,apc,apd,arb,arq,ars,ary,arz,auz,avl,ayh,ayl,ayn,ayp,pga,shu,ssh ay,ayc,ayr az,azb,azj -bal,bcc,bgn,bgp -bik,bcl,bln,bto,cts,fbl,lbl,rbl,ubl -bnc,ebk,lbk,obk,rbk,vbk -bua,bxm,bxr,bxu -chm,mhr,mrj cr,crj,crk,crl,crm,csw,cwd -del,umu,unm -den,scs,xsl -din,dib,dik,dip,diw,dks -doi,dgo,xnr et,ekk,vro fa,pes,prs ff,ffm,fub,fuc,fue,fuf,fuh,fui,fuq,fuv -gba,bdt,gbp,gbq,gmm,gso,gya gn,gnw,gug,gui,gun,nhd -gon,esg,gno,wsg -grb,gbo,gec,grj,grv,gry -hai,hax,hdn -hmn,cqd,hea,hma,hmc,hmd,hme,hmg,hmh,hmi,hmj,hml,hmm,hmp,hmq,hms,hmw,hmy,hmz,hnj,hrm,huj,mmr,muq,mww,sfm ik,esi,esk iu,ike,ikt -jrb,aju,jye,yhd,yud kg,kng,kwy,ldi -kln,enb,eyo,niq,oki,pko,sgc,spy,tec,tuy -kok,gom,knn -kpe,gkp,xpe kr,kby,knc,krt ku,ckb,kmr,sdh kv,koi,kpv -lah,hnd,hno,jat,phr,pnb,skr,xhe -luy,bxk,ida,lkb,lko,lks,lri,lrm,lsm,lto,lts,lwg,nle,nyd,rag lv,ltg,lvs -man,emk,mku,mlq,mnk,msc,mwk mg,bhr,bmm,bzc,msh,plt,skg,tdx,tkg,txy,xmv,xmw mn,khk,mvf -ms,bjn,btj,bve,bvu,coa,dup,hji,id,jak,jax,kvb,kvr,kxd,lce,lcf,liw,max,meo,mfa,mfb,min,mqg,msi,mui,orn,ors,pel,pse,tmw,urk,vkk,vkt,xmm,zlm,zmi,zsm -mwr,dhd,mtr,mve,rwr,swv,wry +ms,id,bjn,btj,bve,bvu,coa,dup,hji,jak,jax,kvb,kvr,kxd,lce,lcf,liw,max,meo,mfa,mfb,min,mqg,msi,mui,orn,ors,pel,pse,tmw,urk,vkk,vkt,xmm,zlm,zmi,zsm ne,dty,npi no,nb,nn oj,ciw,ojb,ojc,ojg,ojs,ojw,otw @@ -46,18 +24,40 @@ om,gax,gaz,hae,orc or,ory,spv ps,pbt,pbu,pst qu,qub,qud,quf,qug,quh,quk,qul,qup,qur,qus,quw,qux,quy,quz,qva,qvc,qve,qvh,qvi,qvj,qvl,qvm,qvn,qvo,qvp,qvs,qvw,qvz,qwa,qwc,qwh,qws,qxa,qxc,qxh,qxl,qxn,qxo,qxp,qxr,qxt,qxu,qxw -raj,bgq,gda,gju,hoj,mup,wbr -rom,rmc,rmf,rml,rmn,rmo,rmw,rmy sa,cls,vsn sc,sdc,sdn,src,sro -sh,bs,cnr,hr,sr +sh,bs,hr,sr,cnr sq,aae,aat,aln,als sw,swc,swh -syr,aii,cld -tmh,taq,thv,thz,ttq uz,uzn,uzs yi,ydd,yih za,zch,zeh,zgb,zgm,zgn,zhd,zhn,zlj,zln,zlq,zqe,zyb,zyg,zyj,zyn,zzj -zap,zaa,zab,zac,zad,zae,zaf,zai,zam,zao,zaq,zar,zas,zat,zav,zaw,zax,zca,zcd,zoo,zpa,zpb,zpc,zpd,zpe,zpf,zpg,zph,zpi,zpj,zpk,zpl,zpm,zpn,zpo,zpp,zpq,zpr,zps,zpt,zpu,zpv,zpw,zpx,zpy,zpz,zsr,zte,ztg,ztl,ztm,ztn,ztp,ztq,zts,ztt,ztu,ztx,zty zh,cdo,cjy,cmn,cnp,cpx,csp,czh,czo,gan,hak,hnm,hsn,luh,lzh,mnp,nan,sjc,wuu,yue +bal,bcc,bgn,bgp +bik,bcl,bln,bto,cts,fbl,lbl,rbl,ubl +bnc,ebk,lbk,obk,rbk,vbk +bua,bxm,bxr,bxu +chm,mhr,mrj +del,umu,unm +den,scs,xsl +din,dib,dik,dip,diw,dks +doi,dgo,xnr +gba,bdt,gbp,gbq,gmm,gso,gya +gon,esg,gno,wsg +grb,gbo,gec,grj,grv,gry +hai,hax,hdn +hmn,cqd,hea,hma,hmc,hmd,hme,hmg,hmh,hmi,hmj,hml,hmm,hmp,hmq,hms,hmw,hmy,hmz,hnj,hrm,huj,mmr,muq,mww,sfm +jrb,aju,jye,yhd,yud +kln,enb,eyo,niq,oki,pko,sgc,spy,tec,tuy +kok,gom,knn +kpe,gkp,xpe +lah,hnd,hno,jat,phr,pnb,skr,xhe +luy,bxk,ida,lkb,lko,lks,lri,lrm,lsm,lto,lts,lwg,nle,nyd,rag +man,emk,mku,mlq,mnk,msc,mwk +mwr,dhd,mtr,mve,rwr,swv,wry +raj,bgq,gda,gju,hoj,mup,wbr +rom,rmc,rmf,rml,rmn,rmo,rmw,rmy +syr,aii,cld +tmh,taq,thv,thz,ttq +zap,zaa,zab,zac,zad,zae,zaf,zai,zam,zao,zaq,zar,zas,zat,zav,zaw,zax,zca,zcd,zoo,zpa,zpb,zpc,zpd,zpe,zpf,zpg,zph,zpi,zpj,zpk,zpl,zpm,zpn,zpo,zpp,zpq,zpr,zps,zpt,zpu,zpv,zpw,zpx,zpy,zpz,zsr,zte,ztg,ztl,ztm,ztn,ztp,ztq,zts,ztt,ztu,ztx,zty zza,diq,kiu diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 772de70a525..dc2cf578bfe 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -64,7 +64,7 @@ public void run() throws IOException { } Map> scriptToVariants = new TreeMap<>(); - Map> macroLangToLangs = new TreeMap<>(); + Map> macroLangToLangs = new TreeMap<>(LANGUGAE_TAG_COMPARATOR); for (Item item : items) { String type = item.firstValueOrThrow("Type"); @@ -78,7 +78,7 @@ public void run() throws IOException { switch (type) { case "language", "extlang" -> { item.firstValue("Macrolanguage").ifPresent(macroLang -> - macroLangToLangs.computeIfAbsent(macroLang, k -> new TreeSet<>()) + macroLangToLangs.computeIfAbsent(macroLang, k -> new TreeSet<>(LANGUGAE_TAG_COMPARATOR)) .add(subtag)); } case "variant" -> { @@ -236,4 +236,10 @@ void parse(BufferedReader reader) throws IOException { } } + private static final Comparator LANGUGAE_TAG_COMPARATOR = (lang1, lang2) -> { + if (lang1.length() != lang2.length()) + return Integer.compare(lang1.length(), lang2.length()); + else + return lang1.compareTo(lang2); + }; } From 63dfd9c91b291720a6def1ae5191950e7fa24656 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 16:59:20 +0800 Subject: [PATCH 16/35] update --- .../jackhuang/hmcl/util/i18n/LocaleUtils.java | 79 +++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index 783c951632d..e5eabe67d90 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -17,14 +17,14 @@ */ package org.jackhuang.hmcl.util.i18n; -import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.io.IOUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -46,60 +46,59 @@ public final class LocaleUtils { public static final String DEFAULT_LANGUAGE_KEY = "default"; - private static final Map subLanguageToParent = new HashMap<>(); - private static final Map iso3To2 = new HashMap<>(); - private static final Set rtl = new HashSet<>(); + private static final Map subLanguageToParent = loadCSV("sublanguages.csv"); + private static final Map iso3To2 = loadCSV("iso_languages.csv"); + private static final Set rtl = Set.copyOf(loadList("rtl.txt")); - static { - try { - for (String line : Lang.toIterable(IOUtils.readFullyAsString(LocaleUtils.class.getResourceAsStream("/assets/lang/sublanguages.csv")).lines())) { - if (line.startsWith("#") || line.isBlank()) { - continue; - } + /// Load CSV files located in `/assets/lang/`. + /// Each line in these files contains at least two elements. + /// + /// For example, if a file contains `value0,value1,value2`, the return value will be `{value1=value0, value2=value0}`. + private static Map loadCSV(String fileName) { + InputStream resource = LocaleUtils.class.getResourceAsStream("/assets/lang/" + fileName); + if (resource == null) { + LOG.warning("Can't find file: " + fileName); + return Map.of(); + } + + HashMap result = new HashMap<>(); + try (resource) { + new String(resource.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> { + if (line.startsWith("#") || line.isBlank()) + return; String[] languages = line.split(","); if (languages.length < 2) { - LOG.warning("Invalid line in sublanguages.csv: " + line); - continue; + LOG.warning("Invalid line in " + fileName + ": " + line); + return; } String parent = languages[0]; for (int i = 1; i < languages.length; i++) { - subLanguageToParent.put(languages[i], parent); + result.put(languages[i], parent); } - } + }); } catch (Throwable e) { - LOG.warning("Failed to load sublanguages.csv", e); + LOG.warning("Failed to load " + fileName, e); } - try { - // Line Format: (?[a-z]{2}),(?[a-z]{3}) - for (String line : Lang.toIterable(IOUtils.readFullyAsString(LocaleUtils.class.getResourceAsStream("/assets/lang/iso_languages.csv")).lines())) { - if (line.startsWith("#") || line.isBlank()) { - continue; - } - - String[] parts = line.split(",", 3); - if (parts.length != 2) { - LOG.warning("Invalid line in iso_languages.csv: " + line); - continue; - } + return Map.copyOf(result); + } - iso3To2.put(parts[1], parts[0]); - } - } catch (Throwable e) { - LOG.warning("Failed to load iso_languages.csv", e); + private static List loadList(String fileName) { + InputStream resource = LocaleUtils.class.getResourceAsStream("/assets/lang/" + fileName); + if (resource == null) { + LOG.warning("Can't find file: " + fileName); + return List.of(); } - try { - for (String line : Lang.toIterable(IOUtils.readFullyAsString(LocaleUtils.class.getResourceAsStream("/assets/lang/rtl.txt")).lines())) { - if (line.startsWith("#") || line.isBlank()) { - continue; - } - rtl.add(line.trim()); - } + try (resource) { + return new String(resource.readAllBytes(), StandardCharsets.UTF_8).lines() + .filter(line -> !line.startsWith("#") && !line.isBlank()) + .toList(); } catch (Throwable e) { - LOG.warning("Failed to load rtl.txt", e); + LOG.warning("Failed to load " + fileName, e); + return List.of(); } } From e64ff0124742ed1b500301cb890738bdb41b1814 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 17:01:34 +0800 Subject: [PATCH 17/35] update --- .../java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index e5eabe67d90..52fad76acad 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -48,6 +48,7 @@ public final class LocaleUtils { private static final Map subLanguageToParent = loadCSV("sublanguages.csv"); private static final Map iso3To2 = loadCSV("iso_languages.csv"); + private static final Map variantToScript = loadCSV("variant_to_script.csv"); private static final Set rtl = Set.copyOf(loadList("rtl.txt")); /// Load CSV files located in `/assets/lang/`. @@ -148,6 +149,12 @@ public static String toLanguageKey(Locale locale) { /// the script will be inferred based on the language, the region and the variant. public static @NotNull String getScript(Locale locale) { if (locale.getScript().isEmpty()) { + if (!locale.getVariant().isEmpty()) { + String script = variantToScript.get(locale.getVariant()); + if (script != null) + return script; + } + if (isEnglish(locale)) { if ("UD".equals(locale.getCountry())) { return "Qabs"; @@ -155,8 +162,6 @@ public static String toLanguageKey(Locale locale) { } if (isChinese(locale)) { - if (CHINESE_LATN_VARIANTS.contains(locale.getVariant())) - return "Latn"; if (locale.getLanguage().equals("lzh") || CHINESE_TRADITIONAL_REGIONS.contains(locale.getCountry())) return "Hant"; else @@ -378,7 +383,6 @@ public static boolean isEnglish(Locale locale) { } public static final Set CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO"); - public static final Set CHINESE_LATN_VARIANTS = Set.of("pinyin", "wadegile", "tongyong"); public static boolean isChinese(Locale locale) { return "zh".equals(getRootLanguage(locale)); From ae849b0f1e87b6fdb6c4fb8089a2713bf838dbf6 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 20:02:01 +0800 Subject: [PATCH 18/35] update --- .../org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java | 1 - .../hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java | 10 +++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java index 64bce19f464..5959bab7823 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java @@ -201,7 +201,6 @@ public void testMapToISO2Language() { assertEquals("zh", LocaleUtils.mapToISO2Language("zho")); assertEquals("zu", LocaleUtils.mapToISO2Language("zul")); - assertNull(LocaleUtils.mapToISO2Language(null)); assertNull(LocaleUtils.mapToISO2Language("")); assertNull(LocaleUtils.mapToISO2Language("cmn")); assertNull(LocaleUtils.mapToISO2Language("lzh")); diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index dc2cf578bfe..5c3d04ab79a 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -20,8 +20,6 @@ import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; @@ -41,8 +39,6 @@ /// @see [language-subtag-registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) public abstract class ParseLanguageSubtagRegistry extends DefaultTask { - private static final Logger LOGGER = Logging.getLogger(ParseLanguageSubtagRegistry.class); - @InputFile public abstract RegularFileProperty getLanguageSubtagRegistryFile(); @@ -64,7 +60,7 @@ public void run() throws IOException { } Map> scriptToVariants = new TreeMap<>(); - Map> macroLangToLangs = new TreeMap<>(LANGUGAE_TAG_COMPARATOR); + Map> macroLangToLangs = new TreeMap<>(LANGUAGE_TAG_COMPARATOR); for (Item item : items) { String type = item.firstValueOrThrow("Type"); @@ -78,7 +74,7 @@ public void run() throws IOException { switch (type) { case "language", "extlang" -> { item.firstValue("Macrolanguage").ifPresent(macroLang -> - macroLangToLangs.computeIfAbsent(macroLang, k -> new TreeSet<>(LANGUGAE_TAG_COMPARATOR)) + macroLangToLangs.computeIfAbsent(macroLang, k -> new TreeSet<>(LANGUAGE_TAG_COMPARATOR)) .add(subtag)); } case "variant" -> { @@ -236,7 +232,7 @@ void parse(BufferedReader reader) throws IOException { } } - private static final Comparator LANGUGAE_TAG_COMPARATOR = (lang1, lang2) -> { + private static final Comparator LANGUAGE_TAG_COMPARATOR = (lang1, lang2) -> { if (lang1.length() != lang2.length()) return Integer.compare(lang1.length(), lang2.length()); else From 61032a05f763f70c9463022f3314421c4f306c08 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 15 Oct 2025 20:06:29 +0800 Subject: [PATCH 19/35] update --- .../test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java index 5959bab7823..1fcb36b26d3 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java @@ -135,6 +135,7 @@ public void testGetScript() { assertEquals("Hant", LocaleUtils.getScript(Locale.forLanguageTag("lzh-CN"))); assertEquals("Latn", LocaleUtils.getScript(Locale.forLanguageTag("zh-pinyin"))); + assertEquals("Latn", LocaleUtils.getScript(Locale.forLanguageTag("ja-hepburn"))); } @Test From 5bdb63ce23a4904e58183fd87a51bf03b64287f6 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 15:47:34 +0800 Subject: [PATCH 20/35] update --- .../i18n/DefaultResourceBundleControl.java | 2 +- .../jackhuang/hmcl/util/i18n/LocaleUtils.java | 34 +++++-------------- ...iso_languages.csv => language_aliases.csv} | 0 .../hmcl/util/i18n/LocaleUtilsTest.java | 26 +++++++------- 4 files changed, 23 insertions(+), 39 deletions(-) rename HMCLCore/src/main/resources/assets/lang/{iso_languages.csv => language_aliases.csv} (100%) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/DefaultResourceBundleControl.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/DefaultResourceBundleControl.java index 6c41f3984f1..076faff830e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/DefaultResourceBundleControl.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/DefaultResourceBundleControl.java @@ -30,7 +30,7 @@ /// - For all Chinese locales, `zh-CN` is always added to the candidate list. If `zh-Hans` already exists in the candidate list, /// `zh-CN` is inserted before `zh`; otherwise, it is inserted after `zh`. /// - For all Traditional Chinese locales, `zh-TW` is always added to the candidate list (before `zh`). -/// - For all [supported][LocaleUtils#mapToISO2Language(String)] ISO 639-3 language code (such as `eng`, `zho`, `lzh`, etc.), +/// - For all supported ISO 639-3 language code (such as `eng`, `zho`, `lzh`, etc.), /// a candidate list with the language code replaced by the ISO 639-1 (Macro)language code is added to the end of the candidate list. /// /// @author Glavo diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index 52fad76acad..fc529dae148 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -47,7 +47,7 @@ public final class LocaleUtils { public static final String DEFAULT_LANGUAGE_KEY = "default"; private static final Map subLanguageToParent = loadCSV("sublanguages.csv"); - private static final Map iso3To2 = loadCSV("iso_languages.csv"); + private static final Map languageAliases = loadCSV("language_aliases.csv"); private static final Map variantToScript = loadCSV("variant_to_script.csv"); private static final Set rtl = Set.copyOf(loadList("rtl.txt")); @@ -133,13 +133,7 @@ public static String toLanguageKey(Locale locale) { /// - If `language` is empty, return `en`; /// - Otherwise, return the `language`. public static @NotNull String getRootLanguage(String language) { - if (language.isEmpty()) return "en"; - if (language.length() <= 2) - return language; - - String iso2 = mapToISO2Language(language); - if (iso2 != null) - return iso2; + language = normalizeLanguage(language); String parent = getParentLanguage(language); return parent != null ? parent : language; @@ -193,8 +187,6 @@ public static String toLanguageKey(Locale locale) { return CANDIDATE_LOCALES.computeIfAbsent(locale, LocaleUtils::createCandidateLocaleList); } - // ------------- - private static List createCandidateLocaleList(Locale locale) { String language = locale.getLanguage(); if (language.isEmpty()) @@ -208,17 +200,7 @@ private static List createCandidateLocaleList(Locale locale) { ArrayList result = new ArrayList<>(); do { - String currentLanguage; - - if (language.length() <= 2) { - currentLanguage = language; - } else { - String iso2 = mapToISO2Language(language); - currentLanguage = iso2 != null - ? iso2 - : language; - } - + String currentLanguage = normalizeLanguage(language); addCandidateLocales(result, currentLanguage, script, region, variants); } while ((language = getParentLanguage(language)) != null); @@ -366,10 +348,12 @@ else if (fileName.length() > defaultFileNameLength + 1 && fileName.charAt(baseNa // --- - /// Map ISO 639 alpha-3 language codes to ISO 639 alpha-2 language codes. - /// Returns `null` if there is no corresponding ISO 639 alpha-2 language code. - public static @Nullable String mapToISO2Language(String iso3Language) { - return iso3To2.get(iso3Language); + /// Normalize the language code to the code in the IANA Language Subtag Registry. + /// Typically, it normalizes ISO 639 alpha-3 codes to ISO 639 alpha-2 codes. + public static @NotNull String normalizeLanguage(String language) { + if (language.isEmpty()) + return "en"; + return languageAliases.getOrDefault(language, language); } /// If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage), diff --git a/HMCLCore/src/main/resources/assets/lang/iso_languages.csv b/HMCLCore/src/main/resources/assets/lang/language_aliases.csv similarity index 100% rename from HMCLCore/src/main/resources/assets/lang/iso_languages.csv rename to HMCLCore/src/main/resources/assets/lang/language_aliases.csv diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java index 1fcb36b26d3..9a75a08187f 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java @@ -193,19 +193,19 @@ public void testFindAllLocalizedFiles() throws IOException { } @Test - public void testMapToISO2Language() { - assertEquals("en", LocaleUtils.mapToISO2Language("eng")); - assertEquals("es", LocaleUtils.mapToISO2Language("spa")); - assertEquals("ja", LocaleUtils.mapToISO2Language("jpn")); - assertEquals("ru", LocaleUtils.mapToISO2Language("rus")); - assertEquals("uk", LocaleUtils.mapToISO2Language("ukr")); - assertEquals("zh", LocaleUtils.mapToISO2Language("zho")); - assertEquals("zu", LocaleUtils.mapToISO2Language("zul")); - - assertNull(LocaleUtils.mapToISO2Language("")); - assertNull(LocaleUtils.mapToISO2Language("cmn")); - assertNull(LocaleUtils.mapToISO2Language("lzh")); - assertNull(LocaleUtils.mapToISO2Language("tlh")); + public void testNormalizeLanguage() { + assertEquals("en", LocaleUtils.normalizeLanguage("eng")); + assertEquals("es", LocaleUtils.normalizeLanguage("spa")); + assertEquals("ja", LocaleUtils.normalizeLanguage("jpn")); + assertEquals("ru", LocaleUtils.normalizeLanguage("rus")); + assertEquals("uk", LocaleUtils.normalizeLanguage("ukr")); + assertEquals("zh", LocaleUtils.normalizeLanguage("zho")); + assertEquals("zu", LocaleUtils.normalizeLanguage("zul")); + + assertNull(LocaleUtils.normalizeLanguage("")); + assertNull(LocaleUtils.normalizeLanguage("cmn")); + assertNull(LocaleUtils.normalizeLanguage("lzh")); + assertNull(LocaleUtils.normalizeLanguage("tlh")); } @Test From 68f4a0654f838f0d8a0569a38dc6240cf1d7cbc7 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 15:50:56 +0800 Subject: [PATCH 21/35] update --- .../jackhuang/hmcl/util/i18n/LocaleUtils.java | 16 +++--- .../hmcl/gradle/l10n/LocalizationUtils.java | 56 ++++++++++--------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index fc529dae148..343c606a3f9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -68,15 +68,15 @@ private static Map loadCSV(String fileName) { if (line.startsWith("#") || line.isBlank()) return; - String[] languages = line.split(","); - if (languages.length < 2) { + String[] items = line.split(","); + if (items.length < 2) { LOG.warning("Invalid line in " + fileName + ": " + line); return; } - String parent = languages[0]; - for (int i = 1; i < languages.length; i++) { - result.put(languages[i], parent); + String parent = items[0]; + for (int i = 1; i < items.length; i++) { + result.put(items[i], parent); } }); } catch (Throwable e) { @@ -351,9 +351,9 @@ else if (fileName.length() > defaultFileNameLength + 1 && fileName.charAt(baseNa /// Normalize the language code to the code in the IANA Language Subtag Registry. /// Typically, it normalizes ISO 639 alpha-3 codes to ISO 639 alpha-2 codes. public static @NotNull String normalizeLanguage(String language) { - if (language.isEmpty()) - return "en"; - return languageAliases.getOrDefault(language, language); + return language.isEmpty() + ? "en" + : languageAliases.getOrDefault(language, language); } /// If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage), diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/LocalizationUtils.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/LocalizationUtils.java index 805c5f16715..f0f4da4b878 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/LocalizationUtils.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/LocalizationUtils.java @@ -19,36 +19,42 @@ import org.gradle.api.GradleException; -import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.*; final class LocalizationUtils { - public static final Map subLanguageToParent; - - static { - InputStream input = LocalizationUtils.class.getResourceAsStream("sublanguages.csv"); - if (input == null) - throw new GradleException("Missing sublanguages.csv file"); - - Map map = new HashMap<>(); - try (input) { - new String(input.readAllBytes()).lines() - .filter(line -> !line.startsWith("#") && !line.isBlank()) - .forEach(line -> { - String[] languages = line.split(","); - if (languages.length < 2) - throw new GradleException("Invalid line in sublanguages.csv: " + line); - - String parent = languages[0]; - for (int i = 1; i < languages.length; i++) { - map.put(languages[i], parent); - } - }); - } catch (IOException e) { - throw new GradleException("Failed to read sublanguages.csv", e); + public static final Map subLanguageToParent = loadCSV("sublanguages.csv"); + + private static Map loadCSV(String fileName) { + InputStream resource = LocalizationUtils.class.getResourceAsStream(fileName); + if (resource == null) { + throw new GradleException("Resource not found: " + fileName); + } + + HashMap result = new HashMap<>(); + try (resource) { + new String(resource.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> { + if (line.startsWith("#") || line.isBlank()) + return; + + String[] items = line.split(","); + if (items.length < 2) { + throw new GradleException("Invalid line in sublanguages.csv: " + line); + } + + String parent = items[0]; + for (int i = 1; i < items.length; i++) { + result.put(items[i], parent); + } + }); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new GradleException("Failed to load " + fileName, e); } - subLanguageToParent = Collections.unmodifiableMap(map); + + return Map.copyOf(result); } private static List resolveLanguage(String language) { From cb1df2a7a4ba3a386744cfd09a6cda5de4b3c9ef Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 16:04:37 +0800 Subject: [PATCH 22/35] update --- .../l10n/ParseLanguageSubtagRegistry.java | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 5c3d04ab79a..8c816d30949 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -28,7 +28,6 @@ import java.io.BufferedReader; import java.io.IOException; -import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.*; @@ -60,7 +59,8 @@ public void run() throws IOException { } Map> scriptToVariants = new TreeMap<>(); - Map> macroLangToLangs = new TreeMap<>(LANGUAGE_TAG_COMPARATOR); + Map> scriptToLanguages = new TreeMap<>(); + Map> languageToSub = new TreeMap<>(LANGUAGE_TAG_COMPARATOR); for (Item item : items) { String type = item.firstValueOrThrow("Type"); @@ -74,8 +74,13 @@ public void run() throws IOException { switch (type) { case "language", "extlang" -> { item.firstValue("Macrolanguage").ifPresent(macroLang -> - macroLangToLangs.computeIfAbsent(macroLang, k -> new TreeSet<>(LANGUAGE_TAG_COMPARATOR)) + languageToSub.computeIfAbsent(macroLang, k -> new TreeSet<>(LANGUAGE_TAG_COMPARATOR)) .add(subtag)); + + item.firstValue("Suppress-Script").ifPresent(script -> { + scriptToLanguages.computeIfAbsent(script, k -> new TreeSet<>(LANGUAGE_TAG_COMPARATOR)) + .add(subtag); + }); } case "variant" -> { List prefixes = item.allValues("Prefix"); @@ -107,26 +112,28 @@ public void run() throws IOException { } } - try (var writer = new PrintWriter(Files.newBufferedWriter(getSublanguagesFile().getAsFile().get().toPath(), + saveToCSV(languageToSub, getSublanguagesFile()); + saveToCSV(scriptToVariants, getVariantToScriptFile()); + } + + private static void saveToCSV(Map> allValues, RegularFileProperty csvFile) throws IOException { + try (var writer = Files.newBufferedWriter(csvFile.getAsFile().get().toPath(), StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING))) { - macroLangToLangs.forEach((k, v) -> { - writer.println(k + "," + String.join(",", v)); - }); + StandardOpenOption.TRUNCATE_EXISTING)) { - if (writer.checkError()) - throw new GradleException(); - } + for (Map.Entry> entry : allValues.entrySet()) { + String key = entry.getKey(); + Set values = entry.getValue(); - try (var writer = new PrintWriter(Files.newBufferedWriter(getVariantToScriptFile().getAsFile().get().toPath(), - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING))) { - scriptToVariants.forEach((k, v) -> { - writer.println(k + "," + String.join(",", v)); - }); + writer.write(key); + + for (String value : values) { + writer.write(','); + writer.write(value); + } - if (writer.checkError()) - throw new GradleException(); + writer.newLine(); + } } } From b0469536c434de472802f1b4d0f674dca3c4ebd4 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 16:18:41 +0800 Subject: [PATCH 23/35] update --- .../jackhuang/hmcl/util/i18n/LocaleUtils.java | 24 ++++--- .../resources/assets/lang/default_script.csv | 29 ++++++++ .../assets/lang/variant_to_script.csv | 1 - build.gradle.kts | 2 +- .../l10n/ParseLanguageSubtagRegistry.java | 68 ++++++++++--------- 5 files changed, 81 insertions(+), 43 deletions(-) create mode 100644 HMCLCore/src/main/resources/assets/lang/default_script.csv delete mode 100644 HMCLCore/src/main/resources/assets/lang/variant_to_script.csv diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index 343c606a3f9..f3017004ca2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -48,7 +48,7 @@ public final class LocaleUtils { private static final Map subLanguageToParent = loadCSV("sublanguages.csv"); private static final Map languageAliases = loadCSV("language_aliases.csv"); - private static final Map variantToScript = loadCSV("variant_to_script.csv"); + private static final Map defaultScript = loadCSV("default_script.csv"); private static final Set rtl = Set.copyOf(loadList("rtl.txt")); /// Load CSV files located in `/assets/lang/`. @@ -144,23 +144,27 @@ public static String toLanguageKey(Locale locale) { public static @NotNull String getScript(Locale locale) { if (locale.getScript().isEmpty()) { if (!locale.getVariant().isEmpty()) { - String script = variantToScript.get(locale.getVariant()); + String script = defaultScript.get(locale.getVariant()); if (script != null) return script; } - if (isEnglish(locale)) { - if ("UD".equals(locale.getCountry())) { - return "Qabs"; - } + String rootLanguage = getRootLanguage(locale.getLanguage()); + if ("en".equals(rootLanguage) && "UD".equals(locale.getCountry())) { + return "Qabs"; } + String script = defaultScript.get(rootLanguage); + if (script != null) + return script; + if (isChinese(locale)) { - if (locale.getLanguage().equals("lzh") || CHINESE_TRADITIONAL_REGIONS.contains(locale.getCountry())) - return "Hant"; - else - return "Hans"; + return CHINESE_TRADITIONAL_REGIONS.contains(locale.getCountry()) + ? "Hant" + : "Hans"; } + + return ""; } return locale.getScript(); diff --git a/HMCLCore/src/main/resources/assets/lang/default_script.csv b/HMCLCore/src/main/resources/assets/lang/default_script.csv new file mode 100644 index 00000000000..9b90629fa15 --- /dev/null +++ b/HMCLCore/src/main/resources/assets/lang/default_script.csv @@ -0,0 +1,29 @@ +Arab,ar,fa,ps,ur +Armn,hy +Beng,as,bn +Blis,zbl +Cyrl,ab,be,bg,kk,mk,ru,uk +Deva,hi,mr,ne,kok,mai +Ethi,am,ti +Geor,ka +Grek,el +Gujr,gu +Guru,pa +Hant,lzh +Hebr,he,yi +Jpan,ja +Khmr,km +Knda,kn +Kore,ko +Laoo,lo +Latn,af,ay,bs,ca,ch,cs,cy,da,de,en,eo,es,et,eu,fi,fj,fo,fr,fy,ga,gl,gn,gv,hr,ht,hu,id,is,it,kl,la,lb,ln,lt,lv,mg,mh,ms,mt,na,nb,nd,nl,nn,no,nr,ny,om,pl,pt,qu,rm,rn,ro,rw,sg,sk,sl,sm,so,sq,ss,st,sv,sw,tl,tn,to,tr,ts,ve,vi,xh,zu,dsb,frr,frs,gsw,hsb,men,nds,niu,nso,tem,tkl,tmh,tpi,tvl,tailo,pinyin,hepburn,pehoeji,tongyong,wadegile +Mlym,ml +Mymr,my +Nkoo,nqo +Orya,or +Sinh,si +Taml,ta +Telu,te +Thaa,dv +Thai,th +Tibt,dz diff --git a/HMCLCore/src/main/resources/assets/lang/variant_to_script.csv b/HMCLCore/src/main/resources/assets/lang/variant_to_script.csv deleted file mode 100644 index d61b855a24a..00000000000 --- a/HMCLCore/src/main/resources/assets/lang/variant_to_script.csv +++ /dev/null @@ -1 +0,0 @@ -Latn,hepburn,pehoeji,pinyin,tailo,tongyong,wadegile diff --git a/build.gradle.kts b/build.gradle.kts index 5d23d0e3b07..2c72a13cecc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -81,7 +81,7 @@ tasks.register("parseLanguageSubtagRegistry") { languageSubtagRegistryFile.set(layout.projectDirectory.file("language-subtag-registry")) sublanguagesFile.set(layout.projectDirectory.file("HMCLCore/src/main/resources/assets/lang/sublanguages.csv")) - variantToScriptFile.set(layout.projectDirectory.file("HMCLCore/src/main/resources/assets/lang/variant_to_script.csv")) + defaultScriptFile.set(layout.projectDirectory.file("HMCLCore/src/main/resources/assets/lang/default_script.csv")) } tasks.register("updateDocuments") { diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java index 8c816d30949..75ada0854de 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/ParseLanguageSubtagRegistry.java @@ -44,9 +44,9 @@ public abstract class ParseLanguageSubtagRegistry extends DefaultTask { @OutputFile public abstract RegularFileProperty getSublanguagesFile(); - /// CSV file storing the mapping from variants to their default scripts. + /// CSV file storing the mapping from subtag to their default scripts. @OutputFile - public abstract RegularFileProperty getVariantToScriptFile(); + public abstract RegularFileProperty getDefaultScriptFile(); @TaskAction public void run() throws IOException { @@ -58,9 +58,11 @@ public void run() throws IOException { items = builder.items; } - Map> scriptToVariants = new TreeMap<>(); - Map> scriptToLanguages = new TreeMap<>(); - Map> languageToSub = new TreeMap<>(LANGUAGE_TAG_COMPARATOR); + MultiMap scriptToSubtag = new MultiMap(); + MultiMap languageToSub = new MultiMap(); + + // Classical Chinese should use Traditional Chinese characters by default + scriptToSubtag.add("Hant", "lzh"); for (Item item : items) { String type = item.firstValueOrThrow("Type"); @@ -73,14 +75,11 @@ public void run() throws IOException { mainSwitch: switch (type) { case "language", "extlang" -> { - item.firstValue("Macrolanguage").ifPresent(macroLang -> - languageToSub.computeIfAbsent(macroLang, k -> new TreeSet<>(LANGUAGE_TAG_COMPARATOR)) - .add(subtag)); - - item.firstValue("Suppress-Script").ifPresent(script -> { - scriptToLanguages.computeIfAbsent(script, k -> new TreeSet<>(LANGUAGE_TAG_COMPARATOR)) - .add(subtag); - }); + item.firstValue("Macrolanguage") + .ifPresent(macroLang -> languageToSub.add(macroLang, subtag)); + + item.firstValue("Suppress-Script") + .ifPresent(script -> scriptToSubtag.add(script, subtag)); } case "variant" -> { List prefixes = item.allValues("Prefix"); @@ -101,8 +100,7 @@ public void run() throws IOException { } if (defaultScript != null) { - scriptToVariants.computeIfAbsent(defaultScript, k -> new TreeSet<>()) - .add(subtag); + scriptToSubtag.add(defaultScript, subtag); } } case "region", "script" -> { @@ -112,27 +110,35 @@ public void run() throws IOException { } } - saveToCSV(languageToSub, getSublanguagesFile()); - saveToCSV(scriptToVariants, getVariantToScriptFile()); + languageToSub.saveToCSV(getSublanguagesFile()); + scriptToSubtag.saveToCSV(getDefaultScriptFile()); } - private static void saveToCSV(Map> allValues, RegularFileProperty csvFile) throws IOException { - try (var writer = Files.newBufferedWriter(csvFile.getAsFile().get().toPath(), - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING)) { + private static final class MultiMap { + private final TreeMap> allValues = new TreeMap<>(TAG_COMPARATOR); - for (Map.Entry> entry : allValues.entrySet()) { - String key = entry.getKey(); - Set values = entry.getValue(); + void add(String key, String value) { + allValues.computeIfAbsent(key, k -> new TreeSet<>(TAG_COMPARATOR)).add(value); + } - writer.write(key); + void saveToCSV(RegularFileProperty csvFile) throws IOException { + try (var writer = Files.newBufferedWriter(csvFile.getAsFile().get().toPath(), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING)) { - for (String value : values) { - writer.write(','); - writer.write(value); - } + for (Map.Entry> entry : allValues.entrySet()) { + String key = entry.getKey(); + Set values = entry.getValue(); - writer.newLine(); + writer.write(key); + + for (String value : values) { + writer.write(','); + writer.write(value); + } + + writer.newLine(); + } } } } @@ -239,7 +245,7 @@ void parse(BufferedReader reader) throws IOException { } } - private static final Comparator LANGUAGE_TAG_COMPARATOR = (lang1, lang2) -> { + private static final Comparator TAG_COMPARATOR = (lang1, lang2) -> { if (lang1.length() != lang2.length()) return Integer.compare(lang1.length(), lang2.length()); else From 1faa2bbb80882392374c860b93d19fb311c1e8b8 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 16:20:02 +0800 Subject: [PATCH 24/35] update --- .../jackhuang/hmcl/util/i18n/LocaleUtils.java | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index f3017004ca2..1f44783f675 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -50,6 +50,7 @@ public final class LocaleUtils { private static final Map languageAliases = loadCSV("language_aliases.csv"); private static final Map defaultScript = loadCSV("default_script.csv"); private static final Set rtl = Set.copyOf(loadList("rtl.txt")); + private static final Set CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO"); /// Load CSV files located in `/assets/lang/`. /// Each line in these files contains at least two elements. @@ -123,6 +124,30 @@ public static String toLanguageKey(Locale locale) { : locale.stripExtensions().toLanguageTag(); } + public static boolean isEnglish(Locale locale) { + return "en".equals(getRootLanguage(locale)); + } + + public static boolean isChinese(Locale locale) { + return "zh".equals(getRootLanguage(locale)); + } + + // --- + + /// Normalize the language code to the code in the IANA Language Subtag Registry. + /// Typically, it normalizes ISO 639 alpha-3 codes to ISO 639 alpha-2 codes. + public static @NotNull String normalizeLanguage(String language) { + return language.isEmpty() + ? "en" + : languageAliases.getOrDefault(language, language); + } + + /// If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage), + /// return the macrolanguage; otherwise, return `null`. + public static @Nullable String getParentLanguage(String language) { + return subLanguageToParent.get(language); + } + public static @NotNull String getRootLanguage(Locale locale) { return getRootLanguage(locale.getLanguage()); } @@ -352,29 +377,6 @@ else if (fileName.length() > defaultFileNameLength + 1 && fileName.charAt(baseNa // --- - /// Normalize the language code to the code in the IANA Language Subtag Registry. - /// Typically, it normalizes ISO 639 alpha-3 codes to ISO 639 alpha-2 codes. - public static @NotNull String normalizeLanguage(String language) { - return language.isEmpty() - ? "en" - : languageAliases.getOrDefault(language, language); - } - - /// If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage), - /// return the macrolanguage; otherwise, return `null`. - public static @Nullable String getParentLanguage(String language) { - return subLanguageToParent.get(language); - } - - public static boolean isEnglish(Locale locale) { - return "en".equals(getRootLanguage(locale)); - } - - public static final Set CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO"); - - public static boolean isChinese(Locale locale) { - return "zh".equals(getRootLanguage(locale)); - } private LocaleUtils() { } From 866a83a086e11f6f012778bb535186c2c198645e Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 16:34:54 +0800 Subject: [PATCH 25/35] update --- .../jackhuang/hmcl/util/i18n/LocaleUtils.java | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index 1f44783f675..54e5b454ae6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -46,10 +46,14 @@ public final class LocaleUtils { public static final String DEFAULT_LANGUAGE_KEY = "default"; - private static final Map subLanguageToParent = loadCSV("sublanguages.csv"); - private static final Map languageAliases = loadCSV("language_aliases.csv"); - private static final Map defaultScript = loadCSV("default_script.csv"); - private static final Set rtl = Set.copyOf(loadList("rtl.txt")); + private static final Map PARENT_LANGUAGE = loadCSV("sublanguages.csv"); + private static final Map NORMALIZED_TAG = loadCSV("language_aliases.csv"); + private static final Map DEFAULT_SCRIPT = loadCSV("default_script.csv"); + private static final Map PREFERRED_LANGUAGE = Map.of( + "zh", "cmn" + ); + + private static final Set RTL_LANGUAGES = Set.copyOf(loadList("rtl.txt")); private static final Set CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO"); /// Load CSV files located in `/assets/lang/`. @@ -139,15 +143,16 @@ public static boolean isChinese(Locale locale) { public static @NotNull String normalizeLanguage(String language) { return language.isEmpty() ? "en" - : languageAliases.getOrDefault(language, language); + : NORMALIZED_TAG.getOrDefault(language, language); } /// If `language` is a sublanguage of a [macrolanguage](https://en.wikipedia.org/wiki/ISO_639_macrolanguage), /// return the macrolanguage; otherwise, return `null`. public static @Nullable String getParentLanguage(String language) { - return subLanguageToParent.get(language); + return PARENT_LANGUAGE.get(language); } + /// @see #getRootLanguage(String) public static @NotNull String getRootLanguage(Locale locale) { return getRootLanguage(locale.getLanguage()); } @@ -164,12 +169,20 @@ public static boolean isChinese(Locale locale) { return parent != null ? parent : language; } + /// If `language` is a macrolanguage, try to map it to the most commonly used individual language. + /// + /// For example, if `language` is `zh`, this method will return `cmn`. + public static @NotNull String getPreferredLanguage(String language) { + language = normalizeLanguage(language); + return PREFERRED_LANGUAGE.getOrDefault(language, language); + } + /// Get the script of the locale. If the script is empty and the language is Chinese, /// the script will be inferred based on the language, the region and the variant. public static @NotNull String getScript(Locale locale) { if (locale.getScript().isEmpty()) { if (!locale.getVariant().isEmpty()) { - String script = defaultScript.get(locale.getVariant()); + String script = DEFAULT_SCRIPT.get(locale.getVariant()); if (script != null) return script; } @@ -179,7 +192,7 @@ public static boolean isChinese(Locale locale) { return "Qabs"; } - String script = defaultScript.get(rootLanguage); + String script = DEFAULT_SCRIPT.get(rootLanguage); if (script != null) return script; @@ -196,7 +209,7 @@ public static boolean isChinese(Locale locale) { } public static @NotNull TextDirection getTextDirection(Locale locale) { - TextDirection direction = rtl.contains(getRootLanguage(locale)) + TextDirection direction = RTL_LANGUAGES.contains(getRootLanguage(locale)) ? TextDirection.RIGHT_TO_LEFT : TextDirection.LEFT_TO_RIGHT; @@ -217,10 +230,7 @@ public static boolean isChinese(Locale locale) { } private static List createCandidateLocaleList(Locale locale) { - String language = locale.getLanguage(); - if (language.isEmpty()) - return List.of(Locale.ENGLISH, Locale.ROOT); - + String language = getPreferredLanguage(locale.getLanguage()); String script = getScript(locale); String region = locale.getCountry(); List variants = locale.getVariant().isEmpty() @@ -229,8 +239,7 @@ private static List createCandidateLocaleList(Locale locale) { ArrayList result = new ArrayList<>(); do { - String currentLanguage = normalizeLanguage(language); - addCandidateLocales(result, currentLanguage, script, region, variants); + addCandidateLocales(result, language, script, region, variants); } while ((language = getParentLanguage(language)) != null); result.add(Locale.ROOT); From 19802fd5e5dd5859d34a83aac79dcecba43afaf9 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 17:02:46 +0800 Subject: [PATCH 26/35] update --- .../jackhuang/hmcl/util/i18n/LocaleUtils.java | 5 +- .../hmcl/util/i18n/LocaleUtilsTest.java | 111 +++++++++++------- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index 54e5b454ae6..76e0b7a124f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -187,12 +187,11 @@ public static boolean isChinese(Locale locale) { return script; } - String rootLanguage = getRootLanguage(locale.getLanguage()); - if ("en".equals(rootLanguage) && "UD".equals(locale.getCountry())) { + if ("UD".equals(locale.getCountry())) { return "Qabs"; } - String script = DEFAULT_SCRIPT.get(rootLanguage); + String script = DEFAULT_SCRIPT.get(normalizeLanguage(locale.getLanguage())); if (script != null) return script; diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java index 9a75a08187f..6a8f0a19f26 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java @@ -28,7 +28,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; @@ -41,55 +40,78 @@ private static void assertCandidateLocales(String languageTag, List cand LocaleUtils.getCandidateLocales(Locale.forLanguageTag(languageTag)) .stream() .map(Locale::toLanguageTag) - .collect(Collectors.toList())); + .toList()); + } + + private static void assertCandidateLocalesEquals(String l1, String l2) { + assertEquals( + LocaleUtils.getCandidateLocales(Locale.forLanguageTag(l1)), + LocaleUtils.getCandidateLocales(Locale.forLanguageTag(l2)) + ); } @Test public void testGetCandidateLocales() { - assertCandidateLocales("zh", List.of("zh-Hans", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-CN", List.of("zh-Hans-CN", "zh-Hans", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-SG", List.of("zh-Hans-SG", "zh-Hans", "zh-SG", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-MY", List.of("zh-Hans-MY", "zh-Hans", "zh-MY", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-US", List.of("zh-Hans-US", "zh-Hans", "zh-US", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-TW", List.of("zh-Hant-TW", "zh-Hant", "zh-TW", "zh", "zh-CN", "und")); - assertCandidateLocales("zh-HK", List.of("zh-Hant-HK", "zh-Hant", "zh-HK", "zh-TW", "zh", "zh-CN", "und")); - assertCandidateLocales("zh-MO", List.of("zh-Hant-MO", "zh-Hant", "zh-MO", "zh-TW", "zh", "zh-CN", "und")); - assertCandidateLocales("zh-Hans", List.of("zh-Hans", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-Hant", List.of("zh-Hant", "zh-TW", "zh", "zh-CN", "und")); - assertCandidateLocales("zh-Hans-US", List.of("zh-Hans-US", "zh-Hans", "zh-US", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-Hant-CN", List.of("zh-Hant-CN", "zh-Hant", "zh-CN", "zh-TW", "zh", "und")); - assertCandidateLocales("zh-Hans-TW", List.of("zh-Hans-TW", "zh-Hans", "zh-TW", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-Latn", List.of("zh-Latn", "zh", "zh-CN", "und")); - assertCandidateLocales("zh-Latn-CN", List.of("zh-Latn-CN", "zh-Latn", "zh-CN", "zh", "und")); - assertCandidateLocales("zh-pinyin", List.of("zh-Latn-pinyin", "zh-Latn", "zh-pinyin", "zh", "zh-CN", "und")); - assertCandidateLocales("zho", List.of("zh-Hans", "zh-CN", "zh", "und")); - assertCandidateLocales("lzh", List.of("lzh-Hant", "lzh", "zh-Hant", "zh-TW", "zh", "zh-CN", "und")); - assertCandidateLocales("lzh-Hant", List.of("lzh-Hant", "lzh", "zh-Hant", "zh-TW", "zh", "zh-CN", "und")); - assertCandidateLocales("lzh-Hans", List.of("lzh-Hans", "lzh", "zh-Hans", "zh-CN", "zh", "und")); - assertCandidateLocales("cmn", List.of("cmn-Hans", "cmn", "zh-Hans", "zh-CN", "zh", "und")); - assertCandidateLocales("cmn-Hans", List.of("cmn-Hans", "cmn", "zh-Hans", "zh-CN", "zh", "und")); - assertCandidateLocales("yue", List.of("yue-Hans", "yue", "zh-Hans", "zh-CN", "zh", "und")); + // English + + assertCandidateLocales("en", List.of("en-Latn", "en", "und")); + assertCandidateLocales("en-US", List.of("en-Latn-US", "en-Latn", "en-US", "en", "und")); + assertCandidateLocalesEquals("en", "eng"); + assertCandidateLocalesEquals("en-US", "eng-US"); + assertCandidateLocalesEquals("und", "en"); + + // Spanish + + assertCandidateLocales("es", List.of("es-Latn", "es", "und")); + assertCandidateLocalesEquals("es", "spa"); + + // Japanese + + assertCandidateLocales("ja", List.of("ja-Jpan", "ja", "und")); + assertCandidateLocales("ja-JP", List.of("ja-Jpan-JP", "ja-Jpan", "ja-JP", "ja", "und")); + assertCandidateLocalesEquals("ja", "jpn"); + assertCandidateLocalesEquals("ja-JP", "jpn-JP"); - assertCandidateLocales("ja", List.of("ja", "und")); - assertCandidateLocales("jpn", List.of("ja", "und")); - assertCandidateLocales("ja-JP", List.of("ja-JP", "ja", "und")); - assertCandidateLocales("jpn-JP", List.of("ja-JP", "ja", "und")); + // Russian - assertCandidateLocales("en", List.of("en", "und")); - assertCandidateLocales("eng", List.of("en", "und")); - assertCandidateLocales("en-US", List.of("en-US", "en", "und")); - assertCandidateLocales("eng-US", List.of("en-US", "en", "und")); + assertCandidateLocales("ru", List.of("ru-Cyrl", "ru", "und")); + assertCandidateLocalesEquals("ru", "rus"); - assertCandidateLocales("es", List.of("es", "und")); - assertCandidateLocales("spa", List.of("es", "und")); + // Ukrainian - assertCandidateLocales("ru", List.of("ru", "und")); - assertCandidateLocales("rus", List.of("ru", "und")); + assertCandidateLocales("uk", List.of("uk-Cyrl", "uk", "und")); + assertCandidateLocalesEquals("uk", "ukr"); + + // Chinese + + assertCandidateLocales("zh", List.of("cmn-Hans", "cmn", "zh-Hans", "zh-CN", "zh", "und")); + assertCandidateLocales("zh-CN", List.of("cmn-Hans-CN", "cmn-Hans", "cmn-CN", "cmn", "zh-Hans-CN", "zh-Hans", "zh-CN", "zh", "und")); + assertCandidateLocales("zh-SG", List.of("cmn-Hans-SG", "cmn-Hans", "cmn-SG", "cmn", "zh-Hans-SG", "zh-Hans", "zh-SG", "zh-CN", "zh", "und")); + + assertCandidateLocales("zh-TW", List.of("cmn-Hant-TW", "cmn-Hant", "cmn-TW", "cmn", "zh-Hant-TW", "zh-Hant", "zh-TW", "zh", "zh-CN", "und")); + assertCandidateLocales("zh-HK", List.of("cmn-Hant-HK", "cmn-Hant", "cmn-HK", "cmn", "zh-Hant-HK", "zh-Hant", "zh-HK", "zh-TW", "zh", "zh-CN", "und")); + assertCandidateLocales("zh-Hant-CN", List.of("cmn-Hant-CN", "cmn-Hant", "cmn-CN", "cmn", "zh-Hant-CN", "zh-Hant", "zh-CN", "zh-TW", "zh", "und")); + + assertCandidateLocales("zh-pinyin", List.of("cmn-Latn-pinyin", "cmn-Latn", "cmn-pinyin", "cmn", "zh-Latn-pinyin", "zh-Latn", "zh-pinyin", "zh", "zh-CN", "und")); + + assertCandidateLocales("lzh", List.of("lzh-Hant", "lzh", "zh-Hant", "zh-TW", "zh", "zh-CN", "und")); + assertCandidateLocales("yue", List.of("yue-Hans", "yue", "zh-Hans", "zh-CN", "zh", "und")); + + assertCandidateLocalesEquals("zh", "cmn-Hans"); + assertCandidateLocalesEquals("zh-CN", "cmn-Hans-CN"); + assertCandidateLocalesEquals("zh-SG", "cmn-Hans-SG"); + assertCandidateLocalesEquals("zh-MY", "cmn-Hans-MY"); + assertCandidateLocalesEquals("zh-TW", "cmn-Hant-TW"); + assertCandidateLocalesEquals("zh-HK", "cmn-Hant-HK"); + assertCandidateLocalesEquals("zh-Hans", "cmn-Hans"); + assertCandidateLocalesEquals("zh-Hant", "cmn-Hant"); + assertCandidateLocalesEquals("zh-Hant-CN", "cmn-Hant-CN"); + assertCandidateLocalesEquals("zh-Hant-SG", "cmn-Hant-SG"); + assertCandidateLocalesEquals("zh-Latn", "cmn-Latn"); + assertCandidateLocalesEquals("zh-pinyin", "cmn-Latn-pinyin"); + assertCandidateLocalesEquals("zho", "zh"); - assertCandidateLocales("uk", List.of("uk", "und")); - assertCandidateLocales("ukr", List.of("uk", "und")); - assertCandidateLocales("und", List.of("en", "und")); } @Test @@ -134,6 +156,7 @@ public void testGetScript() { assertEquals("Hant", LocaleUtils.getScript(Locale.forLanguageTag("lzh-Hant"))); assertEquals("Hant", LocaleUtils.getScript(Locale.forLanguageTag("lzh-CN"))); + assertEquals("Latn", LocaleUtils.getScript(Locale.forLanguageTag("en"))); assertEquals("Latn", LocaleUtils.getScript(Locale.forLanguageTag("zh-pinyin"))); assertEquals("Latn", LocaleUtils.getScript(Locale.forLanguageTag("ja-hepburn"))); } @@ -194,6 +217,7 @@ public void testFindAllLocalizedFiles() throws IOException { @Test public void testNormalizeLanguage() { + assertEquals("en", LocaleUtils.normalizeLanguage("")); assertEquals("en", LocaleUtils.normalizeLanguage("eng")); assertEquals("es", LocaleUtils.normalizeLanguage("spa")); assertEquals("ja", LocaleUtils.normalizeLanguage("jpn")); @@ -201,11 +225,8 @@ public void testNormalizeLanguage() { assertEquals("uk", LocaleUtils.normalizeLanguage("ukr")); assertEquals("zh", LocaleUtils.normalizeLanguage("zho")); assertEquals("zu", LocaleUtils.normalizeLanguage("zul")); - - assertNull(LocaleUtils.normalizeLanguage("")); - assertNull(LocaleUtils.normalizeLanguage("cmn")); - assertNull(LocaleUtils.normalizeLanguage("lzh")); - assertNull(LocaleUtils.normalizeLanguage("tlh")); + assertEquals("en", LocaleUtils.normalizeLanguage("")); + assertEquals("cmn", LocaleUtils.normalizeLanguage("cmn")); } @Test From e0a32c2ac198a0852947908a5c96ae844973e9aa Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 17:03:14 +0800 Subject: [PATCH 27/35] update --- .../main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index 76e0b7a124f..b1cb5f3671e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -383,9 +383,6 @@ else if (fileName.length() > defaultFileNameLength + 1 && fileName.charAt(baseNa return Map.of(); } - // --- - - private LocaleUtils() { } } From 09b2f50293fb5d63e25489b0ac07033aae22a5ba Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 20:19:34 +0800 Subject: [PATCH 28/35] update --- .../jackhuang/hmcl/util/i18n/LocaleUtils.java | 34 ++----------------- .../assets/lang/language_aliases.csv | 1 - .../hmcl/util/i18n/LocaleUtilsTest.java | 3 +- 3 files changed, 5 insertions(+), 33 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index b1cb5f3671e..05039ac2712 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -49,11 +49,9 @@ public final class LocaleUtils { private static final Map PARENT_LANGUAGE = loadCSV("sublanguages.csv"); private static final Map NORMALIZED_TAG = loadCSV("language_aliases.csv"); private static final Map DEFAULT_SCRIPT = loadCSV("default_script.csv"); - private static final Map PREFERRED_LANGUAGE = Map.of( - "zh", "cmn" - ); + private static final Map PREFERRED_LANGUAGE = Map.of("zh", "cmn"); + private static final Set RTL_SCRIPTS = Set.of("Qabs", "Arab", "Hebr"); - private static final Set RTL_LANGUAGES = Set.copyOf(loadList("rtl.txt")); private static final Set CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO"); /// Load CSV files located in `/assets/lang/`. @@ -91,23 +89,6 @@ private static Map loadCSV(String fileName) { return Map.copyOf(result); } - private static List loadList(String fileName) { - InputStream resource = LocaleUtils.class.getResourceAsStream("/assets/lang/" + fileName); - if (resource == null) { - LOG.warning("Can't find file: " + fileName); - return List.of(); - } - - try (resource) { - return new String(resource.readAllBytes(), StandardCharsets.UTF_8).lines() - .filter(line -> !line.startsWith("#") && !line.isBlank()) - .toList(); - } catch (Throwable e) { - LOG.warning("Failed to load " + fileName, e); - return List.of(); - } - } - private static Locale getInstance(String language, String script, String region, String variant) { Locale.Builder builder = new Locale.Builder(); @@ -208,18 +189,9 @@ public static boolean isChinese(Locale locale) { } public static @NotNull TextDirection getTextDirection(Locale locale) { - TextDirection direction = RTL_LANGUAGES.contains(getRootLanguage(locale)) + return RTL_SCRIPTS.contains(getScript(locale)) ? TextDirection.RIGHT_TO_LEFT : TextDirection.LEFT_TO_RIGHT; - - if ("Qabs".equals(getScript(locale))) { - direction = switch (direction) { - case RIGHT_TO_LEFT -> TextDirection.LEFT_TO_RIGHT; - case LEFT_TO_RIGHT -> TextDirection.RIGHT_TO_LEFT; - }; - } - - return direction; } private static final ConcurrentMap> CANDIDATE_LOCALES = new ConcurrentHashMap<>(); diff --git a/HMCLCore/src/main/resources/assets/lang/language_aliases.csv b/HMCLCore/src/main/resources/assets/lang/language_aliases.csv index 732d28d7ebd..4e828a31919 100644 --- a/HMCLCore/src/main/resources/assets/lang/language_aliases.csv +++ b/HMCLCore/src/main/resources/assets/lang/language_aliases.csv @@ -73,7 +73,6 @@ io,ido is,isl it,ita iu,iku -iw,heb ja,jpn ji,yid jv,jav diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java index 6a8f0a19f26..5cdf0a25972 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java @@ -249,10 +249,11 @@ public void testGetTextDirection() { assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag("zh"))); assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag("zh-Hans"))); assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag("zh-CN"))); - assertEquals(TextDirection.LEFT_TO_RIGHT, LocaleUtils.getTextDirection(Locale.forLanguageTag("ar-Qabs"))); assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("en-Qabs"))); assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("ar"))); assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("ara"))); + assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("he"))); + assertEquals(TextDirection.RIGHT_TO_LEFT, LocaleUtils.getTextDirection(Locale.forLanguageTag("heb"))); } } From bb0dca8c13dc1220ea321b4b525f35aa3aa56171 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 20:19:57 +0800 Subject: [PATCH 29/35] update --- .../src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index 05039ac2712..b61cf36335a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -51,7 +51,6 @@ public final class LocaleUtils { private static final Map DEFAULT_SCRIPT = loadCSV("default_script.csv"); private static final Map PREFERRED_LANGUAGE = Map.of("zh", "cmn"); private static final Set RTL_SCRIPTS = Set.of("Qabs", "Arab", "Hebr"); - private static final Set CHINESE_TRADITIONAL_REGIONS = Set.of("TW", "HK", "MO"); /// Load CSV files located in `/assets/lang/`. From 331c34de7ab68d5ff7b87515b477a04f7380ff37 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 21:59:15 +0800 Subject: [PATCH 30/35] update --- docs/Localization_zh.md | 137 ++++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 42 deletions(-) diff --git a/docs/Localization_zh.md b/docs/Localization_zh.md index 1df59934623..41432db4032 100644 --- a/docs/Localization_zh.md +++ b/docs/Localization_zh.md @@ -27,19 +27,17 @@ HMCL 为多种语言提供本地化支持。 HMCL 使用符合 IETF BCP 47 规范的语言标签。 -对于 ISO 639 标准中定义的语言,如果同时存在两字母语言代码和三字母语言代码,那么应当优先选择两字母语言代码。 +在选择语言标签时,我们会遵循以下原则: -例如,对于英语,我们使用 `en` 而不是 `eng` 作为语言代码。 +1. 对于 ISO 639 标准中定义的语言,如果已经在 [IANA 语言子标签注册表](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)中注册,我们总是用是使用经过注册的标签。 -对于 Minecraft 所定义的非标准语言,应当优先使用语言文件的 `language.code` 中定义的代码,而非游戏语言文件的名称 -(但对于存在两字母代码的语言,应当将三字母语言代码替换为对应的两字母语言代码)。 -这是因为 Minecraft 有时候会用现实中实际存在的国家/地区代码来表示虚构语言 (比如说海盗英语的语言文件为 `en_pt`,但 `PT` 其实是葡萄牙的国家代码)。 + 例如,对于英语,我们使用 `en` 而不是 `eng` 作为语言代码。 -例如,对于颠倒的英语,我们使用 `en-Qabs` 作为语言代码,而不是 `en-UD`。 +2. 对于 Minecraft 所定义的非标准语言,应当优先使用语言文件的 `language.code` 中定义的代码,而非游戏语言文件的名称。 -此外,语言代码中应当尽可能选择地区中立的语言标签。 + 这是因为 Minecraft 有时候会用现实中实际存在的国家/地区代码来表示虚构语言 (比如说海盗英语的语言文件为 `en_pt`,但 `PT` 其实是葡萄牙的国家代码)。 -例如,对于简体中文和繁体中文,我们使用 `zh-Hans`和 `zh-Hant` 作为语言代码,而不是 `zh-CN` 和 `zh-TW`。 + 例如,对于颠倒的英语,我们使用 `en-Qabs` 作为语言代码,而不是 `en-UD`。 @@ -106,7 +104,7 @@ HMCL 的绝大多数文本都位于这个文件中,翻译此文件就能翻译 这是一个 Java Properties 文件,格式非常简单。 在翻译前请先阅读该格式的介绍: [Properties 文件](https://en.wikipedia.org/wiki/.properties)。 -作为翻译的第一步,请从[这张表格](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes)中查询这个语言对应的两字母或三字母语言标签。 +作为翻译的第一步,请从[这张表格](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)中查询这个语言对应的两字母或三字母语言标签。 例如,英语的语言标签为 `en`。 在确定了语言标签后,请在 [`I18N.properties` 文件旁](../HMCL/src/main/resources/assets/lang)创建 `I18N_<语言标签>.properites` (例如 `I18N_en.properties`) 文件。 @@ -169,12 +167,62 @@ HMCL 的维护者会替你完成其他步骤。 对于某个语言下的缺失的资源,HMCL 支持一套资源回退机制,会根据不同的语言标签推导出一个搜索列表, 根据该列表依次搜索资源。 -例如,如果当前环境的语言标签为 `en-US`,那么 HMCL 会根据以下列表的顺序搜索对应的本地化资源: +在搜索前,我们会先通过以下步骤对语言标签进行细化推导。 -1. `en-US` +1. 归一化语言代码 + + 如果当前语言标签中的语言代码子标签未在 IANA 语言子标签注册表中进行注册,HMCL 会先尝试将其映射为注册表中已注册的标签。 + + 例如,HMCL会将语言代码 `eng` 替换为 `en`。 + +2. 映射宏语言至子语言 + + 如果当前语言代码是一个 [ISO 639 宏语言](https://en.wikipedia.org/wiki/ISO_639_macrolanguage), + 且该宏语言通常指代某个个体语言,HMCL 会将其替换为该个体语言。 + + 例如 `zh` (中文) 通常实际指代 `cmn` (官话),所以我们会将语言代码 `zh` 替换为 `cmn`。 + +3. 推导拼写脚本 + + 如果当前语言标签中未指定拼写脚本,HMCL 会依次根据以下规则尝试推导拼写脚本: + + 1. 如果当前语言标签指定了语言变体,该语言变体已在 IANA 语言子标签注册表中, + 且注册表中其所有 `Prefix` 都包含相同的拼写脚本,则将当前拼写脚本指定为该脚本。 + + 例如,如果当前语言变体为 `pinyin` (汉语拼音),则当前拼写脚本会被指定为 `Latn` (拉丁文)。 + + 2. 如果当前语言代码在 IANA 语言子标签注册表中被指定了 `Suppress-Script`,则将当前拼写脚本指定为该脚本。 + + 例如,如果当前语言代码为 `en` (英语),则当前拼写脚本会被指定为 `Latn` (拉丁文); + 如果当前语言代码为 `ru` (俄语),则当前拼写脚本会被指定为 `Cyrl` (西里尔文)。 + + 3. 如果当前语言代码是 `lzh` (文言),则将当前拼写脚本指定为 `Hant` (繁体汉字)。 + + 4. 如果当前语言代码是 `zh` 或 `zh` 的子语言,则检查当前国家/地区代码是否为 `TW`、`HK`、`MO` 之一。 + 如果结果为真,则将当前拼写脚本指定为 `Hant` (繁体汉字);否则将当前拼写脚本指定为 `Hans` (简体汉字)。 + +在对语言代码细化推导完成后,HMCL 会开始根据此语言标签推导出一个语言标签列表。 + +例如,对于语言标签 `en-US`,HMCL 会将其细化为 `en-Latn-US`,并据此推导出以下搜索列表: + +1. `en-Latn-US` +2. `en-Latn` +3. `en-US` 2. `en` 3. `und` +对于语言标签 `zh-CN`,HMCL 会将其细化为 `cmn-Hans-CN`,并据此推导出以下搜索列表: + +1. `cmn-Hans-CN` +2. `cmn-Hans` +3. `cmn-CN` +4. `cmn` +5. `zh-Hans-CN` +6. `zh-Hans` +7. `zh-CN` +8. `zh` +9. `und` + 对于能够混合的资源 (例如 `.properties` 文件),HMCL 会根据此列表的优先级混合资源; 对于难以混合的资源 (例如字体文件),HMCL 会根据此列表加载找到的最高优先级的资源。 @@ -182,35 +230,34 @@ HMCL 的维护者会替你完成其他步骤。 例如,如果当前环境的语言标签为 `eng-US`,那么 HMCL 会将其映射至 `en-US` 后再根据上述规则搜索本地化资源。 -如果当前语言是一个 [ISO 639 宏语言](https://en.wikipedia.org/wiki/ISO_639_macrolanguage)的子语言,那么 HMCL 也会搜索宏语言对应的资源。 - ### 对于中文的额外规则 -对于中文 (以及其子语言标签,例如文言文 (`lzh`)、普通话 (`cmn`)、粤语 (`yue`) 等等),HMCL 有着额外的支持。 - -如果当前环境的语言为中文 (及其子语言),且未指定书写脚本,那么 HMCL 会根据语言和地区标签推导出默认的书写脚本。 - -对于语言为 `lzh` 或地区为 `TW`、`HK`、`MO` 的情况,默认书写脚本为繁体中文 (`Hant`); -而对于其他语言和地区,默认书写脚本为简体中文 (`Hans`)。 - -此外,HMCL 会将 `zh-CN` 加入所有中文环境的搜索列表中,将 `zh-TW` 加入所有繁体中文环境的搜索列表中, -从而适应更多场景。 +对于中文 (以及其子语言标签,例如文言文 (`lzh`)、普通话 (`cmn`)、粤语 (`yue`) 等等), +HMCL 会将 `zh-CN` 加入所有中文环境的搜索列表中,将 `zh-TW` 加入所有繁体中文环境的搜索列表中。 以下是几个常见中文环境对应的本地化资源搜索列表。 - `zh-CN`: - 1. `zh-Hans-CN` - 2. `zh-Hans` - 3. `zh-CN` - 4. `zh` - 5. `und` + 1. `cmn-Hans-CN` + 2. `cmn-Hans` + 3. `cmn-CN` + 4. `cmn` + 5. `zh-Hans-CN` + 6. `zh-Hans` + 7. `zh-CN` + 8. `zh` + 9. `und` - `zh-SG`: - 1. `zh-Hans-SG` - 2. `zh-Hans` - 3. `zh-SG` - 4. `zh-CN` - 5. `zh` - 6. `und` + 1. `cmn-Hans-SG` + 2. `cmn-Hans` + 3. `cmn-SG` + 4. `cmn` + 5. `zh-Hans-SG` + 6. `zh-Hans` + 7. `zh-SG` + 8. `zh-CN` + 9. `zh` + 10. `und` - `zh-TW`: 1. `zh-Hant-TW` 2. `zh-Hant` @@ -219,19 +266,25 @@ HMCL 的维护者会替你完成其他步骤。 5. `zh-CN` 6. `und` - `zh-HK`: - 1. `zh-Hant-HK` - 2. `zh-Hant` - 3. `zh-HK` - 4. `zh-TW` - 5. `zh` - 6. `zh-CN` - 7. `und` + 1. `cmn-Hant-HK` + 2. `cmn-Hant` + 3. `cmn-HK` + 4. `cmn` + 5. `zh-Hant-HK` + 6. `zh-Hant` + 7. `zh-HK` + 8. `zh-TW` + 9. `zh` + 10. `zh-CN` + 11. `und` - `lzh`: 1. `lzh-Hant` 2. `lzh` 3. `zh-Hant` - 4. `zh` - 5. `und` + 4. `zh-TW` + 5. `zh` + 6. `zh-CN` + 7. `und` ## 自动同步文档内容 From f87aaa22ba611287959fe7a6810a1d564cd461b4 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 22:04:45 +0800 Subject: [PATCH 31/35] update --- docs/Localization_zh.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Localization_zh.md b/docs/Localization_zh.md index 41432db4032..55928d28d17 100644 --- a/docs/Localization_zh.md +++ b/docs/Localization_zh.md @@ -232,8 +232,7 @@ HMCL 的维护者会替你完成其他步骤。 ### 对于中文的额外规则 -对于中文 (以及其子语言标签,例如文言文 (`lzh`)、普通话 (`cmn`)、粤语 (`yue`) 等等), -HMCL 会将 `zh-CN` 加入所有中文环境的搜索列表中,将 `zh-TW` 加入所有繁体中文环境的搜索列表中。 +HMCL 总是会将 `zh-CN` 加入所有中文环境的搜索列表中,将 `zh-TW` 加入所有繁体中文环境的搜索列表中。 以下是几个常见中文环境对应的本地化资源搜索列表。 From 189e94a542046ed56827fcc3f61549ab953fe3e0 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 16 Oct 2025 22:18:57 +0800 Subject: [PATCH 32/35] Update docs/Localization_zh.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/Localization_zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Localization_zh.md b/docs/Localization_zh.md index 55928d28d17..f57d23c854e 100644 --- a/docs/Localization_zh.md +++ b/docs/Localization_zh.md @@ -29,7 +29,7 @@ HMCL 使用符合 IETF BCP 47 规范的语言标签。 在选择语言标签时,我们会遵循以下原则: -1. 对于 ISO 639 标准中定义的语言,如果已经在 [IANA 语言子标签注册表](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)中注册,我们总是用是使用经过注册的标签。 +1. 对于 ISO 639 标准中定义的语言,如果已经在 [IANA 语言子标签注册表](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)中注册,我们总是使用经过注册的标签。 例如,对于英语,我们使用 `en` 而不是 `eng` 作为语言代码。 From 44707625f63acaf4bedb3374a479e4ed4cec89bf Mon Sep 17 00:00:00 2001 From: Glavo Date: Fri, 17 Oct 2025 20:29:06 +0800 Subject: [PATCH 33/35] update --- .../test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java index 5cdf0a25972..6918ed2ab7e 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/i18n/LocaleUtilsTest.java @@ -110,8 +110,6 @@ public void testGetCandidateLocales() { assertCandidateLocalesEquals("zh-Latn", "cmn-Latn"); assertCandidateLocalesEquals("zh-pinyin", "cmn-Latn-pinyin"); assertCandidateLocalesEquals("zho", "zh"); - - } @Test From 28d1b5a4feaba68d4d6026e8062e4a85e0036e09 Mon Sep 17 00:00:00 2001 From: Glavo Date: Mon, 20 Oct 2025 20:46:07 +0800 Subject: [PATCH 34/35] update --- .../src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java index 9cf8f97446d..7cf7ece168e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocaleUtils.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.util.i18n; import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.platform.NativeUtils; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.windows.Kernel32; From 0a51647fe690c9a841e483edc7beb7393d77abbd Mon Sep 17 00:00:00 2001 From: Glavo Date: Mon, 20 Oct 2025 20:51:24 +0800 Subject: [PATCH 35/35] update --- HMCLCore/src/main/resources/assets/lang/rtl.txt | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 HMCLCore/src/main/resources/assets/lang/rtl.txt diff --git a/HMCLCore/src/main/resources/assets/lang/rtl.txt b/HMCLCore/src/main/resources/assets/lang/rtl.txt deleted file mode 100644 index cbd40fc72f4..00000000000 --- a/HMCLCore/src/main/resources/assets/lang/rtl.txt +++ /dev/null @@ -1,6 +0,0 @@ -ar -fa -he -ps -ur -yi \ No newline at end of file