From 49d88e13e6c710ad4ac875d3db13359883acff3f Mon Sep 17 00:00:00 2001 From: Timi Date: Mon, 14 Jul 2025 14:27:01 +0800 Subject: [PATCH] Initial project --- .gitignore | 128 +++------- .idea/.gitignore | 3 + .idea/encodings.xml | 7 + .idea/misc.xml | 14 ++ .idea/uiDesigner.xml | 124 ++++++++++ .idea/vcs.xml | 6 + pom.xml | 47 ++++ .../lang/mapper/AbstractLanguageMapper.java | 31 +++ .../imyeyu/lang/mapper/FileLanguageMap.java | 35 +++ .../com/imyeyu/lang/mapper/LanguageMap.java | 124 ++++++++++ .../lang/mapper/PropertiesLanguageMap.java | 27 +++ .../lang/mapper/ResourcesLanguageMap.java | 39 ++++ .../imyeyu/lang/multi/FileMultilingual.java | 29 +++ .../com/imyeyu/lang/multi/Multilingual.java | 218 ++++++++++++++++++ .../lang/multi/ResourcesMultilingual.java | 29 +++ 15 files changed, 767 insertions(+), 94 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 pom.xml create mode 100644 src/main/java/com/imyeyu/lang/mapper/AbstractLanguageMapper.java create mode 100644 src/main/java/com/imyeyu/lang/mapper/FileLanguageMap.java create mode 100644 src/main/java/com/imyeyu/lang/mapper/LanguageMap.java create mode 100644 src/main/java/com/imyeyu/lang/mapper/PropertiesLanguageMap.java create mode 100644 src/main/java/com/imyeyu/lang/mapper/ResourcesLanguageMap.java create mode 100644 src/main/java/com/imyeyu/lang/multi/FileMultilingual.java create mode 100644 src/main/java/com/imyeyu/lang/multi/Multilingual.java create mode 100644 src/main/java/com/imyeyu/lang/multi/ResourcesMultilingual.java diff --git a/.gitignore b/.gitignore index c6d98d1..5ff6309 100644 --- a/.gitignore +++ b/.gitignore @@ -1,98 +1,38 @@ -# ---> JetBrains -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -# ---> Maven target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar -.mvn/wrapper/maven-wrapper.jar +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ -# Eclipse m2e generated files -# Eclipse Core -.project -# JDT-specific (Eclipse Java Development Tools) +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated .classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fdc35ea --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..40d8ee0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + com.imyeyu.lang + timi-lang + 0.0.1 + jar + + + true + 21 + 21 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + UTF-8 + + + + + + + + com.imyeyu.io + timi-io + 0.0.1 + + + org.mariadb.jdbc + mariadb-java-client + 3.5.3 + test + + + diff --git a/src/main/java/com/imyeyu/lang/mapper/AbstractLanguageMapper.java b/src/main/java/com/imyeyu/lang/mapper/AbstractLanguageMapper.java new file mode 100644 index 0000000..c2d07ab --- /dev/null +++ b/src/main/java/com/imyeyu/lang/mapper/AbstractLanguageMapper.java @@ -0,0 +1,31 @@ +package com.imyeyu.lang.mapper; + +import com.imyeyu.java.bean.Language; +import com.imyeyu.java.bean.LanguageMapping; + +import java.text.MessageFormat; + +/** + * @author 夜雨 + * @version 2024-04-01 16:25 + */ +public abstract class AbstractLanguageMapper implements LanguageMapping { + + protected static final MessageFormat FORMAT = new MessageFormat(""); + + protected final Language language; + + protected boolean isDebugging = false; + + public AbstractLanguageMapper(Language language) { + this.language = language; + } + + public Language getLanguage() { + return language; + } + + public void setDebugging(boolean debugging) { + isDebugging = debugging; + } +} diff --git a/src/main/java/com/imyeyu/lang/mapper/FileLanguageMap.java b/src/main/java/com/imyeyu/lang/mapper/FileLanguageMap.java new file mode 100644 index 0000000..c837f05 --- /dev/null +++ b/src/main/java/com/imyeyu/lang/mapper/FileLanguageMap.java @@ -0,0 +1,35 @@ +package com.imyeyu.lang.mapper; + +import com.imyeyu.io.IO; +import com.imyeyu.java.bean.Language; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Properties; + +/** + * @author 夜雨 + * @version 2024-04-09 01:00 + */ +public class FileLanguageMap extends PropertiesLanguageMap { + + /** + * 默认构造 + * + * @param language 所属语言 + */ + public FileLanguageMap(Language language) { + super(language); + } + + public void load(String path) { + try { + InputStream is = IO.getInputStream(path.formatted(language.toString())); + Properties properties = new Properties(); + properties.load(new InputStreamReader(is)); + load(properties); + } catch (Exception e) { + throw new RuntimeException("load language properties file error", e); + } + } +} diff --git a/src/main/java/com/imyeyu/lang/mapper/LanguageMap.java b/src/main/java/com/imyeyu/lang/mapper/LanguageMap.java new file mode 100644 index 0000000..d79e8f8 --- /dev/null +++ b/src/main/java/com/imyeyu/lang/mapper/LanguageMap.java @@ -0,0 +1,124 @@ +package com.imyeyu.lang.mapper; + +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.Language; +import com.imyeyu.utils.Text; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author 夜雨 + * @version 2024-04-03 10:11 + */ +public class LanguageMap extends AbstractLanguageMapper { + + protected final Map map; + + /** + * 默认构造 + * + * @param language 所属语言 + */ + public LanguageMap(Language language) { + super(language); + this.map = new HashMap<>(); + } + + public void union(LanguageMap map) { + this.map.putAll(map.map); + } + + @Override + public void add(String key, String value) { + map.put(key, value); + } + + @Override + public boolean has(String key) { + return map.containsKey(key); + } + + /** + * 获取文本,支持二次映射,没有找到映射时返回入参键 + *
+	 *     test.msg=Hello world
+	 *     mapping=@test.msg
+	 *     no_mapping=\@test.msg
+	 *
+	 *     Multilingual.text("mapping");    // Hello world
+	 *     Multilingual.text("no_mapping"); // @test.msg
+	 * 
+ * + * @param key 键 + * @return 文本值 + */ + @Override + public String text(String key) { + if (map.containsKey(key)) { + String result = map.get(key); + if (result.startsWith("@")) { + // 递归映射 + return text(result.substring(1)); + } else { + if (result.startsWith("\\@")) { + return result.substring(1); + } else { + return result; + } + } + } else { + if (TimiJava.isNotEmpty(key)) { + // 推测 + String guessKey = key; + LinkedHashMap keySRL = Text.similarityRatioList(map.keySet(), key); + if (keySRL.keySet().iterator().hasNext()) { + String k = keySRL.keySet().iterator().next(); + Number ratio = keySRL.get(k); + if (.7 < ratio.doubleValue()) { + guessKey = k; + } + } + System.err.printf("not found language mapping for key: [%s], it is [%s]?%n", key, guessKey); + if (isDebugging) { + throw new RuntimeException("not found language mapping key: " + key); + } + } + } + // 找不到映射,直接返回键 + return key; + } + + /** + * 获取文本 + * + * @param key 键 + * @param def 默认值(没有找到映射值时) + * @return 获取结果 + */ + @Override + public String text(String key, String def) { + String result = text(key); + return key.equals(result) ? def : result; + } + + /** + * 插入参数获取文本 + * + * @param key 键 + * @param args 参数 + * @return 结果 + */ + @Override + public String textArgs(String key, Object... args) { + String result = text(key); + if (map.containsKey(result)) { + // 没有映射值 + return result + Arrays.toString(args); + } + FORMAT.applyPattern(result); + return FORMAT.format(args); + } +} diff --git a/src/main/java/com/imyeyu/lang/mapper/PropertiesLanguageMap.java b/src/main/java/com/imyeyu/lang/mapper/PropertiesLanguageMap.java new file mode 100644 index 0000000..18ae14a --- /dev/null +++ b/src/main/java/com/imyeyu/lang/mapper/PropertiesLanguageMap.java @@ -0,0 +1,27 @@ +package com.imyeyu.lang.mapper; + +import com.imyeyu.java.bean.Language; + +import java.util.HashMap; +import java.util.Properties; + +/** + * @author 夜雨 + * @version 2024-04-09 00:50 + */ +public class PropertiesLanguageMap extends LanguageMap { + + /** + * 默认构造 + * + * @param language 所属语言 + */ + public PropertiesLanguageMap(Language language) { + super(language); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public void load(Properties properties) { + map.putAll(new HashMap(properties)); + } +} diff --git a/src/main/java/com/imyeyu/lang/mapper/ResourcesLanguageMap.java b/src/main/java/com/imyeyu/lang/mapper/ResourcesLanguageMap.java new file mode 100644 index 0000000..0eae46d --- /dev/null +++ b/src/main/java/com/imyeyu/lang/mapper/ResourcesLanguageMap.java @@ -0,0 +1,39 @@ +package com.imyeyu.lang.mapper; + +import com.imyeyu.io.IO; +import com.imyeyu.java.bean.Language; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Properties; + +/** + * @author 夜雨 + * @version 2024-04-09 00:55 + */ +public class ResourcesLanguageMap extends PropertiesLanguageMap { + /** + * 默认构造 + * + * @param language 所属语言 + */ + public ResourcesLanguageMap(Language language) { + super(language); + } + + public void load(String path) { + try { + InputStream is = IO.resourceToInputStream(getClass(), path.formatted(language.toString())); + if (is != null) { + Properties properties = new Properties(); + properties.load(new InputStreamReader(is)); + load(properties); + } else { + throw new FileNotFoundException("not found language properties file: " + path.formatted(language.toString())); + } + } catch (Exception e) { + throw new RuntimeException("load language properties file error", e); + } + } +} diff --git a/src/main/java/com/imyeyu/lang/multi/FileMultilingual.java b/src/main/java/com/imyeyu/lang/multi/FileMultilingual.java new file mode 100644 index 0000000..3384666 --- /dev/null +++ b/src/main/java/com/imyeyu/lang/multi/FileMultilingual.java @@ -0,0 +1,29 @@ +package com.imyeyu.lang.multi; + +import com.imyeyu.java.bean.Language; +import com.imyeyu.lang.mapper.AbstractLanguageMapper; +import com.imyeyu.lang.mapper.FileLanguageMap; +import com.imyeyu.lang.mapper.LanguageMap; + +/** + * @author 夜雨 + * @version 2024-04-09 01:06 + */ +public class FileMultilingual extends Multilingual { + + public void addAll(String path) { + Language[] values = Language.values(); + for (int i = 0; i < values.length; i++) { + FileLanguageMap mapper = new FileLanguageMap(values[i]); + mapper.load(path); + if (multilingualMap.containsKey(values[i])) { + AbstractLanguageMapper existMapper = multilingualMap.get(values[i]); + if (existMapper instanceof LanguageMap map) { + map.union(mapper); + } + } else { + add(values[i], mapper); + } + } + } +} diff --git a/src/main/java/com/imyeyu/lang/multi/Multilingual.java b/src/main/java/com/imyeyu/lang/multi/Multilingual.java new file mode 100644 index 0000000..9eb4ff1 --- /dev/null +++ b/src/main/java/com/imyeyu/lang/multi/Multilingual.java @@ -0,0 +1,218 @@ +package com.imyeyu.lang.multi; + +import com.imyeyu.java.bean.CallbackArg; +import com.imyeyu.java.bean.Language; +import com.imyeyu.java.bean.LanguageMapping; +import com.imyeyu.java.ref.Ref; +import com.imyeyu.lang.mapper.AbstractLanguageMapper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** + * 多语言系统,支持多文件多语言加载 + *
+ *     // lang 文件
+ *     say.msg=hello {0}
+ *
+ *     // 加载
+ *     Multilingual lang = Multilingual.getInstance();
+ *     lang.load(Language.zh_CN, "lang/timijava/%s.lang");
+ *
+ *     // 加载所有支持的映射文件
+ *     lang.loadAll("lang/timifx/%s.lang");
+ *
+ *     // 设置当前激活语言
+ *     lang.setActivated("zh_CN");
+ *
+ *     // 引用当前激活语言
+ *     Multilingual.text("say.msg");
+ *     Multilingual.text("say.msg", "timi-java");
+ *
+ *     // 动态引用
+ *     Multilingual.map("en_US").text("say.msg");
+ * 
+ * + * @author 夜雨 + * @version 2021-10-25 15:16 + */ +public class Multilingual implements LanguageMapping { + + protected final Map multilingualMap; + protected final List> updateActiveListeners; + + /** 当前激活语言 */ + protected Language activated; + + protected boolean isDebugging = false; + + public Multilingual() { + multilingualMap = new HashMap<>(); + updateActiveListeners = new ArrayList<>(); + + activated = Ref.toType(Language.class, Locale.getDefault().toString()); + + if (activated == null) { + activated = Language.zh_CN; + } + } + + public void add(Language language, AbstractLanguageMapper mapper) { + mapper.setDebugging(isDebugging); + multilingualMap.put(language, mapper); + } + + /** + * 添加更新激活语言回调 + * + * @param callback 回调 + */ + public void addUpdateActiveListeners(CallbackArg callback) { + updateActiveListeners.add(callback); + } + + /** + * 移除更新激活语言回调 + * + * @param callback 回调 + */ + public void removeUpdateActiveListeners(CallbackArg callback) { + updateActiveListeners.remove(callback); + } + + /** + * 设置激活语言 + * + * @param languageName 激活语言 + */ + public void setActivated(String languageName) { + setActivated(Ref.toType(Language.class, languageName)); + } + + /** + * 设置激活语言 + * + * @param activated 激活语言 + */ + public void setActivated(Language activated) { + Objects.requireNonNull(activated); + this.activated = activated; + + String[] localSp = activated.toString().split("_"); + Locale.setDefault(new Locale(localSp[0], localSp[1])); + + for (int i = 0; i < updateActiveListeners.size(); i++) { + updateActiveListeners.get(i).handler(activated); + } + } + + public Language getActivated() { + return activated; + } + + public void setDebugging(boolean debugging) { + isDebugging = debugging; + for (Map.Entry item : multilingualMap.entrySet()) { + item.getValue().setDebugging(true); + } + } + + /** + * 获取指定映射数据 + * + * @param lang 语言 + * @return 映射表 + */ + public AbstractLanguageMapper map(String lang) { + Language language = Ref.toType(Language.class, lang); + if (language == null) { + throw new RuntimeException("not support language: " + lang); + } + return map(language); + } + + /** + * 获取指定映射数据 + * + * @param lang 语言 + * @return 映射表 + */ + public AbstractLanguageMapper map(Language lang) { + return multilingualMap.get(lang); + } + + @Override + public void add(String key, String value) { + multilingualMap.get(getActivated()).add(key, value); + } + + /** + * 当前激活语言是否存在语言映射 + * + * @param key 键 + * @return true 为存在 + */ + @Override + public boolean has(String key) { + if (multilingualMap.get(getActivated()) == null) { + return false; + } + return multilingualMap.get(getActivated()).has(key); + } + + /** + * 从当前激活语言获取文本,支持二次映射,没有找到映射时返回入参键 + *
+	 *     test.msg=Hello world
+	 *     mapping=@test.msg
+	 *     no_mapping=\@test.msg
+	 *
+	 *     Multilingual.text("mapping");    // Hello world
+	 *     Multilingual.text("no_mapping"); // @test.msg
+	 * 
+ * + * @param key 键 + * @return 文本值 + */ + @Override + public String text(String key) { + if (multilingualMap.get(getActivated()) == null) { + return key; + } + return multilingualMap.get(getActivated()).text(key); + } + + /** + * 从当前激活语言获取文本 + * + * @param key 键 + * @param def 默认值(没有找到映射值时) + * @return 获取结果 + */ + @Override + public String text(String key, String def) { + if (multilingualMap.get(getActivated()) == null) { + return Objects.requireNonNullElse(def, key); + } + return multilingualMap.get(getActivated()).text(key, def); + } + + /** + * 从当前激活语言获取插入参数文本 + * + * @param key 键 + * @param params 参数 + * @return 结果 + */ + @Override + public String textArgs(String key, Object... params) { + if (multilingualMap.get(getActivated()) == null) { + return key; + } + return multilingualMap.get(getActivated()).textArgs(key, params); + } +} diff --git a/src/main/java/com/imyeyu/lang/multi/ResourcesMultilingual.java b/src/main/java/com/imyeyu/lang/multi/ResourcesMultilingual.java new file mode 100644 index 0000000..b8cb341 --- /dev/null +++ b/src/main/java/com/imyeyu/lang/multi/ResourcesMultilingual.java @@ -0,0 +1,29 @@ +package com.imyeyu.lang.multi; + +import com.imyeyu.java.bean.Language; +import com.imyeyu.lang.mapper.AbstractLanguageMapper; +import com.imyeyu.lang.mapper.LanguageMap; +import com.imyeyu.lang.mapper.ResourcesLanguageMap; + +/** + * @author 夜雨 + * @version 2024-04-09 01:03 + */ +public class ResourcesMultilingual extends Multilingual { + + public void addAll(String path) { + Language[] values = Language.values(); + for (int i = 0; i < values.length; i++) { + ResourcesLanguageMap mapper = new ResourcesLanguageMap(values[i]); + mapper.load(path); + if (multilingualMap.containsKey(values[i])) { + AbstractLanguageMapper existMapper = multilingualMap.get(values[i]); + if (existMapper instanceof LanguageMap map) { + map.union(mapper); + } + } else { + add(values[i], mapper); + } + } + } +}