https://github.com/linxueyuanstdio/mlang
Android 动态化多语言框架,支持语言包的动态下发、升级、删除,一处安装,到处使用
https://github.com/linxueyuanstdio/mlang
android
Last synced: about 1 year ago
JSON representation
Android 动态化多语言框架,支持语言包的动态下发、升级、删除,一处安装,到处使用
- Host: GitHub
- URL: https://github.com/linxueyuanstdio/mlang
- Owner: LinXueyuanStdio
- License: apache-2.0
- Created: 2020-10-23T06:21:17.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2023-01-30T06:23:16.000Z (over 3 years ago)
- Last Synced: 2025-04-03T06:51:11.379Z (about 1 year ago)
- Topics: android
- Language: Java
- Homepage:
- Size: 908 KB
- Stars: 112
- Watchers: 5
- Forks: 12
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# MLang 动态化多语言框架
`MLang` 是 MultiLanguage 的简写,是一款动态化的多语言框架。
设计优雅
- [x] 语言包存储格式为 xml 格式,和 res 下的 strings.xml 一致
- [x] 零依赖,完全使用系统 api 和系统的 xml 解析器
- [x] 不持有 context,无内存泄漏
- [x] 静态方法 + 单例模式,一处安装,到处使用
动态化语言包
- [x] 动态下发语言包
- [x] 语言包的增加、升级、删除
- [x] 语言包内部任意字符串的增加、升级、删除
- [x] 自定义语言包的存储路径
完全兼容
- [x] 跟随系统语言
- [x] 时间格式跟随系统的 24 小时制
- [x] 处理各种语言的时区、时间格式化问题
- [x] 处理各种语言的复数格式化问题
- [x] 处理各种语言的阅读顺序问题(从左到右、从右到左)
## 1. 使用
使用字符串
```java
// 本地和云端都存在的字符串
MyLang.getString("local_string", R.string.local_string)
// 云端存在 remote_string_only
// 但本地没有 R.string.remote_string_only,用 R.string.fallback_string 代替
MyLang.getString("remote_string_only", R.string.fallback_string)
```
使用语言包(语言包文件是 xml 格式,和 res 下的 strings.xml 一样。)
```java
//应用一种语言(这里自动处理了语言包的升级、语言包内部字符串的升级)
MyLang.getInstance().applyLanguage(Context, LocaleInfo, force=true, init=false);
//删除一种语言
MyLang.getInstance().deleteLanguage(Context, LocaleInfo);
```
`LocaleInfo` 可以在以下地方找到
```java
//1. 所有云端的语言包
MyLang.getInstance().remoteLanguages
//2. 所有下载到本地、可用的语言包
MyLang.getInstance().languages
//3. 所有非官方的语言包
MyLang.getInstance().unofficialLanguages
//4. 除内置支持的语言外,另外安装的云端的语言包
MyLang.getInstance().otherLanguages
```
## 2. 安装
2.1. 引入
```java
//build.gradle
allprojects {
repositories {
google()
jcenter()
maven { url "https://github.com/LinXueyuanStdio/MLang/raw/main/dist/" }
}
}
//app/build.gradle
implementation 'com.timecat.component:MLang:2.0.2'
```
2.2. 在 Application 中初始化,并监听系统语言的更改(如果跟随系统语言的话):
```java
public class MyApplication extends Application {
@SuppressLint("StaticFieldLeak")
public static volatile Context applicationContext;
public static volatile Handler applicationHandler;
@Override
public void onCreate() {
super.onCreate();
applicationContext = this;
applicationHandler = new Handler(applicationContext.getMainLooper());
MyLang.init(applicationContext);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
MyLang.onConfigurationChanged(newConfig);
}
}
```
其中建议自己新建一个静态类 MyLang 来代理 MLang。
这样有两个好处:
1. 隔绝 MLang 的 api 变化,提高兼容性和稳定性。
2. 使用更简洁。MLang 不持有 context,但每次获取字符串为空时,需要 context 来兜底,获取本地的字符串。在自己的 MyLang 默认提供 application Context,可以不用到处提供 context,更简洁。
```java
public class MyLang {
private static File filesDir;
private static LangAction action;
public static void init(@NonNull Context applicationContext) {
filesDir = applicationContext.getCacheDir();
action = new MyLangAction();
getInstance();
}
public static MLang getInstance() {
return MLang.getInstance(MyApplication.applicationContext, filesDir, action);
}
public static void onConfigurationChanged(@NonNull Configuration newConfig) {
getInstance().onDeviceConfigurationChange(getContext(), newConfig);
}
}
```
## 3. 设计
### 3.1. 单例模式接收 3 个参数,context,fileDir,action
1. context:MLang 内部不持有该 context。该 context 用于注册时区广播(根据时区来格式化字符串中的时间)、 判断系统当前时间是否 24 小时制等等。
2. filesDir:持久化语言包文件的存储地址。语言包文件是 xml 格式,和 res 下的 strings.xml 一样。
3. action:action 包含了应用语言包、切换语言等等需要的所有回调,即 `LangAction` 接口。
```java
MLang.getInstance(context, filesDir, action);
```
### 3.2. `LangAction` 接口定义了 2 个东西
1. 当前语言的设置存储。
MLang 根据语言 id (string) 来识别当前语言。语言 id 需要持久化。
所以设计了下面两个方法,可以自行决定持久化的方式(SharedPreferences、MMKV、SQLite等等)。
```java
void saveLanguageKeyInLocal(String language);
@Nullable String loadLanguageKeyInLocal();
```
2. 必要的网络接口。
```java
void langpack_getDifference(String lang_pack, String lang_code, int from_version, @NonNull final LangAction.GetDifferenceCallback callback)
void langpack_getLanguages(@NonNull final LangAction.GetLanguagesCallback callback)
void langpack_getLangPack(String lang_code, @NonNull final LangAction.GetLangPackCallback callback)
```
`LangAction` 的注释如下:
```java
public interface LangAction {
/**
* SharedPreferences preferences = Utilities.getGlobalMainSettings();
* SharedPreferences.Editor editor = preferences.edit();
* editor.putString("language", language);
* editor.commit();
* @param language localeInfo.getKey() 语言 id
*/
void saveLanguageKeyInLocal(String language);
/**
* SharedPreferences preferences = Utilities.getGlobalMainSettings();
* String lang = preferences.getString("language", null);
* @return @Nullable lang 语言 id
*/
String loadLanguageKeyInLocal();
/**
* 在其他线程网络请求,在主线程或UI线程调用callback
* 这里设计成这样,是因为这个方法里支持异步执行
* 您需要在合适的时机手动调用 callback,且只能调用一次
* @param lang_pack 语言包名字
* @param lang_code 语言包版本名称
* @param from_version 语言包版本号
* @param callback @NonNull 在主线程或UI线程调用
*/
void langpack_getDifference(String lang_pack, String lang_code, int from_version, GetDifferenceCallback callback);
/**
* 在其他线程网络请求,在主线程或UI线程调用callback
* 这里设计成这样,是因为这个方法里支持异步执行
* 您需要在合适的时机手动调用 callback,且只能调用一次
* @param callback @NonNull 在主线程或UI线程调用
*/
void langpack_getLanguages(GetLanguagesCallback callback);
/**
* 在其他线程网络请求,在主线程或UI线程调用callback
* 这里设计成这样,是因为这个方法里支持异步执行
* 您需要在合适的时机手动调用 callback,且只能调用一次
* @param lang_code 语言包版本名称
* @param callback @NonNull 在主线程或UI线程调用
*/
void langpack_getLangPack(String lang_code, GetLangPackCallback callback);
interface GetLanguagesCallback {
/**
* 必须在UI线程或者主线程调用
* 所有可用的语言包
* @param languageList 语言包列表
*/
void onLoad(List languageList);
}
interface GetDifferenceCallback {
/**
* 必须在UI线程或者主线程调用
* 如果服务端没有实现增量分发的功能,可以用完整的语言包代替
* @param languageList 增量的语言包
*/
void onLoad(LangPackDifference languageList);
}
interface GetLangPackCallback {
/**
* 必须在UI线程或者主线程调用
* @param languageList 完整的语言包
*/
void onLoad(LangPackDifference languageList);
}
}
```
实现`LangAction`的一个示例如下:
```java
public class MyLangAction implements LangAction {
@Override
public static void saveLanguageKeyInLocal(String language) {
SharedPreferences preferences = getContext().getSharedPreferences("language_locale", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("language", language);
editor.apply();
}
@Override
@Nullable
public static String loadLanguageKeyInLocal() {
SharedPreferences preferences = getContext().getSharedPreferences("language_locale", Context.MODE_PRIVATE);
return preferences.getString("language", null);
}
@Override
public void langpack_getDifference(String lang_pack, String lang_code, int from_version, @NonNull final LangAction.GetDifferenceCallback callback) {
Server.request_langpack_getDifference(lang_pack, lang_code, from_version, new Server.GetDifferenceCallback() {
@Override
public void onNext(final LangPackDifference difference) {
callback.onLoad(difference);
}
});
}
@Override
public void langpack_getLanguages(@NonNull final LangAction.GetLanguagesCallback callback) {
Server.request_langpack_getLanguages(new Server.GetLanguagesCallback() {
@Override
public void onNext(final List languageList) {
callback.onLoad(languageList);
}
});
}
@Override
public void langpack_getLangPack(String lang_code, @NonNull final LangAction.GetLangPackCallback callback) {
Server.request_langpack_getLangPack(lang_code, new Server.GetLangPackCallback() {
@Override
public void onNext(final LangPackDifference difference) {
callback.onLoad(difference);
}
});
}
}
```
### 3.3. 服务器语言包的结构
[模拟的服务器数据](https://github.com/LinXueyuanStdio/MLang/blob/main/app/src/main/java/com/timecat/ui/Server.java)
语言包实体
- `LangPackLanguage(name, version, ...)`
语言包的数据
- `LangPackDifference(name, version, List, ...)`
- `LangPackString(key: String, value: String)`
```java
public class Server {
public static LangPackLanguage chineseLanguage() {
LangPackLanguage langPackLanguage = new LangPackLanguage();
langPackLanguage.name = "chinese";
langPackLanguage.native_name = "简体中文";
langPackLanguage.lang_code = "zh";
langPackLanguage.base_lang_code = "zh";
return langPackLanguage;
}
public static LangPackDifference chinesePackDifference() {
LangPackDifference difference = new LangPackDifference();
difference.lang_code = "zh";
difference.from_version = 0;
difference.version = 1;
difference.strings = chineseStrings();
return difference;
}
public static ArrayList chineseStrings() {
ArrayList list = new ArrayList<>();
list.add(new LangPackString("LanguageName", "中文简体"));
list.add(new LangPackString("LanguageNameInEnglish", "Chinese"));
list.add(new LangPackString("local_string", "中文的云端字符串"));
list.add(new LangPackString("remote_string_only", "本地缺失,云端存在的字符串"));
return list;
}
}
```
## 4. 进阶配置
```java
MLang.isRTL = false; //是否从右到左阅读(默认 false)
MLang.is24HourFormat = false; //是否 24 小时制(默认 false)
MLang.USE_CLOUD_STRINGS = true; //是否使用云端字符串(默认 true)
```