├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── issue_zh_template_suggest.yml
│ ├── issue_zh_template_question.yml
│ └── issue_zh_template_bug.yml
├── workflows
│ └── android.yml
└── FUNDING.yml
├── app
├── AppSignature.jks
├── gradle.properties
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ ├── xml
│ │ │ └── network_security_config.xml
│ │ ├── anim
│ │ │ ├── activity_alpha_in.xml
│ │ │ └── activity_alpha_out.xml
│ │ ├── values-zh-rCN
│ │ │ └── strings.xml
│ │ ├── values-zh-rTW
│ │ │ └── strings.xml
│ │ ├── values-ar
│ │ │ └── strings.xml
│ │ ├── values-en
│ │ │ └── strings.xml
│ │ ├── values-v23
│ │ │ └── styles.xml
│ │ └── layout
│ │ │ └── activity_main.xml
│ │ ├── java
│ │ └── com
│ │ │ └── hjq
│ │ │ └── language
│ │ │ └── demo
│ │ │ ├── BaseActivity.java
│ │ │ ├── LanguagesWebView.java
│ │ │ ├── AppApplication.java
│ │ │ ├── MainActivity.java
│ │ │ └── ActivityManager.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── picture
├── demo_code.png
└── dynamic_figure.gif
├── library
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── hjq
│ │ └── language
│ │ ├── OnLanguageListener.java
│ │ ├── ConfigurationObserver.java
│ │ ├── LocaleChangeReceiver.java
│ │ ├── LanguagesConfig.java
│ │ ├── ActivityLanguages.java
│ │ ├── LanguagesUtils.java
│ │ ├── MultiLanguages.java
│ │ └── LocaleContract.java
├── proguard-rules.pro
└── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── common.gradle
├── gradle.properties
├── settings.gradle
├── gradlew.bat
├── gradlew
├── HelpDoc.md
├── README.md
├── LICENSE
├── README-en.md
└── HelpDoc-en.md
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
--------------------------------------------------------------------------------
/app/AppSignature.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/app/AppSignature.jks
--------------------------------------------------------------------------------
/picture/demo_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/picture/demo_code.png
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/picture/dynamic_figure.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/picture/dynamic_figure.gif
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/gradle.properties:
--------------------------------------------------------------------------------
1 | StoreFile = AppSignature.jks
2 | StorePassword = AndroidProject
3 | KeyAlias = AndroidProject
4 | KeyPassword = AndroidProject
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/MultiLanguages/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.gradle
2 | /.idea
3 | /build
4 | */build
5 | /captures
6 | /.cxx
7 | */.cxx
8 | /.externalNativeBuild
9 |
10 | ._*
11 | *.iml
12 | .DS_Store
13 | local.properties
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 | #000000
5 | #FF0033
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | zipStoreBase = GRADLE_USER_HOME
2 | zipStorePath = wrapper/dists
3 | distributionBase = GRADLE_USER_HOME
4 | distributionPath = wrapper/dists
5 | distributionUrl = https\://services.gradle.org/distributions/gradle-7.4-all.zip
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: set up JDK 1.8
13 | uses: actions/setup-java@v1
14 | with:
15 | java-version: 1.8
16 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/activity_alpha_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/activity_alpha_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 国际化
4 | 简体中文
5 |
6 | 当前 Activity 语种:
7 | 当前 Application 语种:
8 | 当前 System 语种:
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 国际化
4 | 跟随系统
5 |
6 | 当前 Activity 语种:
7 | 当前 Application 语种:
8 | 当前 System 语种:
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 國際化
4 | 繁體中文
5 |
6 | 當前 Activity 語種:
7 | 當前 Application 語種:
8 | 當前 System 語種:
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ar/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | تدويل
4 | اتبع النظام
5 |
6 | النشاط الحالي اللغة:
7 | لغة التطبيق الحالية:
8 | لغة النظام الحالية:
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-en/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | MultiLanguages
4 | English
5 |
6 | Current Activity language:
7 | Current Application language:
8 | Current System language:
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hjq/language/demo/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language.demo;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 |
7 | import com.hjq.language.MultiLanguages;
8 |
9 | /**
10 | * author : Android 轮子哥
11 | * github : https://github.com/getActivity/MultiLanguages
12 | * time : 2019/08/10
13 | * desc : Activity 基类
14 | */
15 | public abstract class BaseActivity extends AppCompatActivity {
16 |
17 | @Override
18 | protected void attachBaseContext(Context newBase) {
19 | // 绑定语种
20 | super.attachBaseContext(MultiLanguages.attach(newBase));
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-v23/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_ali.png
13 |
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/language/OnLanguageListener.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language;
2 |
3 | import java.util.Locale;
4 |
5 | /**
6 | * author : Android 轮子哥
7 | * github : https://github.com/getActivity/MultiLanguages
8 | * time : 2021/01/18
9 | * desc : 语种变化监听器
10 | */
11 | public interface OnLanguageListener {
12 |
13 | /**
14 | * 当前应用语种发生变化时回调
15 | *
16 | * @param oldLocale 旧语种
17 | * @param newLocale 新语种
18 | */
19 | void onAppLocaleChange(Locale oldLocale, Locale newLocale);
20 |
21 | /**
22 | * 手机系统语种发生变化时回调
23 | *
24 | * @param oldLocale 旧语种
25 | * @param newLocale 新语种
26 | */
27 | void onSystemLocaleChange(Locale oldLocale, Locale newLocale);
28 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\SDK\Studio\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\SDK\Studio\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hjq/language/demo/LanguagesWebView.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language.demo;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.webkit.WebView;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 |
10 | import com.hjq.language.MultiLanguages;
11 |
12 | /**
13 | * author : Android 轮子哥
14 | * github : https://github.com/getActivity/MultiLanguages
15 | * time : 2019/08/10
16 | * desc : 修复语种的 WebView
17 | */
18 | public final class LanguagesWebView extends WebView {
19 |
20 | public LanguagesWebView(@NonNull Context context) {
21 | this(context, null);
22 | }
23 |
24 | public LanguagesWebView(@NonNull Context context, @Nullable AttributeSet attrs) {
25 | this(context, attrs, android.R.attr.webViewStyle);
26 | }
27 |
28 | public LanguagesWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
29 | super(context, attrs, defStyleAttr);
30 | // 修复 WebView 初始化时会修改 Activity 语种配置的问题
31 | MultiLanguages.updateAppLanguage(context);
32 | }
33 | }
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply from : '../common.gradle'
3 |
4 | android {
5 |
6 | defaultConfig {
7 | minSdkVersion 14
8 | }
9 |
10 | android.libraryVariants.configureEach { variant ->
11 | // aar 输出文件名配置
12 | variant.outputs.configureEach { output ->
13 | outputFileName = "${rootProject.name}-${android.defaultConfig.versionName}.aar"
14 | }
15 | }
16 | }
17 |
18 | tasks.withType(Javadoc).configureEach {
19 | options.addStringOption('Xdoclint:none', '-quiet')
20 | options.addStringOption('encoding', 'UTF-8')
21 | options.addStringOption('charSet', 'UTF-8')
22 | }
23 |
24 | tasks.register('sourcesJar', Jar) {
25 | from android.sourceSets.main.java.srcDirs
26 | classifier = 'sources'
27 | }
28 |
29 | tasks.register('javadoc', Javadoc) {
30 | source = android.sourceSets.main.java.srcDirs
31 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
32 | }
33 |
34 | tasks.register('javadocJar', Jar) {
35 | dependsOn javadoc
36 | classifier = 'javadoc'
37 | from javadoc.destinationDir
38 | }
--------------------------------------------------------------------------------
/common.gradle:
--------------------------------------------------------------------------------
1 | // 通用配置
2 | android {
3 |
4 | // 编译源码版本
5 | compileSdk 34
6 | defaultConfig {
7 | // Android 版本适配指南:https://github.com/getActivity/AndroidVersionAdapter
8 | targetSdk 34
9 | versionCode 1020
10 | versionName "10.2"
11 | }
12 |
13 | // 支持 Java JDK 8
14 | compileOptions {
15 | targetCompatibility JavaVersion.VERSION_1_8
16 | sourceCompatibility JavaVersion.VERSION_1_8
17 | }
18 |
19 | // 读取 local.properties 文件配置
20 | def properties = new Properties()
21 | def localPropertiesFile = rootProject.file("local.properties")
22 | if (localPropertiesFile.exists()) {
23 | localPropertiesFile.withInputStream { inputStream ->
24 | properties.load(inputStream)
25 | }
26 | }
27 |
28 | String buildDirPath = properties.getProperty("build.dir")
29 | if (buildDirPath != null && buildDirPath != "") {
30 | // 将构建文件统一输出到指定的目录下
31 | setBuildDir(new File(buildDirPath, rootProject.name + "/build/${path.replaceAll(':', '/')}"))
32 | } else {
33 | // 将构建文件统一输出到项目根目录下的 build 文件夹
34 | setBuildDir(new File(rootDir, "build/${path.replaceAll(':', '/')}"))
35 | }
36 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | # 表示使用 AndroidX
20 | android.useAndroidX = true
21 | # 表示将第三方库迁移到 AndroidX
22 | android.enableJetifier = true
23 |
24 | # Compilation failed while handling compileSdk 35
25 | # https://stackoverflow.com/questions/78678063/android-15-update-compilesdk-android-35-cause-an-error-res-table-type-type-e
26 | #android.aapt2Version=8.6.1-11315950
27 | #android.suppressUnsupportedCompileSdk=35
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/language/ConfigurationObserver.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language;
2 |
3 | import android.app.Application;
4 | import android.content.ComponentCallbacks;
5 | import android.content.res.Configuration;
6 |
7 | /**
8 | * author : Android 轮子哥
9 | * github : https://github.com/getActivity/MultiLanguages
10 | * time : 2019/05/06
11 | * desc : 手机配置变化监听
12 | */
13 | final class ConfigurationObserver implements ComponentCallbacks {
14 |
15 | /**
16 | * 注册系统语种变化监听
17 | */
18 | static void register(Application application) {
19 | ConfigurationObserver configurationObserver = new ConfigurationObserver(application);
20 | application.registerComponentCallbacks(configurationObserver);
21 | }
22 |
23 | private final Application mApplication;
24 |
25 | private ConfigurationObserver(Application application) {
26 | mApplication = application;
27 | }
28 |
29 | /**
30 | * 手机的配置发生了变化
31 | */
32 | @Override
33 | public void onConfigurationChanged(Configuration newConfig) {
34 | if (newConfig == null) {
35 | return;
36 | }
37 | // 如果当前是跟随系统语种,就则不往下执行
38 | if (MultiLanguages.isSystemLanguage(mApplication)) {
39 | return;
40 | }
41 | // 更新 Application 的配置,否则会出现横竖屏切换之后 Application 的 orientation 没有随之变化的问题
42 | LanguagesUtils.updateConfigurationChanged(mApplication, newConfig, MultiLanguages.getAppLanguage(mApplication));
43 | }
44 |
45 | @Override
46 | public void onLowMemory() {}
47 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_suggest.yml:
--------------------------------------------------------------------------------
1 | name: 提交建议
2 | description: 请告诉我框架的不足之处,让我做得更好!
3 | title: "[建议]:"
4 | labels: ["help wanted"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: 你觉得框架有什么不足之处?【必答】
15 | description: 你可以描述框架有什么令你不满意的地方
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: issue 是否有人曾提过类似的建议?【必答】
22 | description: 一旦出现重复提问我将不会再次解答
23 | multiple: false
24 | options:
25 | - 未选择
26 | - 是
27 | - 否
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: 框架文档是否提及了该问题【必答】
34 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
35 | multiple: false
36 | options:
37 | - 未选择
38 | - 是
39 | - 否
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: 是否已经查阅框架文档但还未能解决的【必答】
46 | description: 如果查阅了文档但还是没有解决的话,可以选择是
47 | multiple: false
48 | options:
49 | - 未选择
50 | - 是
51 | - 否
52 | validations:
53 | required: true
54 | - type: textarea
55 | id: input_id_5
56 | attributes:
57 | label: 你觉得该怎么去完善会比较好?【非必答】
58 | description: 你可以提供一下自己的想法或者做法供作者参考
59 | validations:
60 | required: false
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_question.yml:
--------------------------------------------------------------------------------
1 | name: 提出疑问
2 | description: 提出你的困惑,我会给你解答
3 | title: "[疑惑]:"
4 | labels: ["question"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: 问题描述【必填】
15 | description: 请描述一下你的问题(注意:如果确定是框架 bug 请不要在这里提,否则一概不受理)
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: 框架文档是否提及了该问题【必答】
22 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
23 | multiple: false
24 | options:
25 | - 未选择
26 | - 是
27 | - 否
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: 是否已经查阅框架文档但还未能解决的【必答】
34 | description: 如果查阅了文档但还是没有解决的话,可以选择是
35 | multiple: false
36 | options:
37 | - 未选择
38 | - 是
39 | - 否
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: issue 列表中是否有人曾提过类似的问题【必答】
46 | description: 可以在 issue 列表在搜索问题关键字,参考一下别人的解决方案
47 | multiple: false
48 | options:
49 | - 未选择
50 | - 是
51 | - 否
52 | validations:
53 | required: true
54 | - type: dropdown
55 | id: input_id_5
56 | attributes:
57 | label: 是否已经搜索过了 issue 列表但还未能解决的【必答】
58 | description: 如果搜索过了 issue 列表但是问题没有解决的话,可以选择是
59 | multiple: false
60 | options:
61 | - 未选择
62 | - 是
63 | - 否
64 | validations:
65 | required: true
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | // 阿里云云效仓库(Gradle 插件):https://maven.aliyun.com/mvn/guide
5 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
6 | // 阿里云云效仓库:https://maven.aliyun.com/mvn/guide
7 | maven { url 'https://maven.aliyun.com/repository/public' }
8 | maven { url 'https://maven.aliyun.com/repository/google' }
9 | // 华为开源镜像:https://mirrors.huaweicloud.com
10 | maven { url 'https://repo.huaweicloud.com/repository/maven' }
11 | // JitPack 远程仓库:https://jitpack.io
12 | maven { url 'https://jitpack.io' }
13 | // MavenCentral 远程仓库:https://mvnrepository.com
14 | mavenCentral()
15 | // Google 仓库:https://maven.google.com/web/index.html
16 | google()
17 | // noinspection JcenterRepositoryObsolete
18 | jcenter()
19 | }
20 | }
21 | dependencyResolutionManagement {
22 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
23 | repositories {
24 | // 阿里云云效仓库:https://maven.aliyun.com/mvn/guide
25 | maven { url 'https://maven.aliyun.com/repository/public' }
26 | maven { url 'https://maven.aliyun.com/repository/google' }
27 | // 华为开源镜像:https://mirrors.huaweicloud.com
28 | maven { url 'https://repo.huaweicloud.com/repository/maven' }
29 | // JitPack 远程仓库:https://jitpack.io
30 | maven { url 'https://jitpack.io' }
31 | // MavenCentral 远程仓库:https://mvnrepository.com
32 | mavenCentral()
33 | // Google 仓库:https://maven.google.com/web/index.html
34 | google()
35 | // noinspection JcenterRepositoryObsolete
36 | jcenter()
37 | }
38 | }
39 |
40 | include ':app'
41 | include ':library'
--------------------------------------------------------------------------------
/app/src/main/java/com/hjq/language/demo/AppApplication.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language.demo;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.content.Context;
6 | import android.util.Log;
7 | import com.hjq.language.MultiLanguages;
8 | import com.hjq.language.OnLanguageListener;
9 | import com.hjq.toast.Toaster;
10 | import java.util.List;
11 | import java.util.Locale;
12 |
13 | /**
14 | * author : Android 轮子哥
15 | * github : https://github.com/getActivity/MultiLanguages
16 | * time : 2019/08/10
17 | * desc : 应用入口
18 | */
19 | public final class AppApplication extends Application {
20 |
21 | // static {
22 | // // 设置默认的语种(越早设置越好)
23 | // MultiLanguages.setDefaultLanguage(LocaleContract.getEnglishLocale());
24 | // }
25 |
26 | @Override
27 | public void onCreate() {
28 | super.onCreate();
29 |
30 | ActivityManager.getInstance().init(this);
31 |
32 | // 初始化 Toast 框架
33 | Toaster.init(this);
34 |
35 | // 初始化多语种框架
36 | MultiLanguages.init(this);
37 | // 设置语种变化监听器
38 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
39 |
40 | @Override
41 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
42 | Log.i("MultiLanguages", "监听到应用切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale);
43 | }
44 |
45 | @Override
46 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
47 | Log.i("MultiLanguages", "监听到系统切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale +
48 | ",是否跟随系统:" + MultiLanguages.isSystemLanguage(AppApplication.this));
49 |
50 | if (!MultiLanguages.isSystemLanguage(AppApplication.this)) {
51 | return;
52 | }
53 | // 如需在系统切换语种后应用也要随之变化的,可以在这里获取所有的 Activity 并调用它的 recreate 方法
54 | // getAllActivity 只是演示代码,需要自行替换成项目已实现的方法,若项目中没有,请自行封装
55 | List activityList = ActivityManager.getInstance().getActivityList();
56 | for (Activity activity : activityList) {
57 | activity.recreate();
58 | }
59 | }
60 | });
61 | }
62 |
63 | @Override
64 | protected void attachBaseContext(Context newBase) {
65 | // 绑定语种
66 | super.attachBaseContext(MultiLanguages.attach(newBase));
67 | }
68 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/language/LocaleChangeReceiver.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language;
2 |
3 | import android.app.Application;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentFilter;
8 | import java.util.Locale;
9 |
10 | /**
11 | * author : Android 轮子哥
12 | * github : https://github.com/getActivity/MultiLanguages
13 | * time : 2023/11/24
14 | * desc : 语种变化广播
15 | */
16 | final class LocaleChangeReceiver extends BroadcastReceiver {
17 |
18 | /** 系统语种 */
19 | private static volatile Locale sSystemLanguage;
20 |
21 | static void register(Application application) {
22 | sSystemLanguage = LanguagesUtils.getSystemLocale(application);
23 | IntentFilter filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
24 | application.registerReceiver(new LocaleChangeReceiver(application), filter);
25 | }
26 |
27 | private final Application mApplication;
28 |
29 | public LocaleChangeReceiver(Application application) {
30 | mApplication = application;
31 | }
32 |
33 | @Override
34 | public void onReceive(Context context, Intent intent) {
35 | if (intent == null) {
36 | return;
37 | }
38 |
39 | String action = intent.getAction();
40 |
41 | if (action == null) {
42 | return;
43 | }
44 |
45 | if (!Intent.ACTION_LOCALE_CHANGED.equals(action)) {
46 | return;
47 | }
48 |
49 | if (sSystemLanguage == null) {
50 | return;
51 | }
52 |
53 | Locale latestSystemLocale = MultiLanguages.getSystemLanguage(mApplication);
54 | if (MultiLanguages.equalsLanguageAndCountry(latestSystemLocale, sSystemLanguage)) {
55 | return;
56 | }
57 |
58 | notifySystemLocaleChange(sSystemLanguage, latestSystemLocale);
59 | }
60 |
61 | /**
62 | * 通知系统语种发生变化
63 | */
64 | public void notifySystemLocaleChange(Locale oldLocale, Locale newLocale) {
65 | sSystemLanguage = newLocale;
66 |
67 | // 如果当前的语种是跟随系统变化的,那么就需要重置一下当前 App 的语种
68 | if (LanguagesConfig.isSystemLanguage(mApplication)) {
69 | LanguagesConfig.clearLanguageSetting(mApplication);
70 | }
71 |
72 | OnLanguageListener listener = MultiLanguages.getOnLanguagesListener();
73 | if (listener == null) {
74 | return;
75 | }
76 | listener.onSystemLocaleChange(oldLocale, newLocale);
77 | }
78 | }
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | // apply plugin: 'org.jetbrains.kotlin.android'
3 | apply from : '../common.gradle'
4 |
5 | android {
6 |
7 | defaultConfig {
8 | applicationId "com.hjq.language.demo"
9 | minSdkVersion 16
10 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
11 | }
12 |
13 | // Apk 签名的那些事:https://www.jianshu.com/p/a1f8e5896aa2
14 | signingConfigs {
15 | config {
16 | storeFile file(StoreFile)
17 | storePassword StorePassword
18 | keyAlias KeyAlias
19 | keyPassword KeyPassword
20 | }
21 | }
22 |
23 | buildTypes {
24 | debug {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
27 | signingConfig signingConfigs.config
28 | }
29 |
30 | release {
31 | minifyEnabled true
32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
33 | signingConfig signingConfigs.config
34 | }
35 | }
36 |
37 | applicationVariants.configureEach { variant ->
38 | // apk 输出文件名配置
39 | variant.outputs.configureEach { output ->
40 | outputFileName = rootProject.getName() + '.apk'
41 | }
42 | }
43 |
44 | bundle {
45 | language {
46 | enableSplit = false
47 | }
48 | }
49 |
50 | //androidResources {
51 | // 启用各应用自动设定语言支持:
52 | // https://developer.android.google.cn/guide/topics/resources/app-languages?hl=zh-cn#auto-localeconfig
53 | //generateLocaleConfig = true
54 | //}
55 | }
56 |
57 | dependencies {
58 | // 依赖 libs 目录下所有的 jar 和 aar 包
59 | implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
60 |
61 | implementation project(':library')
62 |
63 | // AndroidX 库:https://github.com/androidx/androidx
64 | implementation 'androidx.appcompat:appcompat:1.4.0'
65 | // Material 库:https://github.com/material-components/material-components-android
66 | implementation 'com.google.android.material:material:1.4.0'
67 |
68 | // 标题栏框架:https://github.com/getActivity/TitleBar
69 | implementation 'com.github.getActivity:TitleBar:10.6'
70 |
71 | // 吐司框架:https://github.com/getActivity/Toaster
72 | implementation 'com.github.getActivity:Toaster:12.8'
73 |
74 | // 内存泄漏检测:https://github.com/square/leakcanary
75 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
76 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/language/LanguagesConfig.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.text.TextUtils;
6 | import java.util.Locale;
7 |
8 | /**
9 | * author : Android 轮子哥
10 | * github : https://github.com/getActivity/MultiLanguages
11 | * time : 2019/05/03
12 | * desc : 语种配置保存类
13 | */
14 | final class LanguagesConfig {
15 |
16 | private static final String KEY_LANGUAGE = "key_language";
17 | private static final String KEY_COUNTRY = "key_country";
18 |
19 | private static String sSharedPreferencesName = "language_setting";
20 |
21 | /** 当前语种 */
22 | private static volatile Locale sCurrentLocale;
23 |
24 | /** 默认语种 */
25 | private static volatile Locale sDefaultLocale;
26 |
27 | static void setSharedPreferencesName(String name) {
28 | sSharedPreferencesName = name;
29 | }
30 |
31 | private static SharedPreferences getSharedPreferences(Context context) {
32 | return context.getSharedPreferences(sSharedPreferencesName, Context.MODE_PRIVATE);
33 | }
34 |
35 | /**
36 | * 读取 App 语种
37 | */
38 | static Locale readAppLanguageSetting(Context context) {
39 | if (sCurrentLocale != null) {
40 | return sCurrentLocale;
41 | }
42 |
43 | String language = getSharedPreferences(context).getString(KEY_LANGUAGE, "");
44 | String country = getSharedPreferences(context).getString(KEY_COUNTRY, "");
45 |
46 | if (!TextUtils.isEmpty(language)) {
47 | sCurrentLocale = new Locale(language, country);
48 | return sCurrentLocale;
49 | }
50 |
51 | if (sDefaultLocale != null) {
52 | sCurrentLocale = sDefaultLocale;
53 | return sCurrentLocale;
54 | }
55 |
56 | sCurrentLocale = LanguagesUtils.getLocale(context);
57 |
58 | return sCurrentLocale;
59 | }
60 |
61 | /**
62 | * 保存 App 语种设置
63 | */
64 | static void saveAppLanguageSetting(Context context, Locale locale) {
65 | sCurrentLocale = locale;
66 | getSharedPreferences(context).edit()
67 | .putString(KEY_LANGUAGE, locale.getLanguage())
68 | .putString(KEY_COUNTRY, locale.getCountry())
69 | .apply();
70 | }
71 |
72 | /**
73 | * 清除语种设置
74 | */
75 | static void clearLanguageSetting(Context context) {
76 | sCurrentLocale = MultiLanguages.getSystemLanguage(context);
77 | getSharedPreferences(context).edit()
78 | .remove(KEY_LANGUAGE)
79 | .remove(KEY_COUNTRY)
80 | .apply();
81 | }
82 |
83 | /**
84 | * 是否跟随系统
85 | */
86 | public static boolean isSystemLanguage(Context context) {
87 | if (sDefaultLocale != null) {
88 | return false;
89 | }
90 |
91 | String language = getSharedPreferences(context).getString(KEY_LANGUAGE, "");
92 | return TextUtils.isEmpty(language);
93 | }
94 |
95 | /**
96 | * 设置默认的语种
97 | */
98 | public static void setDefaultLanguage(Locale locale) {
99 | if (sCurrentLocale != null) {
100 | // 这个 API 需要越早调用越好,建议放在 Application 静态代码块中初始化
101 | // 当然也可以在 Application 调用 super.attachBaseContext 方法之前
102 | throw new IllegalStateException("Please set this before application initialization");
103 | }
104 | sDefaultLocale = locale;
105 | }
106 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_bug.yml:
--------------------------------------------------------------------------------
1 | name: 提交 Bug
2 | description: 请告诉我框架存在的问题,我会协助你解决此问题!
3 | title: "[Bug]:"
4 | labels: ["bug"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: input
12 | id: input_id_1
13 | attributes:
14 | label: 框架版本【必填】
15 | description: 请输入你使用的框架版本
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: input_id_2
20 | attributes:
21 | label: 问题描述【必填】
22 | description: 请输入你对这个问题的描述
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: input_id_3
27 | attributes:
28 | label: 复现步骤【必填】
29 | description: 请输入问题的复现步骤
30 | validations:
31 | required: true
32 | - type: dropdown
33 | id: input_id_4
34 | attributes:
35 | label: 是否必现【必填】
36 | multiple: false
37 | options:
38 | - 未选择
39 | - 是
40 | - 否
41 | validations:
42 | required: true
43 | - type: input
44 | id: input_id_5
45 | attributes:
46 | label: 项目 targetSdkVersion【必填】
47 | validations:
48 | required: true
49 | - type: input
50 | id: input_id_6
51 | attributes:
52 | label: 出现问题的手机信息【必填】
53 | description: 请填写出现问题的品牌和机型
54 | validations:
55 | required: true
56 | - type: input
57 | id: input_id_7
58 | attributes:
59 | label: 出现问题的安卓版本【必填】
60 | description: 请填写出现问题的 Android 版本
61 | validations:
62 | required: true
63 | - type: dropdown
64 | id: input_id_8
65 | attributes:
66 | label: 问题信息的来源渠道【必填】
67 | multiple: true
68 | options:
69 | - 自己遇到的
70 | - Bugly 看到的
71 | - 用户反馈
72 | - 其他渠道
73 | - type: input
74 | id: input_id_9
75 | attributes:
76 | label: 是部分机型还是所有机型都会出现【必答】
77 | description: 部分/全部(例如:某为,某 Android 版本会出现)
78 | validations:
79 | required: true
80 | - type: dropdown
81 | id: input_id_10
82 | attributes:
83 | label: 框架最新的版本是否存在这个问题【必答】
84 | description: 如果用的是旧版本的话,建议升级看问题是否还存在
85 | multiple: false
86 | options:
87 | - 未选择
88 | - 是
89 | - 否
90 | validations:
91 | required: true
92 | - type: dropdown
93 | id: input_id_11
94 | attributes:
95 | label: 框架文档是否提及了该问题【必答】
96 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
97 | multiple: false
98 | options:
99 | - 未选择
100 | - 是
101 | - 否
102 | validations:
103 | required: true
104 | - type: dropdown
105 | id: input_id_12
106 | attributes:
107 | label: 是否已经查阅框架文档但还未能解决的【必答】
108 | description: 如果查阅了文档但还是没有解决的话,可以选择是
109 | multiple: false
110 | options:
111 | - 未选择
112 | - 是
113 | - 否
114 | validations:
115 | required: true
116 | - type: dropdown
117 | id: input_id_13
118 | attributes:
119 | label: issue 列表中是否有人曾提过类似的问题【必答】
120 | description: 可以在 issue 列表在搜索问题关键字,参考一下别人的解决方案
121 | multiple: false
122 | options:
123 | - 未选择
124 | - 是
125 | - 否
126 | validations:
127 | required: true
128 | - type: dropdown
129 | id: input_id_14
130 | attributes:
131 | label: 是否已经搜索过了 issue 列表但还未能解决的【必答】
132 | description: 如果搜索过了 issue 列表但是问题没有解决的话,可以选择是
133 | multiple: false
134 | options:
135 | - 未选择
136 | - 是
137 | - 否
138 | validations:
139 | required: true
140 | - type: dropdown
141 | id: input_id_15
142 | attributes:
143 | label: 是否可以通过 Demo 来复现该问题【必答】
144 | description: 排查一下是不是自己的项目代码写得有问题导致的
145 | multiple: false
146 | options:
147 | - 未选择
148 | - 是
149 | - 否
150 | validations:
151 | required: true
152 | - type: textarea
153 | id: input_id_16
154 | attributes:
155 | label: 提供报错堆栈
156 | description: 如果有报错的话必填,注意不要拿被混淆过的代码堆栈上来
157 | render: text
158 | validations:
159 | required: false
160 | - type: textarea
161 | id: input_id_17
162 | attributes:
163 | label: 提供截图或视频
164 | description: 根据需要提供,此项不强制
165 | validations:
166 | required: false
167 | - type: textarea
168 | id: input_id_18
169 | attributes:
170 | label: 提供解决方案
171 | description: 如果已经解决了的话,此项不强制
172 | validations:
173 | required: false
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/language/ActivityLanguages.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.os.Bundle;
6 |
7 | /**
8 | * author : Android 轮子哥
9 | * github : https://github.com/getActivity/MultiLanguages
10 | * time : 2021/01/21
11 | * desc : Activity 语种注入
12 | */
13 | final class ActivityLanguages implements Application.ActivityLifecycleCallbacks {
14 |
15 | /*
16 | 这里解释一下,为什么要在 Activity 所有生命周期中刷新语种
17 | 这是因为发现有的手机厂商系统(例如 miui 系统)会偷摸修改 Activity 或 Application 绑定的语种
18 | Github 地址:https://github.com/getActivity/MultiLanguages/issues/52
19 | */
20 |
21 | static void inject(Application application) {
22 | application.registerActivityLifecycleCallbacks(new ActivityLanguages());
23 | }
24 |
25 | @Override
26 | public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
27 | refreshActivityAndApplicationLanguage(activity);
28 | }
29 |
30 | @Override
31 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
32 | refreshActivityAndApplicationLanguage(activity);
33 | }
34 |
35 | @Override
36 | public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
37 | refreshActivityAndApplicationLanguage(activity);
38 | }
39 |
40 | @Override
41 | public void onActivityPreStarted(Activity activity) {
42 | refreshActivityAndApplicationLanguage(activity);
43 | }
44 |
45 | @Override
46 | public void onActivityStarted(Activity activity) {
47 | refreshActivityAndApplicationLanguage(activity);
48 | }
49 |
50 | @Override
51 | public void onActivityPostStarted(Activity activity) {
52 | refreshActivityAndApplicationLanguage(activity);
53 | }
54 |
55 | @Override
56 | public void onActivityPreResumed(Activity activity) {
57 | refreshActivityAndApplicationLanguage(activity);
58 | }
59 |
60 | @Override
61 | public void onActivityResumed(Activity activity) {
62 | refreshActivityAndApplicationLanguage(activity);
63 | }
64 |
65 | @Override
66 | public void onActivityPostResumed(Activity activity) {
67 | refreshActivityAndApplicationLanguage(activity);
68 | }
69 |
70 | @Override
71 | public void onActivityPrePaused(Activity activity) {
72 | refreshActivityAndApplicationLanguage(activity);
73 | }
74 |
75 | @Override
76 | public void onActivityPaused(Activity activity) {
77 | refreshActivityAndApplicationLanguage(activity);
78 | }
79 |
80 | @Override
81 | public void onActivityPostPaused(Activity activity) {
82 | refreshActivityAndApplicationLanguage(activity);
83 | }
84 |
85 | @Override
86 | public void onActivityPreStopped(Activity activity) {
87 | refreshActivityAndApplicationLanguage(activity);
88 | }
89 |
90 | @Override
91 | public void onActivityStopped(Activity activity) {
92 | refreshActivityAndApplicationLanguage(activity);
93 | }
94 |
95 | @Override
96 | public void onActivityPostStopped(Activity activity) {
97 | refreshActivityAndApplicationLanguage(activity);
98 | }
99 |
100 | @Override
101 | public void onActivityPreSaveInstanceState(Activity activity, Bundle outState) {
102 | refreshActivityAndApplicationLanguage(activity);
103 | }
104 |
105 | @Override
106 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
107 | refreshActivityAndApplicationLanguage(activity);
108 | }
109 |
110 | @Override
111 | public void onActivityPostSaveInstanceState(Activity activity, Bundle outState) {
112 | refreshActivityAndApplicationLanguage(activity);
113 | }
114 |
115 | @Override
116 | public void onActivityPreDestroyed(Activity activity) {
117 | refreshActivityAndApplicationLanguage(activity);
118 | }
119 |
120 | @Override
121 | public void onActivityDestroyed(Activity activity) {
122 | refreshApplicationLanguage(activity.getApplication());
123 | }
124 |
125 | @Override
126 | public void onActivityPostDestroyed(Activity activity) {
127 | refreshApplicationLanguage(activity.getApplication());
128 | }
129 |
130 | /**
131 | * 刷新 Activity 和 Application 的语种
132 | */
133 | private void refreshActivityAndApplicationLanguage(Activity activity) {
134 | if (activity == null) {
135 | return;
136 | }
137 | MultiLanguages.updateAppLanguage(activity);
138 | refreshApplicationLanguage(activity.getApplication());
139 | }
140 |
141 | /**
142 | * 刷新 Application 的语种
143 | */
144 | private void refreshApplicationLanguage(Application application) {
145 | if (application == null) {
146 | return;
147 | }
148 | MultiLanguages.updateAppLanguage(application);
149 | }
150 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
25 |
26 |
30 |
31 |
38 |
39 |
45 |
46 |
53 |
54 |
61 |
62 |
69 |
70 |
77 |
78 |
79 |
80 |
85 |
86 |
90 |
91 |
98 |
99 |
100 |
105 |
106 |
110 |
111 |
117 |
118 |
119 |
125 |
126 |
130 |
131 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/language/LanguagesUtils.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language;
2 |
3 | import android.app.LocaleManager;
4 | import android.content.Context;
5 | import android.content.res.Configuration;
6 | import android.content.res.Resources;
7 | import android.os.Build;
8 | import android.os.LocaleList;
9 | import android.text.TextUtils;
10 | import java.util.Locale;
11 |
12 | /**
13 | * author : Android 轮子哥
14 | * github : https://github.com/getActivity/MultiLanguages
15 | * time : 2019/05/03
16 | * desc : 国际化工具类
17 | */
18 | @SuppressWarnings("deprecation")
19 | final class LanguagesUtils {
20 |
21 | /**
22 | * 获取语种对象
23 | */
24 | static Locale getLocale(Context context) {
25 | return getLocale(context.getResources().getConfiguration());
26 | }
27 |
28 | static Locale getLocale(Configuration config) {
29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
30 | return config.getLocales().get(0);
31 | } else {
32 | return config.locale;
33 | }
34 | }
35 |
36 | /**
37 | * 设置语种对象
38 | */
39 | static void setLocale(Configuration config, Locale locale) {
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
41 | LocaleList localeList = new LocaleList(locale);
42 | config.setLocales(localeList);
43 | // 修复在 Android 13 的联想平板上调用 setLocales 或者 setLocale 修改语种无效的问题
44 | // Github issue 地址:https://github.com/getActivity/MultiLanguages/pull/62
45 | if (!equalsLanguageAndCountry(locale, config.getLocales().get(0))) {
46 | config.locale = locale;
47 | }
48 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
49 | config.setLocale(locale);
50 | } else {
51 | config.locale = locale;
52 | }
53 |
54 | // 某些语种(例如:阿拉伯语)默认需支持 RTL 特性,以便符合阿拉伯人的阅读习惯
55 | // Github issue 地址:https://github.com/getActivity/MultiLanguages/pull/62
56 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
57 | config.setLayoutDirection(locale);
58 | }
59 | }
60 |
61 | /**
62 | * 获取系统的语种对象
63 | */
64 | static Locale getSystemLocale(Context context) {
65 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
66 | // 在 Android 13 上,不能用 Resources.getSystem() 来获取系统语种了
67 | // Android 13 上面新增了一个 LocaleManager 的语种管理类
68 | // 因为如果调用 LocaleManager.setApplicationLocales 会影响获取到的结果不准确
69 | // 所以应该得用 LocaleManager.getSystemLocales 来获取会比较精准
70 | LocaleManager localeManager = context.getSystemService(LocaleManager.class);
71 | if (localeManager != null) {
72 | return localeManager.getSystemLocales().get(0);
73 | }
74 | }
75 |
76 | return LanguagesUtils.getLocale(Resources.getSystem().getConfiguration());
77 | }
78 |
79 | /**
80 | * 获取默认的语种环境
81 | */
82 | static Locale getDefaultLocale() {
83 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
84 | return LocaleList.getDefault().get(0);
85 | } else {
86 | return Locale.getDefault();
87 | }
88 | }
89 |
90 | /**
91 | * 设置默认的语种环境(日期格式化会用到)
92 | */
93 | static void setDefaultLocale(Context context) {
94 | Configuration configuration = context.getResources().getConfiguration();
95 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
96 | LocaleList.setDefault(configuration.getLocales());
97 | } else {
98 | Locale.setDefault(configuration.locale);
99 | }
100 | }
101 |
102 | /**
103 | * 绑定当前 App 的语种
104 | */
105 | static Context attachLanguages(Context context, Locale locale) {
106 | Resources resources = context.getResources();
107 | Configuration config = new Configuration(resources.getConfiguration());
108 | setLocale(config, locale);
109 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
110 | context = context.createConfigurationContext(config);
111 | }
112 | resources.updateConfiguration(config, resources.getDisplayMetrics());
113 | return context;
114 | }
115 |
116 | /**
117 | * 更新 Resources 语种
118 | */
119 | static void updateLanguages(Resources resources, Locale locale) {
120 | Configuration config = resources.getConfiguration();
121 | setLocale(config, locale);
122 | resources.updateConfiguration(config, resources.getDisplayMetrics());
123 | }
124 |
125 | /**
126 | * 更新手机配置信息变化
127 | */
128 | static void updateConfigurationChanged(Context context, Configuration newConfig, Locale appLanguage) {
129 | Configuration config = new Configuration(newConfig);
130 | // 绑定当前语种到这个新的配置对象中
131 | setLocale(config, appLanguage);
132 | Resources resources = context.getResources();
133 | // 更新上下文的配置信息
134 | resources.updateConfiguration(config, resources.getDisplayMetrics());
135 | }
136 |
137 | /**
138 | * 生成某个语种下的 Resources 对象
139 | */
140 | static Resources generateLanguageResources(Context context, Locale locale) {
141 | Configuration config = new Configuration();
142 | setLocale(config, locale);
143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
144 | return context.createConfigurationContext(config).getResources();
145 | }
146 | return new Resources(context.getAssets(), context.getResources().getDisplayMetrics(), config);
147 | }
148 |
149 | /**
150 | * 对比两个语言是否同一个语种(比如:中文有简体和繁体,但是它们都属于同一个语种)
151 | */
152 | public static boolean equalsLanguage(Locale locale1, Locale locale2) {
153 | return TextUtils.equals(locale1.getLanguage(), locale2.getLanguage());
154 | }
155 |
156 | /**
157 | * 对比两个语言是否同一个语种并且同一个地方的(比如:中国大陆用的中文简体,中国台湾用的中文繁体)
158 | */
159 | public static boolean equalsLanguageAndCountry(Locale locale1, Locale locale2) {
160 | // Github issue 地址:https://github.com/getActivity/MultiLanguages/issues/63
161 | return TextUtils.equals(locale1.getLanguage(), locale2.getLanguage()) &&
162 | TextUtils.equals(locale1.getCountry(), locale2.getCountry());
163 | }
164 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hjq/language/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language.demo;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.text.TextUtils;
10 | import android.view.View;
11 | import android.webkit.WebChromeClient;
12 | import android.webkit.WebResourceRequest;
13 | import android.webkit.WebView;
14 | import android.webkit.WebViewClient;
15 | import android.widget.RadioGroup;
16 | import android.widget.TextView;
17 | import androidx.annotation.NonNull;
18 | import com.hjq.bar.OnTitleBarListener;
19 | import com.hjq.bar.TitleBar;
20 | import com.hjq.language.LocaleContract;
21 | import com.hjq.language.MultiLanguages;
22 | import java.util.HashMap;
23 | import java.util.Locale;
24 | import java.util.Map;
25 |
26 | /**
27 | * author : Android 轮子哥
28 | * github : https://github.com/getActivity/MultiLanguages
29 | * time : 2019/08/10
30 | * desc : 多语种切换演示
31 | */
32 | public final class MainActivity extends BaseActivity
33 | implements RadioGroup.OnCheckedChangeListener, OnTitleBarListener {
34 |
35 | private WebView mWebView;
36 | private TextView mSystemLanguageView;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_main);
42 |
43 | mWebView = findViewById(R.id.wv_main_web);
44 |
45 | TitleBar titleBar = findViewById(R.id.tb_main_bar);
46 | RadioGroup radioGroup = findViewById(R.id.rg_main_languages);
47 |
48 | titleBar.setOnTitleBarListener(this);
49 |
50 | mWebView.setWebViewClient(new LanguagesViewClient());
51 | mWebView.setWebChromeClient(new WebChromeClient());
52 | mWebView.loadUrl("https://developer.android.google.cn/kotlin", generateLanguageRequestHeader(this));
53 |
54 | //((TextView) findViewById(R.id.tv_language_activity)).setText(this.getResources().getString(R.string.current_language));
55 | ((TextView) findViewById(R.id.tv_main_language_application)).setText(
56 | getApplication().getResources().getString(R.string.current_language));
57 | mSystemLanguageView = findViewById(R.id.tv_main_language_system);
58 | mSystemLanguageView.setText(MultiLanguages.getLanguageString(this,
59 | MultiLanguages.getSystemLanguage(this), R.string.current_language));
60 |
61 | if (MultiLanguages.isSystemLanguage(this)) {
62 | radioGroup.check(R.id.rb_main_language_auto);
63 | } else {
64 | Locale locale = MultiLanguages.getAppLanguage(this);
65 | if (MultiLanguages.equalsLanguageAndCountry(locale, LocaleContract.getSimplifiedChineseLocale())) {
66 | radioGroup.check(R.id.rb_main_language_cn);
67 | } else if (MultiLanguages.equalsLanguageAndCountry(locale, LocaleContract.getTraditionalChineseLocale())) {
68 | radioGroup.check(R.id.rb_main_language_tw);
69 | } else if (MultiLanguages.equalsLanguageAndCountry(locale, LocaleContract.getEnglishLocale())) {
70 | radioGroup.check(R.id.rb_main_language_en);
71 | } else if (MultiLanguages.equalsLanguageAndCountry(locale, LocaleContract.getArabicLocale())) {
72 | radioGroup.check(R.id.rb_main_language_ar);
73 | } else {
74 | radioGroup.check(R.id.rb_main_language_auto);
75 | }
76 | }
77 |
78 | radioGroup.setOnCheckedChangeListener(this);
79 | }
80 |
81 | /**
82 | * {@link RadioGroup.OnCheckedChangeListener}
83 | */
84 | @Override
85 | public void onCheckedChanged(RadioGroup group, int checkedId) {
86 | // 是否需要重启
87 | boolean restart = false;
88 |
89 | if (checkedId == R.id.rb_main_language_auto) {
90 | // 跟随系统
91 | restart = MultiLanguages.clearAppLanguage(this);
92 | } else if (checkedId == R.id.rb_main_language_cn) {
93 | // 简体中文
94 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getSimplifiedChineseLocale());
95 | } else if (checkedId == R.id.rb_main_language_tw) {
96 | // 繁体中文
97 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getTraditionalChineseLocale());
98 | } else if (checkedId == R.id.rb_main_language_en) {
99 | // 英语
100 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getEnglishLocale());
101 | } else if (checkedId == R.id.rb_main_language_ar) {
102 | // 阿拉伯语
103 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getArabicLocale());
104 | }
105 |
106 | if (restart) {
107 | // 1.使用 recreate 来重启 Activity,效果差,有闪屏的缺陷
108 | // recreate();
109 |
110 | // 2.使用常规 startActivity 来重启 Activity,有从左向右的切换动画
111 | // 稍微比 recreate 的效果好一点,但是这种并不是最佳的效果
112 | // startActivity(new Intent(this, LanguageActivity.class));
113 | // finish();
114 |
115 | // 3.我们可以充分运用 Activity 跳转动画,在跳转的时候设置一个渐变的效果,相比前面的两种带来的体验是最佳的
116 | // startActivity(new Intent(this, MainActivity.class));
117 | // overridePendingTransition(R.anim.activity_alpha_in, R.anim.activity_alpha_out);
118 | // finish();
119 |
120 | // 4. 我们可以充分运用 Activity 跳转动画,在跳转的时候设置一个渐变的效果
121 | Intent intent = new Intent(this, MainActivity.class);
122 | // Github 地址:https://github.com/getActivity/MultiLanguages/issues/55
123 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
124 | startActivity(intent);
125 | overridePendingTransition(R.anim.activity_alpha_in, R.anim.activity_alpha_out);
126 | finish();
127 | }
128 | }
129 |
130 | @Override
131 | public void onResume() {
132 | super.onResume();
133 | mWebView.onResume();
134 | mWebView.resumeTimers();
135 |
136 | if (mSystemLanguageView == null) {
137 | return;
138 | }
139 | mSystemLanguageView.setText(MultiLanguages.getLanguageString(this,
140 | MultiLanguages.getSystemLanguage(this), R.string.current_language));
141 | }
142 |
143 | @Override
144 | public void onPause() {
145 | super.onPause();
146 | mWebView.onPause();
147 | mWebView.pauseTimers();
148 | }
149 |
150 | @Override
151 | protected void onDestroy() {
152 | super.onDestroy();
153 | //清除历史记录
154 | mWebView.clearHistory();
155 | //停止加载
156 | mWebView.stopLoading();
157 | //加载一个空白页
158 | mWebView.loadUrl("about:blank");
159 | mWebView.setWebChromeClient(null);
160 | mWebView.setWebViewClient(new WebViewClient());
161 | //移除WebView所有的View对象
162 | mWebView.removeAllViews();
163 | //销毁此的WebView的内部状态
164 | mWebView.destroy();
165 | }
166 |
167 | @Override
168 | public void onTitleClick(TitleBar titleBar) {
169 | Intent intent = new Intent(Intent.ACTION_VIEW);
170 | intent.setData(Uri.parse(titleBar.getTitle().toString()));
171 | startActivity(intent);
172 | }
173 |
174 | public static class LanguagesViewClient extends WebViewClient {
175 |
176 | @TargetApi(Build.VERSION_CODES.N)
177 | @Override
178 | public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
179 | return shouldOverrideUrlLoading(view, request.getUrl().toString());
180 | }
181 |
182 | @Override
183 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
184 | String scheme = Uri.parse(url).getScheme();
185 | if (scheme == null) {
186 | return false;
187 | }
188 | switch (scheme) {
189 | // 如果这是跳链接操作
190 | case "http":
191 | case "https":
192 | view.loadUrl(url, generateLanguageRequestHeader(view.getContext()));
193 | break;
194 | default:
195 | break;
196 | }
197 | return true;
198 | }
199 |
200 | @Override
201 | public void onPageCommitVisible(WebView view, String url) {
202 | super.onPageCommitVisible(view, url);
203 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
204 | return;
205 | }
206 | Locale locale = MultiLanguages.getAppLanguage(view.getContext());
207 | if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_LTR) {
208 | return;
209 | }
210 | // 将水平水平滚动条滚动到最右边,以便更好适配 RTL 特性
211 | // Github issue 地址:https://github.com/getActivity/MultiLanguages/pull/62
212 | view.scrollTo(view.getWidth(), 0);
213 | }
214 | }
215 |
216 | /**
217 | * 给 WebView 请求头添加语种环境
218 | */
219 | @NonNull
220 | public static Map generateLanguageRequestHeader(Context context) {
221 | Map map = new HashMap<>(1);
222 | // Android 13 上面语种失效的问题解决方案
223 | // https://developer.android.google.cn/about/versions/13/features/app-languages?hl=zh-cn#consider-header
224 | map.put("Accept-Language", String.valueOf(MultiLanguages.getAppLanguage(context)));
225 | return map;
226 | }
227 | }
--------------------------------------------------------------------------------
/HelpDoc.md:
--------------------------------------------------------------------------------
1 | #### 目录
2 |
3 | * [如何设置 App 默认的语种](#如何设置-app-默认的语种)
4 |
5 | * [切换语种后没有任何效果该怎么办](#切换语种后没有任何效果该怎么办)
6 |
7 | * [怎么在切换语种后应用到所有 Activity 上](#怎么在切换语种后应用到所有-activity-上)
8 |
9 | * [怎么在用户切换系统语种的时候刷新所有界面](#怎么在用户切换系统语种的时候刷新所有界面)
10 |
11 | * [在系统切换语种返回应用后没有生效怎么办](#在系统切换语种返回应用后没有生效怎么办)
12 |
13 | * [WebView 导致语种失效的解决方案](#webview-导致语种失效的解决方案)
14 |
15 | * [Android 13 WebView 语种失效怎么办](#android-13-webview-语种失效怎么办)
16 |
17 | * [有没有一种不用通过重启的方式来切换语种](#有没有一种不用通过重启的方式来切换语种)
18 |
19 | #### 如何设置 App 默认的语种
20 |
21 | * 在从来没有调用过 `MultiLanguages.setAppLanguage` 的情况下,又不想让应用跟随系统的语种,而是想指定某个语种该怎么做?具体写法示例如下:
22 |
23 | ```java
24 | public final class XxxApplication extends Application {
25 |
26 | static {
27 | // 设置默认的语种(越早设置越好)
28 | MultiLanguages.setDefaultLanguage(LocaleContract.getEnglishLocale());
29 | }
30 | }
31 | ```
32 |
33 | * 当然你如果想判断当前的系统语种类型,然后再设置默认的语种,可以这样写:
34 |
35 | ```java
36 | public final class XxxApplication extends Application {
37 |
38 | @Override
39 | protected void attachBaseContext(Context newBase) {
40 | if (newBase != null) {
41 | Locale systemLanguage = MultiLanguages.getSystemLanguage(newBase);
42 | // 如果当前语种既不是中文(包含简体和繁体)和英语(包含美式英式等),就默认设置成英文的,避免跟随系统语种
43 | if (!MultiLanguages.equalsLanguage(systemLanguage, LocaleContract.getChineseLocale()) &&
44 | !MultiLanguages.equalsLanguage(systemLanguage, LocaleContract.getEnglishLocale())) {
45 | MultiLanguages.setDefaultLanguage(LocaleContract.getEnglishLocale());
46 | }
47 | }
48 | // 绑定语种
49 | super.attachBaseContext(MultiLanguages.attach(newBase));
50 | }
51 | }
52 | ```
53 |
54 | #### 切换语种后没有任何效果该怎么办
55 |
56 | * 情况一:可以检查一下是否在 `build.gradle` 文件中配置了仅保留某个国家的语种资源,例如 `resConfigs 'zh'` 就代表只保留和中文相关的语种资源,而其他国家的语种资源就不会被打包进 apk 包中,这样就会导致在切换语种的时候始终都是中文的尴尬局面。
57 |
58 | * 情况二:如果是 Fragment 里面的语种切换之后没有变化,请检查 Fragment 在 Activity 重启之后是否被复用了,一般情况下是调用了 `fragment.setRetainInstance(true)` 导致的。
59 |
60 | * 情况三:如果是上架 GooglePlay 或者华为的 aab 包后,上架成功后再下载发现切换不了 App 语种,那是因为在分包的时候导致多语言资源缺失了,这时候需要对主模块的 `build.gradle` 文件进行配置,具体用法可以参考[官方文档](https://developer.android.google.cn/guide/app-bundle/configure-base?hl=zh-cn)
61 |
62 | ```groovy
63 | android {
64 |
65 | ......
66 |
67 | bundle {
68 |
69 | ......
70 |
71 | language {
72 | enableSplit = false
73 | }
74 |
75 | ......
76 | }
77 | }
78 | ```
79 |
80 | * 其他情况:如果不是以上原因造成的,请提一个 [issue](https://github.com/getActivity/MultiLanguages/issues) 给到我处理。
81 |
82 | #### 怎么在切换语种后应用到所有 Activity 上
83 |
84 | * 在 Application 的 onCreate 方法中加入以下代码
85 |
86 | ```java
87 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
88 |
89 | @Override
90 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
91 | Log.i("MultiLanguages", "监听到应用切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale);
92 | // 如需在系统切换语种后应用也要随之变化的,可以在这里获取所有的 Activity 并调用它的 recreate 方法
93 | // getAllActivity 只是演示代码,需要自行替换成项目已实现的方法,若项目中没有,请自行封装
94 | List activityList = getAllActivity();
95 | for (Activity activity : activityList) {
96 | activity.recreate();
97 | }
98 | }
99 |
100 | @Override
101 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
102 | Log.i("MultiLanguages", "监听到系统切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale +
103 | ",是否跟随系统:" + MultiLanguages.isSystemLanguage());
104 | }
105 | });
106 | ```
107 |
108 | #### 怎么在用户切换系统语种的时候刷新所有界面
109 |
110 | * 在 Application 的 onCreate 方法中加入以下代码
111 |
112 | ```java
113 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
114 |
115 | @Override
116 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
117 | Log.i("MultiLanguages", "监听到应用切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale);
118 | }
119 |
120 | @Override
121 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
122 | Log.i("MultiLanguages", "监听到系统切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale +
123 | ",是否跟随系统:" + MultiLanguages.isSystemLanguage());
124 |
125 | // 当前语种是否是跟随系统,如果不是则不往下执行
126 | if (!MultiLanguages.isSystemLanguage(AppApplication.this)) {
127 | return;
128 | }
129 |
130 | // 如需在系统切换语种后应用也要随之变化的,可以在这里获取所有的 Activity 并调用它的 recreate 方法
131 | // getAllActivity 只是演示代码,需要自行替换成项目已实现的方法,若项目中没有,请自行封装
132 | List activityList = getAllActivity();
133 | for (Activity activity : activityList) {
134 | activity.recreate();
135 | }
136 | }
137 | });
138 | ```
139 |
140 | #### 在系统切换语种返回应用后没有生效怎么办
141 |
142 | * 请检查在 Activity 配置了 `android:configChanges` 属性,有的话请去掉再进行尝试,这个属性的文档请查看 [``](https://developer.android.google.cn/guide/topics/manifest/activity-element?hl=zh)
143 |
144 | ```xml
145 |
148 | ```
149 |
150 | * 如果你不想去除 `android:configChanges` 属性,又想切换系统语种后生效,在 Application 的 onCreate 方法中加入以下代码
151 |
152 | ```java
153 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
154 |
155 | @Override
156 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
157 | Log.i("MultiLanguages", "监听到应用切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale);
158 | }
159 |
160 | @Override
161 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
162 | Log.i("MultiLanguages", "监听到系统切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale +
163 | ",是否跟随系统:" + MultiLanguages.isSystemLanguage());
164 |
165 | // 当前语种是否是跟随系统,如果不是则不往下执行
166 | if (!MultiLanguages.isSystemLanguage(AppApplication.this)) {
167 | return;
168 | }
169 |
170 | // 如需在系统切换语种后应用也要随之变化的,可以在这里获取所有的 Activity 并调用它的 recreate 方法
171 | // getAllActivity 只是演示代码,需要自行替换成项目已实现的方法,若项目中没有,请自行封装
172 | List activityList = getAllActivity();
173 | for (Activity activity : activityList) {
174 | activity.recreate();
175 | }
176 | }
177 | });
178 | ```
179 |
180 | #### WebView 导致语种失效的解决方案
181 |
182 | * 由于 WebView 初始化会修改 Activity 语种配置,间接导致 Activity 语种会被还原回去,所以需要你手动重写 WebView 对这个问题进行修复
183 |
184 | ```java
185 | public final class LanguagesWebView extends WebView {
186 |
187 | public LanguagesWebView(@NonNull Context context) {
188 | this(context, null);
189 | }
190 |
191 | public LanguagesWebView(@NonNull Context context, @Nullable AttributeSet attrs) {
192 | this(context, attrs, android.R.attr.webViewStyle);
193 | }
194 |
195 | public LanguagesWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
196 | super(context, attrs, defStyleAttr);
197 |
198 | // 修复 WebView 初始化时会修改 Activity 语种配置的问题
199 | MultiLanguages.updateAppLanguage(context);
200 | }
201 | }
202 | ```
203 |
204 | #### Android 13 WebView 语种失效怎么办
205 |
206 | * 将 `webView.loadUrl(@NonNull String url)` 替换成 `webView.loadUrl(@NonNull String url, @NonNull Map additionalHttpHeaders)`,并添加 `Accept-Language` 请求头即可,具体的示例代码如下:
207 |
208 | ```java
209 | public final class MainActivity extends Activity {
210 |
211 | private WebView mWebView;
212 |
213 | @Override
214 | protected void onCreate(Bundle savedInstanceState) {
215 | super.onCreate(savedInstanceState);
216 | setContentView(R.layout.activity_main);
217 |
218 | mWebView = findViewById(R.id.wv_main_web);
219 |
220 | mWebView.setWebViewClient(new LanguagesViewClient());
221 | mWebView.setWebChromeClient(new WebChromeClient());
222 | mWebView.loadUrl("https://developer.android.google.cn/kotlin", generateLanguageRequestHeader(this));
223 | }
224 |
225 | public static class LanguagesViewClient extends WebViewClient {
226 |
227 | @TargetApi(Build.VERSION_CODES.N)
228 | @Override
229 | public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
230 | return shouldOverrideUrlLoading(view, request.getUrl().toString());
231 | }
232 |
233 | @Override
234 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
235 | String scheme = Uri.parse(url).getScheme();
236 | if (scheme == null) {
237 | return false;
238 | }
239 | switch (scheme) {
240 | // 如果这是跳链接操作
241 | case "http":
242 | case "https":
243 | view.loadUrl(url, generateLanguageRequestHeader(view.getContext()));
244 | break;
245 | default:
246 | break;
247 | }
248 | return true;
249 | }
250 | }
251 |
252 | /**
253 | * 给 WebView 请求头添加语种环境
254 | */
255 | @NonNull
256 | public static Map generateLanguageRequestHeader(Context context) {
257 | Map map = new HashMap<>(1);
258 | // Android 13 上面语种失效的问题解决方案
259 | // https://developer.android.google.cn/about/versions/13/features/app-languages?hl=zh-cn#consider-header
260 | map.put("Accept-Language", String.valueOf(MultiLanguages.getAppLanguage(context)));
261 | return map;
262 | }
263 | }
264 | ```
265 |
266 | #### 有没有一种不用通过重启的方式来切换语种
267 |
268 | * 我先问大家一个问题,生米煮成熟饭了,怎么从熟饭变成生米?这显然是不现实的,退一万步讲,假设框架能做到,文字和图片都能自动跟随语种的变化而变化,那么通过网络请求的数据又怎么切换语种?是不是得重新请求?如果是列表数据是不是得从第 1 页开始请求?再问大家一个问题,还有语种切换是一个常用动作吗?我相信大家此时心里已经有了答案。
269 |
270 | * 所以并不是做不到不用重启的效果,而是没有那个必要(切语种不是常用动作),并且存在一定的硬伤(虽然 UI 层不用动,但是数据层还是要重新请求)。
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/language/MultiLanguages.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.content.res.Configuration;
6 | import android.content.res.Resources;
7 | import android.os.Build;
8 | import android.os.Looper;
9 | import android.os.MessageQueue;
10 | import android.text.TextUtils;
11 | import java.util.Locale;
12 |
13 | /**
14 | * author : Android 轮子哥
15 | * github : https://github.com/getActivity/MultiLanguages
16 | * time : 2019/05/03
17 | * desc : 语种切换框架
18 | */
19 | @SuppressWarnings("unused")
20 | public final class MultiLanguages {
21 |
22 | /** 应用上下文对象 */
23 | private static Application sApplication;
24 |
25 | /** 语种变化监听对象 */
26 | private static OnLanguageListener sLanguageListener;
27 |
28 | /**
29 | * 初始化多语种框架
30 | */
31 | public static void init(Application application) {
32 | init(application, true);
33 | }
34 |
35 | public static void init(final Application application, boolean inject) {
36 | if (sApplication != null) {
37 | // 如果框架已经初始化过了,则不往下执行
38 | return;
39 | }
40 | sApplication = application;
41 | LanguagesUtils.setDefaultLocale(application);
42 | if (inject) {
43 | ActivityLanguages.inject(application);
44 | }
45 | /*
46 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
47 | LocaleManager localeManager = application.getSystemService(LocaleManager.class);
48 | if (localeManager != null) {
49 | if (isSystemLanguage(application)) {
50 | // 在没有设置过 setApplicationLocales 方法,里面的默认值会为 LocaleList.getEmptyLocaleList()
51 | // 当设置过一次的 setApplicationLocales 为其他值,在 Android 13 的手机设置中修改语种(系统或者应用)的时候
52 | // 就会导致 context.registerComponentCallbacks 中的 onConfigurationChanged 监听方法没有触发到
53 | // 如果当前应用指定了某种语种,监听方法没有回调是正常的,但是如果当前语种是跟随系统的模式,那么不回调就是有问题的了
54 | // 这是因为系统对 setApplicationLocales 的结果进行了持久化操作,所以这里要重新设置一下,并传入 LocaleList.getEmptyLocaleList()
55 | // Github issue:https://github.com/getActivity/MultiLanguages/issues/37
56 | localeManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
57 | } else {
58 | localeManager.setApplicationLocales(new LocaleList(getAppLanguage(application)));
59 | }
60 | }
61 | }
62 | */
63 | // 等所有的任务都执行完了,再设置对系统语种的监听,用户不可能在这点间隙的时间完成切换语言的
64 | // 经过实践证明 IdleHandler 会在第一个 Activity attachBaseContext 之后调用的,所以没有什么问题
65 | Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
66 | @Override
67 | public boolean queueIdle() {
68 | ConfigurationObserver.register(application);
69 | LocaleChangeReceiver.register(application);
70 | return false;
71 | }
72 | });
73 | }
74 |
75 | /**
76 | * 在上下文的子类中重写 attachBaseContext 方法(用于更新 Context 的语种)
77 | */
78 | public static Context attach(Context context) {
79 | Locale locale = getAppLanguage(context);
80 | if (equalsLanguageAndCountry(locale, LanguagesUtils.getLocale(context))) {
81 | return context;
82 | }
83 | return LanguagesUtils.attachLanguages(context, locale);
84 | }
85 |
86 | /**
87 | * 更新 Context 的语种
88 | */
89 | public static void updateAppLanguage(Context context) {
90 | updateAppLanguage(context, context.getResources());
91 | }
92 |
93 | /**
94 | * 更新 Resources 的语种
95 | */
96 | public static void updateAppLanguage(Context context, Resources resources) {
97 | if (resources == null) {
98 | return;
99 | }
100 | Locale locale = getAppLanguage(context);
101 | Configuration config = resources.getConfiguration();
102 | if (!LanguagesUtils.equalsLanguageAndCountry(locale, LanguagesUtils.getLocale(config))
103 | || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
104 | && TextUtils.getLayoutDirectionFromLocale(locale) != config.getLayoutDirection())) {
105 | LanguagesUtils.updateLanguages(resources, locale);
106 | }
107 | if (!LanguagesUtils.equalsLanguageAndCountry(locale, LanguagesUtils.getDefaultLocale())) {
108 | // Github issue 地址:https://github.com/getActivity/MultiLanguages/issues/59
109 | LanguagesUtils.setDefaultLocale(context);
110 | }
111 | }
112 |
113 | /**
114 | * 获取 App 的语种
115 | */
116 | public static Locale getAppLanguage(Context context) {
117 | if (isSystemLanguage(context)) {
118 | return getSystemLanguage(context);
119 | } else {
120 | return LanguagesConfig.readAppLanguageSetting(context);
121 | }
122 | }
123 |
124 | /**
125 | * 设置 App 的语种
126 | *
127 | * @return 语种是否发生改变了
128 | */
129 | public static boolean setAppLanguage(Context context, Locale newLocale) {
130 | /*
131 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
132 | context.getSystemService(LocaleManager.class).setApplicationLocales(new LocaleList(newLocale));
133 | }
134 | */
135 | // 这里解释一下,在 Android 13 上为什么不用 LocaleManager.setApplicationLocales 来设置语种,原因如下:
136 | // 1. 调用此 API 会自动重启 Activity,而框架是将重启操作放到了外层给开发者去重启
137 | // 2. 上面说了,调用此 API 会重启 Activity,重启也就算了,还顺带闪了一下,这个不能忍
138 | LanguagesConfig.saveAppLanguageSetting(context, newLocale);
139 | if (LanguagesUtils.equalsLanguageAndCountry(newLocale, LanguagesUtils.getLocale(context))) {
140 | return false;
141 | }
142 |
143 | Locale oldLocale = LanguagesUtils.getLocale(context);
144 | // 更新 Context 的语种
145 | LanguagesUtils.updateLanguages(context.getResources(), newLocale);
146 | if (context != sApplication) {
147 | // 更新 Application 的语种
148 | LanguagesUtils.updateLanguages(sApplication.getResources(), newLocale);
149 | }
150 | // 重新设置默认的语种环境
151 | LanguagesUtils.setDefaultLocale(context);
152 |
153 | if (sLanguageListener != null) {
154 | sLanguageListener.onAppLocaleChange(oldLocale, newLocale);
155 | }
156 | return true;
157 | }
158 |
159 | /**
160 | * 获取系统的语种
161 | */
162 | public static Locale getSystemLanguage(Context context) {
163 | return LanguagesUtils.getSystemLocale(context);
164 | }
165 |
166 | /**
167 | * 是否跟随系统的语种
168 | */
169 | public static boolean isSystemLanguage(Context context) {
170 | return LanguagesConfig.isSystemLanguage(context);
171 | }
172 |
173 | /**
174 | * 跟随系统语种
175 | *
176 | * @return 语种是否发生改变了
177 | */
178 | public static boolean clearAppLanguage(Context context) {
179 | LanguagesConfig.clearLanguageSetting(context);
180 | if (LanguagesUtils.equalsLanguageAndCountry(getSystemLanguage(sApplication), LanguagesUtils.getLocale(context))) {
181 | return false;
182 | }
183 |
184 | LanguagesUtils.updateLanguages(context.getResources(), getSystemLanguage(sApplication));
185 | LanguagesUtils.setDefaultLocale(context);
186 | if (context != sApplication) {
187 | // 更新 Application 的语种
188 | LanguagesUtils.updateLanguages(sApplication.getResources(), getSystemLanguage(sApplication));
189 | }
190 | return true;
191 | }
192 |
193 | /**
194 | * 设置默认的语种(越早设置越好)
195 | */
196 | public static void setDefaultLanguage(Locale locale) {
197 | LanguagesConfig.setDefaultLanguage(locale);
198 | }
199 |
200 | /**
201 | * 对比两个语言是否同一个语种(比如:中文有简体和繁体,但是它们都属于同一个语种)
202 | */
203 | public static boolean equalsLanguage(Locale locale1, Locale locale2) {
204 | return LanguagesUtils.equalsLanguage(locale1, locale2);
205 | }
206 |
207 | /**
208 | * @deprecated 该 API 已经过时,随时会被删除,请尽早迁移到 {@link #equalsLanguageAndCountry(Locale, Locale)}
209 | */
210 | public static boolean equalsCountry(Locale locale1, Locale locale2) {
211 | return equalsLanguageAndCountry(locale1, locale2);
212 | }
213 |
214 | /**
215 | * 对比两个语言是否同一个语种并且同一个地方的(比如:中国大陆用的中文简体,中国台湾用的中文繁体)
216 | */
217 | public static boolean equalsLanguageAndCountry(Locale locale1, Locale locale2) {
218 | return LanguagesUtils.equalsLanguageAndCountry(locale1, locale2);
219 | }
220 |
221 | /**
222 | * 获取某个语种下的 String
223 | */
224 | public static String getLanguageString(Context context, Locale locale, int id) {
225 | return generateLanguageResources(context, locale).getString(id);
226 | }
227 |
228 | /**
229 | * 生成某个语种下的 Resources 对象
230 | */
231 | public static Resources generateLanguageResources(Context context, Locale locale) {
232 | return LanguagesUtils.generateLanguageResources(context, locale);
233 | }
234 |
235 | /**
236 | * 设置语种变化监听器
237 | */
238 | public static void setOnLanguageListener(OnLanguageListener listener) {
239 | sLanguageListener = listener;
240 | }
241 |
242 | /**
243 | * 设置保存的 SharedPreferences 文件名(请在 Application 初始化之前设置,可以放在 Application 中的代码块或者静态代码块)
244 | */
245 | public static void setSharedPreferencesName(String name) {
246 | LanguagesConfig.setSharedPreferencesName(name);
247 | }
248 |
249 | /**
250 | * 获取语种变化监听对象
251 | */
252 | static OnLanguageListener getOnLanguagesListener() {
253 | return sLanguageListener;
254 | }
255 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hjq/language/demo/ActivityManager.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language.demo;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.Application;
6 | import android.content.Context;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.text.TextUtils;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.Nullable;
13 |
14 | import java.io.FileInputStream;
15 | import java.io.IOException;
16 | import java.lang.reflect.InvocationTargetException;
17 | import java.lang.reflect.Method;
18 | import java.nio.charset.StandardCharsets;
19 | import java.util.ArrayList;
20 | import java.util.Iterator;
21 | import java.util.List;
22 |
23 | /**
24 | * author : Android 轮子哥
25 | * github : https://github.com/getActivity/AndroidProject
26 | * time : 2018/11/18
27 | * desc : Activity 管理器
28 | */
29 | public final class ActivityManager implements Application.ActivityLifecycleCallbacks {
30 |
31 | private static volatile ActivityManager sInstance;
32 |
33 | /** Activity 存放集合 */
34 | private final List mActivityList = new ArrayList<>();
35 |
36 | /** 应用生命周期回调 */
37 | private final ArrayList mLifecycleCallbacks = new ArrayList<>();
38 |
39 | /** 当前应用上下文对象 */
40 | private Application mApplication;
41 | /** 栈顶的 Activity 对象 */
42 | private Activity mTopActivity;
43 | /** 前台并且可见的 Activity 对象 */
44 | private Activity mResumedActivity;
45 |
46 | private ActivityManager() {}
47 |
48 | public static ActivityManager getInstance() {
49 | if(sInstance == null) {
50 | synchronized (ActivityManager.class) {
51 | if(sInstance == null) {
52 | sInstance = new ActivityManager();
53 | }
54 | }
55 | }
56 | return sInstance;
57 | }
58 |
59 | public void init(Application application) {
60 | mApplication = application;
61 | mApplication.registerActivityLifecycleCallbacks(this);
62 | }
63 |
64 | /**
65 | * 获取 Application 对象
66 | */
67 | @NonNull
68 | public Application getApplication() {
69 | return mApplication;
70 | }
71 |
72 | /**
73 | * 获取栈顶的 Activity
74 | */
75 | @Nullable
76 | public Activity getTopActivity() {
77 | return mTopActivity;
78 | }
79 |
80 | /**
81 | * 获取前台并且可见的 Activity
82 | */
83 | @Nullable
84 | public Activity getResumedActivity() {
85 | return mResumedActivity;
86 | }
87 |
88 | /**
89 | * 获取 Activity 集合
90 | */
91 | @NonNull
92 | public List getActivityList() {
93 | return mActivityList;
94 | }
95 |
96 | /**
97 | * 判断当前应用是否处于前台状态
98 | */
99 | public boolean isForeground() {
100 | return getResumedActivity() != null;
101 | }
102 |
103 | /**
104 | * 注册应用生命周期回调
105 | */
106 | public void registerApplicationLifecycleCallback(ApplicationLifecycleCallback callback) {
107 | mLifecycleCallbacks.add(callback);
108 | }
109 |
110 | /**
111 | * 取消注册应用生命周期回调
112 | */
113 | public void unregisterApplicationLifecycleCallback(ApplicationLifecycleCallback callback) {
114 | mLifecycleCallbacks.remove(callback);
115 | }
116 |
117 | /**
118 | * 销毁指定的 Activity
119 | */
120 | public void finishActivity(Class extends Activity> clazz) {
121 | if (clazz == null) {
122 | return;
123 | }
124 |
125 | Iterator iterator = mActivityList.iterator();
126 | while (iterator.hasNext()) {
127 | Activity activity = iterator.next();
128 | if (!activity.getClass().equals(clazz)) {
129 | continue;
130 | }
131 | if (!activity.isFinishing()) {
132 | activity.finish();
133 | }
134 | iterator.remove();
135 | }
136 | }
137 |
138 | /**
139 | * 销毁所有的 Activity
140 | */
141 | public void finishAllActivities() {
142 | finishAllActivities((Class extends Activity>) null);
143 | }
144 |
145 | /**
146 | * 销毁所有的 Activity
147 | *
148 | * @param classArray 白名单 Activity
149 | */
150 | @SafeVarargs
151 | public final void finishAllActivities(Class extends Activity>... classArray) {
152 | Iterator iterator = mActivityList.iterator();
153 | while (iterator.hasNext()) {
154 | Activity activity = iterator.next();
155 | boolean whiteClazz = false;
156 | if (classArray != null) {
157 | for (Class extends Activity> clazz : classArray) {
158 | if (activity.getClass().equals(clazz)) {
159 | whiteClazz = true;
160 | }
161 | }
162 | }
163 | if (whiteClazz) {
164 | continue;
165 | }
166 | // 如果不是白名单上面的 Activity 就销毁掉
167 | if (!activity.isFinishing()) {
168 | activity.finish();
169 | }
170 | iterator.remove();
171 | }
172 | }
173 |
174 | @Override
175 | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
176 | if (mActivityList.isEmpty()) {
177 | for (ApplicationLifecycleCallback callback : mLifecycleCallbacks) {
178 | callback.onApplicationCreate(activity);
179 | }
180 | }
181 | mActivityList.add(activity);
182 | mTopActivity = activity;
183 | }
184 |
185 | @Override
186 | public void onActivityStarted(@NonNull Activity activity) {
187 | }
188 |
189 | @Override
190 | public void onActivityResumed(@NonNull Activity activity) {
191 | if (mTopActivity == activity && mResumedActivity == null) {
192 | for (ApplicationLifecycleCallback callback : mLifecycleCallbacks) {
193 | callback.onApplicationForeground(activity);
194 | }
195 | }
196 | mTopActivity = activity;
197 | mResumedActivity = activity;
198 | }
199 |
200 | @Override
201 | public void onActivityPaused(@NonNull Activity activity) {
202 | }
203 |
204 | @Override
205 | public void onActivityStopped(@NonNull Activity activity) {
206 | if (mResumedActivity == activity) {
207 | mResumedActivity = null;
208 | }
209 | if (mResumedActivity == null) {
210 | for (ApplicationLifecycleCallback callback : mLifecycleCallbacks) {
211 | callback.onApplicationBackground(activity);
212 | }
213 | }
214 | }
215 |
216 | @Override
217 | public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
218 | }
219 |
220 | @Override
221 | public void onActivityDestroyed(@NonNull Activity activity) {
222 | mActivityList.remove(activity);
223 | if (mTopActivity == activity) {
224 | mTopActivity = null;
225 | }
226 | if (mActivityList.isEmpty()) {
227 | for (ApplicationLifecycleCallback callback : mLifecycleCallbacks) {
228 | callback.onApplicationDestroy(activity);
229 | }
230 | }
231 | }
232 |
233 | /**
234 | * 判断是否在主进程中
235 | */
236 | public static boolean isMainProcess(Context context) {
237 | String processName = getProcessName();
238 | if (TextUtils.isEmpty(processName)) {
239 | // 如果获取不到进程名称,那么则将它当做主进程
240 | return true;
241 | }
242 | return TextUtils.equals(processName, context.getPackageName());
243 | }
244 |
245 | /**
246 | * 获取当前进程名称
247 | */
248 | @SuppressLint("PrivateApi, DiscouragedPrivateApi")
249 | @Nullable
250 | public static String getProcessName() {
251 | String processName = null;
252 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
253 | processName = Application.getProcessName();
254 | } else {
255 | try {
256 | Class> activityThread = Class.forName("android.app.ActivityThread");
257 | Method currentProcessNameMethod = activityThread.getDeclaredMethod("currentProcessName");
258 | processName = (String) currentProcessNameMethod.invoke(null);
259 | } catch (ClassNotFoundException | ClassCastException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
260 | e.printStackTrace();
261 | }
262 | }
263 |
264 | if (!TextUtils.isEmpty(processName)) {
265 | return processName;
266 | }
267 |
268 | // 利用 Linux 系统获取进程名
269 | FileInputStream inputStream = null;
270 | try {
271 | inputStream = new FileInputStream("/proc/self/cmdline");
272 | byte[] buffer = new byte[256];
273 | int len = 0;
274 | int b;
275 | while ((b = inputStream.read()) > 0 && len < buffer.length) {
276 | buffer[len++] = (byte) b;
277 | }
278 | if (len > 0) {
279 | return new String(buffer, 0, len, StandardCharsets.UTF_8);
280 | }
281 | } catch (IOException e) {
282 | e.printStackTrace();
283 | } finally {
284 | if (inputStream != null) {
285 | try {
286 | inputStream.close();
287 | } catch (IOException e) {
288 | e.printStackTrace();
289 | }
290 | }
291 | }
292 | return null;
293 | }
294 |
295 | /**
296 | * 应用生命周期回调
297 | */
298 | public interface ApplicationLifecycleCallback {
299 |
300 | /**
301 | * 第一个 Activity 创建了
302 | */
303 | void onApplicationCreate(Activity activity);
304 |
305 | /**
306 | * 最后一个 Activity 销毁了
307 | */
308 | void onApplicationDestroy(Activity activity);
309 |
310 | /**
311 | * 应用从前台进入到后台
312 | */
313 | void onApplicationBackground(Activity activity);
314 |
315 | /**
316 | * 应用从后台进入到前台
317 | */
318 | void onApplicationForeground(Activity activity);
319 | }
320 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [English Doc](README-en.md)
2 |
3 | # 语种切换框架
4 |
5 | * 项目地址:[Github](https://github.com/getActivity/MultiLanguages)
6 |
7 | * 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/MultiLanguages/releases/download/10.2/MultiLanguages.apk)
8 |
9 | 
10 |
11 | #### 集成步骤
12 |
13 | * 如果你的项目 Gradle 配置是在 `7.0 以下`,需要在 `build.gradle` 文件中加入
14 |
15 | ```groovy
16 | allprojects {
17 | repositories {
18 | // JitPack 远程仓库:https://jitpack.io
19 | maven { url 'https://jitpack.io' }
20 | }
21 | }
22 | ```
23 |
24 | * 如果你的 Gradle 配置是 `7.0 及以上`,则需要在 `settings.gradle` 文件中加入
25 |
26 | ```groovy
27 | dependencyResolutionManagement {
28 | repositories {
29 | // JitPack 远程仓库:https://jitpack.io
30 | maven { url 'https://jitpack.io' }
31 | }
32 | }
33 | ```
34 |
35 | * 配置完远程仓库后,在项目 app 模块下的 `build.gradle` 文件中加入远程依赖
36 |
37 | ```groovy
38 | dependencies {
39 | // 语种切换框架:https://github.com/getActivity/MultiLanguages
40 | implementation 'com.github.getActivity:MultiLanguages:10.2'
41 | }
42 | ```
43 |
44 | #### 初始化框架
45 |
46 | * 在 Application 中初始化框架
47 |
48 | ```java
49 | public final class XxxApplication extends Application {
50 |
51 | @Override
52 | public void onCreate() {
53 | super.onCreate();
54 |
55 | // 初始化语种切换框架
56 | MultiLanguages.init(this);
57 | }
58 | }
59 | ```
60 |
61 | * 重写 Application 的 attachBaseContext 方法
62 |
63 | ```java
64 | @Override
65 | protected void attachBaseContext(Context base) {
66 | // 绑定语种
67 | super.attachBaseContext(MultiLanguages.attach(base));
68 | }
69 | ```
70 |
71 | * 重写**基类** BaseActivity 的 attachBaseContext 方法
72 |
73 | ```java
74 | @Override
75 | protected void attachBaseContext(Context newBase) {
76 | // 绑定语种
77 | super.attachBaseContext(MultiLanguages.attach(newBase));
78 | }
79 | ```
80 |
81 | * 只要是 Context 的子类都需要重写,Service 也雷同,这里不再赘述
82 |
83 | * 温馨提示:Fragment 不需要重写此方法,因为它不是 Context 的子类
84 |
85 | #### 语种设置
86 |
87 | ```java
88 | // 设置当前的语种(返回 true 表示需要重启 App)
89 | MultiLanguages.setAppLanguage(Context context, Locale locale);
90 |
91 | // 获取当前的语种
92 | MultiLanguages.getAppLanguage(Context context);
93 |
94 | // 跟随系统语种(返回 true 表示需要重启 App)
95 | MultiLanguages.clearAppLanguage(Context context);
96 | ```
97 |
98 | #### 其他 API
99 |
100 | ```java
101 | // 获取系统的语种
102 | MultiLanguages.getSystemLanguage(Context context);
103 | // 是否跟随系统的语种
104 | MultiLanguages.isSystemLanguage(Context context);
105 |
106 | // 对比两个语言是否同一个语种(比如:中文有简体和繁体,英语有美式和英式)
107 | MultiLanguages.equalsLanguage(Locale locale1, Locale locale2);
108 | // 对比两个语言是否同一个语种并且同一个地方的(比如:中国大陆用的中文简体,中国台湾用的中文繁体)
109 | MultiLanguages.equalsLanguageAndCountry(Locale locale1, Locale locale2);
110 | // 注意事项:如果是为了判断两个 Locale 对象是否相同,请使用 equalsLanguageAndCountry 方法来判断,而不是 equalsLanguage 方法,
111 | // 另外请不要用 Locale 类中自带的 equals 方法来判断,因为它会比较更多的信息,会导致判断错误。
112 | // Github 相关问题 issue:https://github.com/getActivity/MultiLanguages/issues/63
113 |
114 | // 获取某个语种下的 String
115 | MultiLanguages.getLanguageString(Context context, Locale locale, int stringId);
116 | // 生成某个语种下的 Resources 对象
117 | MultiLanguages.generateLanguageResources(Context context, Locale locale);
118 |
119 | // 更新 Context 的语种
120 | MultiLanguages.updateAppLanguage(Context context);
121 | // 更新 Resources 的语种
122 | MultiLanguages.updateAppLanguage(Resources resources);
123 |
124 | // 设置默认的语种(越早设置越好)
125 | MultiLanguages.setDefaultLanguage(Locale locale);
126 | ```
127 |
128 | #### 语种变化监听器
129 |
130 | ```java
131 | // 设置语种变化监听器
132 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
133 |
134 | @Override
135 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
136 | Log.d("MultiLanguages", "监听到应用切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale);
137 | }
138 |
139 | @Override
140 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
141 | Log.d("MultiLanguages", "监听到系统切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale + ",是否跟随系统:" + MultiLanguages.isSystemLanguage());
142 | }
143 | });
144 | ```
145 |
146 | #### 使用案例
147 |
148 | ```java
149 | @Override
150 | public void onClick(View v) {
151 | // 是否需要重启
152 | boolean restart;
153 | switch (v.getId()) {
154 | // 跟随系统
155 | case R.id.btn_language_auto:
156 | restart = MultiLanguages.clearAppLanguage(this);
157 | break;
158 | // 简体中文
159 | case R.id.btn_language_cn:
160 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getSimplifiedChineseLocale());
161 | break;
162 | // 繁体中文
163 | case R.id.btn_language_tw:
164 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getTraditionalChineseLocale());
165 | break;
166 | // 英语
167 | case R.id.btn_language_en:
168 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getEnglishLocale());
169 | break;
170 | default:
171 | restart = false;
172 | break;
173 | }
174 |
175 | if (restart) {
176 | // 我们可以充分运用 Activity 跳转动画,在跳转的时候设置一个渐变的效果
177 | Intent intent = new Intent(this, MainActivity.class);
178 | // Github 地址:https://github.com/getActivity/MultiLanguages/issues/55
179 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
180 | startActivity(intent);
181 | overridePendingTransition(R.anim.activity_alpha_in, R.anim.activity_alpha_out);
182 | finish();
183 | }
184 | }
185 | ```
186 |
187 | ## [常见疑问请点击此处查看](HelpDoc.md)
188 |
189 | #### 其他资源:[语言代码列表大全](https://github.com/championswimmer/android-locales)
190 |
191 | #### 作者的其他开源项目
192 |
193 | * 安卓技术中台:[AndroidProject](https://github.com/getActivity/AndroidProject)  
194 |
195 | * 安卓技术中台 Kt 版:[AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)  
196 |
197 | * 权限框架:[XXPermissions](https://github.com/getActivity/XXPermissions)  
198 |
199 | * 吐司框架:[Toaster](https://github.com/getActivity/Toaster)  
200 |
201 | * 网络框架:[EasyHttp](https://github.com/getActivity/EasyHttp)  
202 |
203 | * 标题栏框架:[TitleBar](https://github.com/getActivity/TitleBar)  
204 |
205 | * 悬浮窗框架:[EasyWindow](https://github.com/getActivity/EasyWindow)  
206 |
207 | * 设备兼容框架:[DeviceCompat](https://github.com/getActivity/DeviceCompat)  
208 |
209 | * Shape 框架:[ShapeView](https://github.com/getActivity/ShapeView)  
210 |
211 | * Gson 解析容错:[GsonFactory](https://github.com/getActivity/GsonFactory)  
212 |
213 | * 日志查看框架:[Logcat](https://github.com/getActivity/Logcat)  
214 |
215 | * Android 版本适配:[AndroidVersionAdapter](https://github.com/getActivity/AndroidVersionAdapter)  
216 |
217 | * Android 代码规范:[AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard)  
218 |
219 | * Android 资源大汇总:[AndroidIndex](https://github.com/getActivity/AndroidIndex)  
220 |
221 | * Android 开源排行榜:[AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss)  
222 |
223 | * Studio 精品插件:[StudioPlugins](https://github.com/getActivity/StudioPlugins)  
224 |
225 | * 表情包大集合:[EmojiPackage](https://github.com/getActivity/EmojiPackage)  
226 |
227 | * AI 资源大汇总:[AiIndex](https://github.com/getActivity/AiIndex)  
228 |
229 | * 省市区 Json 数据:[ProvinceJson](https://github.com/getActivity/ProvinceJson)  
230 |
231 | * Markdown 语法文档:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc)  
232 |
233 | #### 微信公众号:Android轮子哥
234 |
235 | 
236 |
237 | #### Android 技术 Q 群:10047167
238 |
239 | #### 如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:([点击查看捐赠列表](https://github.com/getActivity/Donate))
240 |
241 |  
242 |
243 | ## License
244 |
245 | ```text
246 | Copyright 2019 Huang JinQun
247 |
248 | Licensed under the Apache License, Version 2.0 (the "License");
249 | you may not use this file except in compliance with the License.
250 | You may obtain a copy of the License at
251 |
252 | http://www.apache.org/licenses/LICENSE-2.0
253 |
254 | Unless required by applicable law or agreed to in writing, software
255 | distributed under the License is distributed on an "AS IS" BASIS,
256 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
257 | See the License for the specific language governing permissions and
258 | limitations under the License.
259 | ```
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, July 2019
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2019 Huang JinQun
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README-en.md:
--------------------------------------------------------------------------------
1 | # [中文文档](README.md)
2 |
3 | # Language Switching Framework
4 |
5 | * Project address: [Github](https://github.com/getActivity/MultiLanguages)
6 |
7 | * [Click here to download demo apk directly](https://github.com/getActivity/MultiLanguages/releases/download/10.2/MultiLanguages.apk)
8 |
9 | 
10 |
11 | #### Integration Steps
12 |
13 | * If your project's Gradle configuration is `below 7.0`, add the following to your `build.gradle` file:
14 |
15 | ```groovy
16 | allprojects {
17 | repositories {
18 | // JitPack remote repository: https://jitpack.io
19 | maven { url 'https://jitpack.io' }
20 | }
21 | }
22 | ```
23 |
24 | * If your Gradle configuration is `7.0 or above`, add the following to your `settings.gradle` file:
25 |
26 | ```groovy
27 | dependencyResolutionManagement {
28 | repositories {
29 | // JitPack remote repository: https://jitpack.io
30 | maven { url 'https://jitpack.io' }
31 | }
32 | }
33 | ```
34 |
35 | * After configuring the remote repository, add the remote dependency in the `build.gradle` file under your app module:
36 |
37 | ```groovy
38 | dependencies {
39 | // Language switching framework: https://github.com/getActivity/MultiLanguages
40 | implementation 'com.github.getActivity:MultiLanguages:10.2'
41 | }
42 | ```
43 |
44 | #### Initialize the Framework
45 |
46 | * Initialize the framework in your Application class:
47 |
48 | ```java
49 | public final class XxxApplication extends Application {
50 |
51 | @Override
52 | public void onCreate() {
53 | super.onCreate();
54 |
55 | // Initialize the language switching framework
56 | MultiLanguages.init(this);
57 | }
58 | }
59 | ```
60 |
61 | * Override the attachBaseContext method in your Application:
62 |
63 | ```java
64 | @Override
65 | protected void attachBaseContext(Context base) {
66 | // Bind language
67 | super.attachBaseContext(MultiLanguages.attach(base));
68 | }
69 | ```
70 |
71 | * Override the attachBaseContext method in your **base class** BaseActivity:
72 |
73 | ```java
74 | @Override
75 | protected void attachBaseContext(Context newBase) {
76 | // Bind language
77 | super.attachBaseContext(MultiLanguages.attach(newBase));
78 | }
79 | ```
80 |
81 | * Any subclass of Context needs to override this method, including Service. This will not be repeated here.
82 |
83 | * Note: Fragment does not need to override this method because it is not a subclass of Context.
84 |
85 | #### Language Settings
86 |
87 | ```java
88 | // Set the current language (returns true if the app needs to be restarted)
89 | MultiLanguages.setAppLanguage(Context context, Locale locale);
90 |
91 | // Get the current language
92 | MultiLanguages.getAppLanguage(Context context);
93 |
94 | // Follow system language (returns true if the app needs to be restarted)
95 | MultiLanguages.clearAppLanguage(Context context);
96 | ```
97 |
98 | #### Other APIs
99 |
100 | ```java
101 | // Get the system language
102 | MultiLanguages.getSystemLanguage(Context context);
103 | // Whether to follow the system language
104 | MultiLanguages.isSystemLanguage(Context context);
105 |
106 | // Compare if two languages are the same (e.g., Simplified and Traditional Chinese, American and British English)
107 | MultiLanguages.equalsLanguage(Locale locale1, Locale locale2);
108 | // Compare whether the two languages are of the same language and from the same place (for example: simplified Chinese used in Chinese mainland and traditional Chinese used in Taiwan, China).
109 | MultiLanguages.equalsLanguageAndCountry(Locale locale1, Locale locale2);
110 | // Notes: If it is to determine whether two Locale objects are the same, please use the equalsLanguageAndCountry method for judgment instead of the equalsLanguage method
111 | // Also, please do not use the equals method that comes with the Locale class to make a judgment, as it will compare more information and may lead to a judgment error.
112 | // Github issue : https://github.com/getActivity/MultiLanguages/issues/63
113 |
114 | // Get a String in a specific language
115 | MultiLanguages.getLanguageString(Context context, Locale locale, int stringId);
116 | // Generate a Resources object for a specific language
117 | MultiLanguages.generateLanguageResources(Context context, Locale locale);
118 |
119 | // Update the language of Context
120 | MultiLanguages.updateAppLanguage(Context context);
121 | // Update the language of Resources
122 | MultiLanguages.updateAppLanguage(Resources resources);
123 |
124 | // Set the default language (the earlier, the better)
125 | MultiLanguages.setDefaultLanguage(Locale locale);
126 | ```
127 |
128 | #### Language Change Listener
129 |
130 | ```java
131 | // Set language change listener
132 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
133 |
134 | @Override
135 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
136 | Log.d("MultiLanguages", "Detected app language change, old language: " + oldLocale + ", new language: " + newLocale);
137 | }
138 |
139 | @Override
140 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
141 | Log.d("MultiLanguages", "Detected system language change, old language: " + oldLocale + ", new language: " + newLocale + ", following system: " + MultiLanguages.isSystemLanguage());
142 | }
143 | });
144 | ```
145 |
146 | #### Usage Example
147 |
148 | ```java
149 | @Override
150 | public void onClick(View v) {
151 | // Whether restart is needed
152 | boolean restart;
153 | switch (v.getId()) {
154 | // Follow system
155 | case R.id.btn_language_auto:
156 | restart = MultiLanguages.clearAppLanguage(this);
157 | break;
158 | // Simplified Chinese
159 | case R.id.btn_language_cn:
160 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getSimplifiedChineseLocale());
161 | break;
162 | // Traditional Chinese
163 | case R.id.btn_language_tw:
164 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getTraditionalChineseLocale());
165 | break;
166 | // English
167 | case R.id.btn_language_en:
168 | restart = MultiLanguages.setAppLanguage(this, LocaleContract.getEnglishLocale());
169 | break;
170 | default:
171 | restart = false;
172 | break;
173 | }
174 |
175 | if (restart) {
176 | // You can make full use of Activity transition animations, and set a fade effect when switching
177 | Intent intent = new Intent(this, MainActivity.class);
178 | // Github address: https://github.com/getActivity/MultiLanguages/issues/55
179 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
180 | startActivity(intent);
181 | overridePendingTransition(R.anim.activity_alpha_in, R.anim.activity_alpha_out);
182 | finish();
183 | }
184 | }
185 | ```
186 |
187 | ## [Click here for Frequently Asked Questions](HelpDoc-en.md)
188 |
189 | #### Other resources: [Complete List of Language Codes](https://github.com/championswimmer/android-locales)
190 |
191 | #### Other Open Source Projects by the Author
192 |
193 | * Android middle office: [AndroidProject](https://github.com/getActivity/AndroidProject)
194 |
195 | * Android middle office kt version: [AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)
196 |
197 | * Permissions framework: [XXPermissions](https://github.com/getActivity/XXPermissions)  
198 |
199 | * Toast framework: [Toaster](https://github.com/getActivity/Toaster)
200 |
201 | * Network framework: [EasyHttp](https://github.com/getActivity/EasyHttp)
202 |
203 | * Title bar framework: [TitleBar](https://github.com/getActivity/TitleBar)
204 |
205 | * Floating window framework: [EasyWindow](https://github.com/getActivity/EasyWindow)
206 |
207 | * Device compatibility framework:[DeviceCompat](https://github.com/getActivity/DeviceCompat)  
208 |
209 | * Shape view framework: [ShapeView](https://github.com/getActivity/ShapeView)
210 |
211 | * Shape drawable framework: [ShapeDrawable](https://github.com/getActivity/ShapeDrawable)
212 |
213 | * Gson parsing fault tolerance: [GsonFactory](https://github.com/getActivity/GsonFactory)
214 |
215 | * Logcat viewing framework: [Logcat](https://github.com/getActivity/Logcat)
216 |
217 | * Nested scrolling layout framework:[NestedScrollLayout](https://github.com/getActivity/NestedScrollLayout)  
218 |
219 | * Android version guide: [AndroidVersionAdapter](https://github.com/getActivity/AndroidVersionAdapter)
220 |
221 | * Android code standard: [AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard)
222 |
223 | * Android resource summary:[AndroidIndex](https://github.com/getActivity/AndroidIndex)  
224 |
225 | * Android open source leaderboard: [AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss)
226 |
227 | * Studio boutique plugins: [StudioPlugins](https://github.com/getActivity/StudioPlugins)
228 |
229 | * Emoji collection: [EmojiPackage](https://github.com/getActivity/EmojiPackage)
230 |
231 | * China provinces json: [ProvinceJson](https://github.com/getActivity/ProvinceJson)
232 |
233 | * Markdown documentation:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc)  
234 |
235 | ## License
236 |
237 | ```text
238 | Copyright 2019 Huang JinQun
239 |
240 | Licensed under the Apache License, Version 2.0 (the "License");
241 | you may not use this file except in compliance with the License.
242 | You may obtain a copy of the License at
243 |
244 | http://www.apache.org/licenses/LICENSE-2.0
245 |
246 | Unless required by applicable law or agreed to in writing, software
247 | distributed under the License is distributed on an "AS IS" BASIS,
248 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
249 | See the License for the specific language governing permissions and
250 | limitations under the License.
251 | ```
--------------------------------------------------------------------------------
/HelpDoc-en.md:
--------------------------------------------------------------------------------
1 | #### Table of Contents
2 |
3 | * [How to Set the Default Language of the App](#how-to-set-the-default-language-of-the-app)
4 |
5 | * [What to Do If Switching Language Has No Effect](#what-to-do-if-switching-language-has-no-effect)
6 |
7 | * [How to Apply Language Change to All Activities](#how-to-apply-language-change-to-all-activities)
8 |
9 | * [How to Refresh All Screens When the User Switches System Language](#how-to-refresh-all-screens-when-the-user-switches-system-language)
10 |
11 | * [What to Do If Returning to the App After System Language Switch Has No Effect](#what-to-do-if-returning-to-the-app-after-system-language-switch-has-no-effect)
12 |
13 | * [Solution for WebView Causing Language Failure](#solution-for-webview-causing-language-failure)
14 |
15 | * [What to Do If WebView Language Fails on Android 13](#what-to-do-if-webview-language-fails-on-android-13)
16 |
17 | * [Is There a Way to Switch Language Without Restarting](#is-there-a-way-to-switch-language-without-restarting)
18 |
19 | #### How to Set the Default Language of the App
20 |
21 | * If you have never called `MultiLanguages.setAppLanguage` and do not want the app to follow the system language, but want to specify a certain language, you can do as follows:
22 |
23 | ```java
24 | public final class XxxApplication extends Application {
25 |
26 | static {
27 | // Set the default language (the earlier, the better)
28 | MultiLanguages.setDefaultLanguage(LocaleContract.getEnglishLocale());
29 | }
30 | }
31 | ```
32 |
33 | * If you want to determine the current system language type and then set the default language, you can write:
34 |
35 | ```java
36 | public final class XxxApplication extends Application {
37 |
38 | @Override
39 | protected void attachBaseContext(Context newBase) {
40 | if (newBase != null) {
41 | Locale systemLanguage = MultiLanguages.getSystemLanguage(newBase);
42 | // If the current language is neither Chinese (including Simplified and Traditional) nor English (including American and British), set it to English by default to avoid following the system language
43 | if (!MultiLanguages.equalsLanguage(systemLanguage, LocaleContract.getChineseLocale()) &&
44 | !MultiLanguages.equalsLanguage(systemLanguage, LocaleContract.getEnglishLocale())) {
45 | MultiLanguages.setDefaultLanguage(LocaleContract.getEnglishLocale());
46 | }
47 | }
48 | // Bind language
49 | super.attachBaseContext(MultiLanguages.attach(newBase));
50 | }
51 | }
52 | ```
53 |
54 | #### What to Do If Switching Language Has No Effect
55 |
56 | * Case 1: Check if you have configured to keep only certain country language resources in the `build.gradle` file, for example, `resConfigs 'zh'` means only Chinese-related language resources are kept, and other countries' language resources will not be packaged into the apk, which will cause the language to always be Chinese when switching.
57 |
58 | * Case 2: If the language switch in Fragment has no effect, check if the Fragment is reused after the Activity restarts, usually caused by calling `fragment.setRetainInstance(true)`.
59 |
60 | * Case 3: If you cannot switch the app language after uploading the aab package to Google Play or Huawei and downloading it, it is because multi-language resources are missing during splitting. You need to configure the main module's `build.gradle` file. For details, refer to the [official documentation](https://developer.android.google.cn/guide/app-bundle/configure-base?hl=en).
61 |
62 | ```groovy
63 | android {
64 |
65 | ......
66 |
67 | bundle {
68 |
69 | ......
70 |
71 | language {
72 | enableSplit = false
73 | }
74 |
75 | ......
76 | }
77 | }
78 | ```
79 |
80 | * Other cases: If the above reasons do not apply, please submit an [issue](https://github.com/getActivity/MultiLanguages/issues) for assistance.
81 |
82 | #### How to Apply Language Change to All Activities
83 |
84 | * Add the following code in the onCreate method of Application:
85 |
86 | ```java
87 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
88 |
89 | @Override
90 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
91 | Log.i("MultiLanguages", "Detected app language change, old language: " + oldLocale + ", new language: " + newLocale);
92 | // If you want the app to change with the system language, get all Activities here and call their recreate method
93 | // getAllActivity is just demonstration code, replace it with your own implementation. If not available, please encapsulate it yourself
94 | List activityList = getAllActivity();
95 | for (Activity activity : activityList) {
96 | activity.recreate();
97 | }
98 | }
99 |
100 | @Override
101 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
102 | Log.i("MultiLanguages", "Detected system language change, old language: " + oldLocale + ", new language: " + newLocale + ", following system: " + MultiLanguages.isSystemLanguage());
103 | }
104 | });
105 | ```
106 |
107 | #### How to Refresh All Screens When the User Switches System Language
108 |
109 | * Add the following code in the onCreate method of Application:
110 |
111 | ```java
112 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
113 |
114 | @Override
115 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
116 | Log.i("MultiLanguages", "Detected app language change, old language: " + oldLocale + ", new language: " + newLocale);
117 | }
118 |
119 | @Override
120 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
121 | Log.i("MultiLanguages", "Detected system language change, old language: " + oldLocale + ", new language: " + newLocale + ", following system: " + MultiLanguages.isSystemLanguage());
122 |
123 | // If the current language is not following the system, do not proceed
124 | if (!MultiLanguages.isSystemLanguage(AppApplication.this)) {
125 | return;
126 | }
127 |
128 | // If you want the app to change with the system language, get all Activities here and call their recreate method
129 | // getAllActivity is just demonstration code, replace it with your own implementation. If not available, please encapsulate it yourself
130 | List activityList = getAllActivity();
131 | for (Activity activity : activityList) {
132 | activity.recreate();
133 | }
134 | }
135 | });
136 | ```
137 |
138 | #### What to Do If Returning to the App After System Language Switch Has No Effect
139 |
140 | * Please check if the Activity is configured with the `android:configChanges` attribute. If so, remove it and try again. For documentation, see [``](https://developer.android.com/guide/topics/manifest/activity-element?hl=en)
141 |
142 | ```xml
143 |
146 | ```
147 |
148 | * If you do not want to remove the `android:configChanges` attribute but want the language switch to take effect after switching the system language, add the following code in the onCreate method of Application:
149 |
150 | ```java
151 | MultiLanguages.setOnLanguageListener(new OnLanguageListener() {
152 |
153 | @Override
154 | public void onAppLocaleChange(Locale oldLocale, Locale newLocale) {
155 | Log.i("MultiLanguages", "Detected app language change, old language: " + oldLocale + ", new language: " + newLocale);
156 | }
157 |
158 | @Override
159 | public void onSystemLocaleChange(Locale oldLocale, Locale newLocale) {
160 | Log.i("MultiLanguages", "Detected system language change, old language: " + oldLocale + ", new language: " + newLocale + ", following system: " + MultiLanguages.isSystemLanguage());
161 |
162 | // If the current language is not following the system, do not proceed
163 | if (!MultiLanguages.isSystemLanguage(AppApplication.this)) {
164 | return;
165 | }
166 |
167 | // If you want the app to change with the system language, get all Activities here and call their recreate method
168 | // getAllActivity is just demonstration code, replace it with your own implementation. If not available, please encapsulate it yourself
169 | List activityList = getAllActivity();
170 | for (Activity activity : activityList) {
171 | activity.recreate();
172 | }
173 | }
174 | });
175 | ```
176 |
177 | #### Solution for WebView Causing Language Failure
178 |
179 | * Since WebView initialization will modify the Activity language configuration, which indirectly causes the Activity language to be restored, you need to manually override WebView to fix this issue
180 |
181 | ```java
182 | public final class LanguagesWebView extends WebView {
183 |
184 | public LanguagesWebView(@NonNull Context context) {
185 | this(context, null);
186 | }
187 |
188 | public LanguagesWebView(@NonNull Context context, @Nullable AttributeSet attrs) {
189 | this(context, attrs, android.R.attr.webViewStyle);
190 | }
191 |
192 | public LanguagesWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
193 | super(context, attrs, defStyleAttr);
194 |
195 | // Fix the issue where WebView initialization modifies the Activity language configuration
196 | MultiLanguages.updateAppLanguage(context);
197 | }
198 | }
199 | ```
200 |
201 | #### What to Do If WebView Language Fails on Android 13
202 |
203 | * Replace `webView.loadUrl(@NonNull String url)` with `webView.loadUrl(@NonNull String url, @NonNull Map additionalHttpHeaders)` and add the `Accept-Language` request header. Example code:
204 |
205 | ```java
206 | public final class MainActivity extends Activity {
207 |
208 | private WebView mWebView;
209 |
210 | @Override
211 | protected void onCreate(Bundle savedInstanceState) {
212 | super.onCreate(savedInstanceState);
213 | setContentView(R.layout.activity_main);
214 |
215 | mWebView = findViewById(R.id.wv_main_web);
216 |
217 | mWebView.setWebViewClient(new LanguagesViewClient());
218 | mWebView.setWebChromeClient(new WebChromeClient());
219 | mWebView.loadUrl("https://developer.android.google.cn/kotlin", generateLanguageRequestHeader(this));
220 | }
221 |
222 | public static class LanguagesViewClient extends WebViewClient {
223 |
224 | @TargetApi(Build.VERSION_CODES.N)
225 | @Override
226 | public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
227 | return shouldOverrideUrlLoading(view, request.getUrl().toString());
228 | }
229 |
230 | @Override
231 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
232 | String scheme = Uri.parse(url).getScheme();
233 | if (scheme == null) {
234 | return false;
235 | }
236 | switch (scheme) {
237 | // If this is a link jump
238 | case "http":
239 | case "https":
240 | view.loadUrl(url, generateLanguageRequestHeader(view.getContext()));
241 | break;
242 | default:
243 | break;
244 | }
245 | return true;
246 | }
247 | }
248 |
249 | /**
250 | * Add language environment to WebView request header
251 | */
252 | @NonNull
253 | public static Map generateLanguageRequestHeader(Context context) {
254 | Map map = new HashMap<>(1);
255 | // Solution for language failure on Android 13
256 | // https://developer.android.com/about/versions/13/features/app-languages#consider-header
257 | map.put("Accept-Language", String.valueOf(MultiLanguages.getAppLanguage(context)));
258 | return map;
259 | }
260 | }
261 | ```
262 |
263 | #### Is There a Way to Switch Language Without Restarting
264 |
265 | * Let me ask everyone a question: once rice is cooked, how do you turn it back into raw rice? Obviously, this is unrealistic. Even if the framework could do it, and text and images could automatically change with the language, how would you switch the language for data requested via the network? Would you need to request again? If it's a list, would you need to request from page 1? Another question: is language switching a common action? I believe you already have the answer.
266 |
267 | * So it's not that switching language without restarting is impossible, but it's unnecessary (language switching is not a common action), and there are some inherent issues (even if the UI doesn't need to change, the data layer still needs to be re-requested).
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/language/LocaleContract.java:
--------------------------------------------------------------------------------
1 | package com.hjq.language;
2 |
3 | import java.util.Locale;
4 |
5 | /**
6 | * author : Android 轮子哥
7 | * github : https://github.com/getActivity/MultiLanguages
8 | * time : 2022/04/08
9 | * desc : 语种契约类
10 | * doc : https://blog.csdn.net/liuhhaiffeng/article/details/54706027
11 | */
12 | public final class LocaleContract {
13 |
14 | /** 中文 */
15 | private static volatile Locale sChineseLocale;
16 |
17 | public static Locale getChineseLocale() {
18 | if (sChineseLocale == null) {
19 | sChineseLocale = Locale.CHINESE;
20 | }
21 | return sChineseLocale;
22 | }
23 |
24 | /** 简体中文 */
25 | private static volatile Locale sSimplifiedChineseLocale;
26 |
27 | public static Locale getSimplifiedChineseLocale() {
28 | if (sSimplifiedChineseLocale == null) {
29 | sSimplifiedChineseLocale = Locale.SIMPLIFIED_CHINESE;
30 | }
31 | return sSimplifiedChineseLocale;
32 | }
33 |
34 | /** 繁体中文 */
35 | private static volatile Locale sTraditionalChineseLocale;
36 |
37 | public static Locale getTraditionalChineseLocale() {
38 | if (sTraditionalChineseLocale == null) {
39 | sTraditionalChineseLocale = Locale.TRADITIONAL_CHINESE;
40 | }
41 | return sTraditionalChineseLocale;
42 | }
43 |
44 | public static Locale getTaiWanLocale() {
45 | return getTraditionalChineseLocale();
46 | }
47 |
48 | /** 新加坡 */
49 | private static volatile Locale sSingaporeLocale ;
50 |
51 | public static Locale getSingaporeLocale() {
52 | if (sSimplifiedChineseLocale == null) {
53 | sSingaporeLocale = new Locale("zh", "SG");
54 | }
55 | return sSingaporeLocale;
56 | }
57 |
58 | /** 英语 */
59 | private static volatile Locale sEnglishLocale;
60 |
61 | public static Locale getEnglishLocale() {
62 | if (sEnglishLocale == null) {
63 | sEnglishLocale = Locale.ENGLISH;
64 | }
65 | return sEnglishLocale;
66 | }
67 |
68 | /** 法语 */
69 | private static volatile Locale sFrenchLocale;
70 |
71 | public static Locale getFrenchLocale() {
72 | if (sFrenchLocale == null) {
73 | sFrenchLocale = Locale.FRENCH;
74 | }
75 | return sFrenchLocale;
76 | }
77 |
78 | /** 德语 */
79 | private static volatile Locale sGermanLocale;
80 |
81 | public static Locale getGermanLocale() {
82 | if (sGermanLocale == null) {
83 | sGermanLocale = Locale.GERMAN;
84 | }
85 | return sGermanLocale;
86 | }
87 |
88 | /** 意大利语 */
89 | private static volatile Locale sItalianLocale;
90 |
91 | public static Locale getItalianLocale() {
92 | if (sItalianLocale == null) {
93 | sItalianLocale = Locale.ITALIAN;
94 | }
95 | return sItalianLocale;
96 | }
97 |
98 | /** 日语 */
99 | private static volatile Locale sJapaneseLocale;
100 |
101 | public static Locale getJapaneseLocale() {
102 | if (sJapaneseLocale == null) {
103 | sJapaneseLocale = Locale.JAPANESE;
104 | }
105 | return sJapaneseLocale;
106 | }
107 |
108 | /** 韩语 */
109 | private static volatile Locale sKoreanLocale;
110 |
111 | public static Locale getKoreanLocale() {
112 | if (sKoreanLocale == null) {
113 | sKoreanLocale = Locale.KOREAN;
114 | }
115 | return sKoreanLocale;
116 | }
117 |
118 | /** 越南语 */
119 | private static volatile Locale sVietnameseLocale;
120 |
121 | public static Locale getVietnameseLocale() {
122 | if (sVietnameseLocale == null) {
123 | sVietnameseLocale = new Locale("vi");
124 | }
125 | return sVietnameseLocale;
126 | }
127 |
128 | /** 荷兰语 */
129 | private static volatile Locale sDutchLocale;
130 |
131 | public static Locale getDutchLocale() {
132 | if (sDutchLocale == null) {
133 | sDutchLocale = new Locale("af");
134 | }
135 | return sDutchLocale;
136 | }
137 |
138 | /** 阿尔巴尼亚语 */
139 | private static volatile Locale sAlbanianLocale;
140 |
141 | public static Locale getAlbanianLocale() {
142 | if (sAlbanianLocale == null) {
143 | sAlbanianLocale = new Locale("sq");
144 | }
145 | return sAlbanianLocale;
146 | }
147 |
148 | /** 阿拉伯语 */
149 | private static volatile Locale sArabicLocale;
150 |
151 | public static Locale getArabicLocale() {
152 | if (sArabicLocale == null) {
153 | sArabicLocale = new Locale("ar");
154 | }
155 | return sArabicLocale;
156 | }
157 |
158 | /** 亚美尼亚语 */
159 | private static volatile Locale sArmenianLocale;
160 |
161 | public static Locale getArmenianLocale() {
162 | if (sArmenianLocale == null) {
163 | sArmenianLocale = new Locale("hy");
164 | }
165 | return sArmenianLocale;
166 | }
167 |
168 | /** 阿塞拜疆语 */
169 | private static volatile Locale sAzerbaijaniLocale;
170 |
171 | public static Locale getAzerbaijaniLocale() {
172 | if (sAzerbaijaniLocale == null) {
173 | sAzerbaijaniLocale = new Locale("az");
174 | }
175 | return sAzerbaijaniLocale;
176 | }
177 |
178 | /** 巴斯克语 */
179 | private static volatile Locale sBasqueLocale;
180 |
181 | public static Locale getBasqueLocale() {
182 | if (sBasqueLocale == null) {
183 | sBasqueLocale = new Locale("eu");
184 | }
185 | return sBasqueLocale;
186 | }
187 |
188 | /** 白俄罗斯 */
189 | private static volatile Locale sBelarusianLocale;
190 |
191 | public static Locale getBelarusianLocale() {
192 | if (sBelarusianLocale == null) {
193 | sBelarusianLocale = new Locale("be");
194 | }
195 | return sBelarusianLocale;
196 | }
197 |
198 | /** 保加利亚 */
199 | private static volatile Locale sBulgariaLocale;
200 |
201 | public static Locale getBulgariaLocale() {
202 | if (sBulgariaLocale == null) {
203 | sBulgariaLocale = new Locale("bg");
204 | }
205 | return sBulgariaLocale;
206 | }
207 |
208 | /** 加泰罗尼亚 */
209 | private static volatile Locale sCatalonianLocale;
210 |
211 | public static Locale getCatalonianLocale() {
212 | if (sCatalonianLocale == null) {
213 | sCatalonianLocale = new Locale("ca");
214 | }
215 | return sCatalonianLocale;
216 | }
217 |
218 | /** 克罗埃西亚 */
219 | private static volatile Locale sCroatiaLocale;
220 |
221 | public static Locale getCroatiaLocale() {
222 | if (sCroatiaLocale == null) {
223 | sCroatiaLocale = new Locale("hr");
224 | }
225 | return sCroatiaLocale;
226 | }
227 |
228 | /** 捷克 */
229 | private static volatile Locale sCzechRepublicLocale;
230 |
231 | public static Locale getCzechRepublicLocale() {
232 | if (sCzechRepublicLocale == null) {
233 | sCzechRepublicLocale = new Locale("cs");
234 | }
235 | return sCzechRepublicLocale;
236 | }
237 |
238 | /** 丹麦文 */
239 | private static volatile Locale sDanishLocale;
240 |
241 | public static Locale getDanishLocale() {
242 | if (sDanishLocale == null) {
243 | sDanishLocale = new Locale("da");
244 | }
245 | return sDanishLocale;
246 | }
247 |
248 | /** 迪维希语 */
249 | private static volatile Locale sDhivehiLocale;
250 |
251 | public static Locale getDhivehiLocale() {
252 | if (sDhivehiLocale == null) {
253 | sDhivehiLocale = new Locale("div");
254 | }
255 | return sDhivehiLocale;
256 | }
257 |
258 | /** 荷兰 */
259 | private static volatile Locale sNetherlandsLocale;
260 |
261 | public static Locale getNetherlandsLocale() {
262 | if (sNetherlandsLocale == null) {
263 | sNetherlandsLocale = new Locale("nl");
264 | }
265 | return sNetherlandsLocale;
266 | }
267 |
268 | /** 法罗语 */
269 | private static volatile Locale sFaroeseLocale;
270 |
271 | public static Locale getFaroeseLocale() {
272 | if (sFaroeseLocale == null) {
273 | sFaroeseLocale = new Locale("fo");
274 | }
275 | return sFaroeseLocale;
276 | }
277 |
278 | /** 爱沙尼亚 */
279 | private static volatile Locale sEstoniaLocale;
280 |
281 | public static Locale getEstoniaLocale() {
282 | if (sEstoniaLocale == null) {
283 | sEstoniaLocale = new Locale("et");
284 | }
285 | return sEstoniaLocale;
286 | }
287 |
288 | /** 波斯语 */
289 | private static volatile Locale sFarsiLocale;
290 |
291 | public static Locale getFarsiLocale() {
292 | if (sFarsiLocale == null) {
293 | sFarsiLocale = new Locale("fa");
294 | }
295 | return sFarsiLocale;
296 | }
297 |
298 | /** 芬兰语 */
299 | private static volatile Locale sFinnishLocale;
300 |
301 | public static Locale getFinnishLocale() {
302 | if (sFinnishLocale == null) {
303 | sFinnishLocale = new Locale("fi");
304 | }
305 | return sFinnishLocale;
306 | }
307 |
308 | /** 加利西亚 */
309 | private static volatile Locale sGaliciaLocale;
310 |
311 | public static Locale getGaliciaLocale() {
312 | if (sGaliciaLocale == null) {
313 | sGaliciaLocale = new Locale("gl");
314 | }
315 | return sGaliciaLocale;
316 | }
317 |
318 | /** 格鲁吉亚州 */
319 | private static volatile Locale sGeorgiaLocale;
320 |
321 | public static Locale getGeorgiaLocale() {
322 | if (sGeorgiaLocale == null) {
323 | sGeorgiaLocale = new Locale("ka");
324 | }
325 | return sGeorgiaLocale;
326 | }
327 |
328 | /** 希腊 */
329 | private static volatile Locale sGreeceLocale;
330 |
331 | public static Locale getGreeceLocale() {
332 | if (sGreeceLocale == null) {
333 | sGreeceLocale = new Locale("el");
334 | }
335 | return sGreeceLocale;
336 | }
337 |
338 | /** 古吉拉特语 */
339 | private static volatile Locale sGujaratiLocale;
340 |
341 | public static Locale getGujaratiLocale() {
342 | if (sGujaratiLocale == null) {
343 | sGujaratiLocale = new Locale("gu");
344 | }
345 | return sGujaratiLocale;
346 | }
347 |
348 | /** 希伯来 */
349 | private static volatile Locale sHebrewLocale;
350 |
351 | public static Locale getHebrewLocale() {
352 | if (sHebrewLocale == null) {
353 | sHebrewLocale = new Locale("he");
354 | }
355 | return sHebrewLocale;
356 | }
357 |
358 | /** 北印度语 */
359 | private static volatile Locale sHindiLocale;
360 |
361 | public static Locale getHindiLocale() {
362 | if (sHindiLocale == null) {
363 | sHindiLocale = new Locale("hi");
364 | }
365 | return sHindiLocale;
366 | }
367 |
368 | /** 匈牙利 */
369 | private static volatile Locale sHungaryLocale;
370 |
371 | public static Locale getHungaryLocale() {
372 | if (sHungaryLocale == null) {
373 | sHungaryLocale = new Locale("hu");
374 | }
375 | return sHungaryLocale;
376 | }
377 |
378 | /** 冰岛语 */
379 | private static volatile Locale sIcelandicLocale;
380 |
381 | public static Locale getIcelandicLocale() {
382 | if (sIcelandicLocale == null) {
383 | sIcelandicLocale = new Locale("is");
384 | }
385 | return sIcelandicLocale;
386 | }
387 |
388 | /** 印尼 */
389 | private static volatile Locale sIndonesiaLocale;
390 |
391 | public static Locale getIndonesiaLocale() {
392 | if (sIndonesiaLocale == null) {
393 | sIndonesiaLocale = new Locale("id");
394 | }
395 | return sIndonesiaLocale;
396 | }
397 |
398 | /** 卡纳达语 */
399 | private static volatile Locale sKannadaLocale;
400 |
401 | public static Locale getKannadaLocale() {
402 | if (sKannadaLocale == null) {
403 | sKannadaLocale = new Locale("kn");
404 | }
405 | return sKannadaLocale;
406 | }
407 |
408 | /** 哈萨克语 */
409 | private static volatile Locale sKazakhLocale;
410 |
411 | public static Locale getKazakhLocale() {
412 | if (sKazakhLocale == null) {
413 | sKazakhLocale = new Locale("kk");
414 | }
415 | return sKazakhLocale;
416 | }
417 |
418 | /** 贡根文 */
419 | private static volatile Locale sKonkaniLocale;
420 |
421 | public static Locale getKonkaniLocale() {
422 | if (sKonkaniLocale == null) {
423 | sKonkaniLocale = new Locale("kok");
424 | }
425 | return sKonkaniLocale;
426 | }
427 |
428 | /** 吉尔吉斯斯坦 */
429 | private static volatile Locale sKyrgyzLocale;
430 |
431 | public static Locale getKyrgyzLocale() {
432 | if (sKyrgyzLocale == null) {
433 | sKyrgyzLocale = new Locale("ky");
434 | }
435 | return sKyrgyzLocale;
436 | }
437 |
438 | /** 拉脱维亚 */
439 | private static volatile Locale sLatviaLocale;
440 |
441 | public static Locale getLatviaLocale() {
442 | if (sLatviaLocale == null) {
443 | sLatviaLocale = new Locale("lv");
444 | }
445 | return sLatviaLocale;
446 | }
447 |
448 | /** 立陶宛 */
449 | private static volatile Locale sLithuaniaLocale;
450 |
451 | public static Locale getLithuaniaLocale() {
452 | if (sLithuaniaLocale == null) {
453 | sLithuaniaLocale = new Locale("lt");
454 | }
455 | return sLithuaniaLocale;
456 | }
457 |
458 | /** 马其顿 */
459 | private static volatile Locale sMacedoniaLocale;
460 |
461 | public static Locale getMacedoniaLocale() {
462 | if (sMacedoniaLocale == null) {
463 | sMacedoniaLocale = new Locale("mk");
464 | }
465 | return sMacedoniaLocale;
466 | }
467 |
468 | /** 马来 */
469 | private static volatile Locale sMalayLocale;
470 |
471 | public static Locale getMalayLocale() {
472 | if (sMalayLocale == null) {
473 | sMalayLocale = new Locale("ms");
474 | }
475 | return sMalayLocale;
476 | }
477 |
478 | /** 马拉地语 */
479 | private static volatile Locale sMarathiLocale;
480 |
481 | public static Locale getMarathiLocale() {
482 | if (sMarathiLocale == null) {
483 | sMarathiLocale = new Locale("mr");
484 | }
485 | return sMarathiLocale;
486 | }
487 |
488 | /** 蒙古 */
489 | private static volatile Locale sMongoliaLocale;
490 |
491 | public static Locale getMongoliaLocale() {
492 | if (sMongoliaLocale == null) {
493 | sMongoliaLocale = new Locale("mn");
494 | }
495 | return sMongoliaLocale;
496 | }
497 |
498 | /** 挪威 */
499 | private static volatile Locale sNorwayLocale;
500 |
501 | public static Locale getNorwayLocale() {
502 | if (sNorwayLocale == null) {
503 | sNorwayLocale = new Locale("no");
504 | }
505 | return sNorwayLocale;
506 | }
507 |
508 | /** 波兰 */
509 | private static volatile Locale sPolandLocale;
510 |
511 | public static Locale getPolandLocale() {
512 | if (sPolandLocale == null) {
513 | sPolandLocale = new Locale("pl");
514 | }
515 | return sPolandLocale;
516 | }
517 |
518 | /** 葡萄牙语 */
519 | private static volatile Locale sPortugueseLocale;
520 |
521 | public static Locale getPortugalLocale() {
522 | if (sPortugueseLocale == null) {
523 | sPortugueseLocale = new Locale("pt");
524 | }
525 | return sPortugueseLocale;
526 | }
527 |
528 | /** 旁遮普语(印度和巴基斯坦语) */
529 | private static volatile Locale sPunjabLocale;
530 |
531 | public static Locale getPunjabLocale() {
532 | if (sPunjabLocale == null) {
533 | sPunjabLocale = new Locale("pa");
534 | }
535 | return sPunjabLocale;
536 | }
537 |
538 | /** 罗马尼亚语 */
539 | private static volatile Locale sRomanianLocale;
540 |
541 | public static Locale getRomanianLocale() {
542 | if (sRomanianLocale == null) {
543 | sRomanianLocale = new Locale("ro");
544 | }
545 | return sRomanianLocale;
546 | }
547 |
548 | /** 俄国 */
549 | private static volatile Locale sRussiaLocale;
550 |
551 | public static Locale getRussiaLocale() {
552 | if (sRussiaLocale == null) {
553 | sRussiaLocale = new Locale("ru");
554 | }
555 | return sRussiaLocale;
556 | }
557 |
558 | /** 梵文 */
559 | private static volatile Locale sSanskritLocale;
560 |
561 | public static Locale getSanskritLocale() {
562 | if (sSanskritLocale == null) {
563 | sSanskritLocale = new Locale("sa");
564 | }
565 | return sSanskritLocale;
566 | }
567 |
568 | /** 斯洛伐克 */
569 | private static volatile Locale sSlovakiaLocale;
570 |
571 | public static Locale getSlovakiaLocale() {
572 | if (sSlovakiaLocale == null) {
573 | sSlovakiaLocale = new Locale("sk");
574 | }
575 | return sSlovakiaLocale;
576 | }
577 |
578 | /** 斯洛文尼亚 */
579 | private static volatile Locale sSloveniaLocale;
580 |
581 | public static Locale getSloveniaLocale() {
582 | if (sSloveniaLocale == null) {
583 | sSloveniaLocale = new Locale("sl");
584 | }
585 | return sSloveniaLocale;
586 | }
587 |
588 | /** 西班牙语 */
589 | private static volatile Locale sSpanishLocale;
590 |
591 | public static Locale getSpainLocale() {
592 | if (sSpanishLocale == null) {
593 | sSpanishLocale = new Locale("es");
594 | }
595 | return sSpanishLocale;
596 | }
597 |
598 | /** 斯瓦希里语 */
599 | private static volatile Locale sSwahiliLocale;
600 |
601 | public static Locale getSwahiliLocale() {
602 | if (sSwahiliLocale == null) {
603 | sSwahiliLocale = new Locale("sw");
604 | }
605 | return sSwahiliLocale;
606 | }
607 |
608 | /** 瑞典 */
609 | private static volatile Locale sSwedenLocale;
610 |
611 | public static Locale getSwedenLocale() {
612 | if (sSwedenLocale == null) {
613 | sSwedenLocale = new Locale("sv");
614 | }
615 | return sSwedenLocale;
616 | }
617 |
618 | /** 叙利亚语 */
619 | private static volatile Locale sSyriacLocale;
620 |
621 | public static Locale getSyriacLocale() {
622 | if (sSyriacLocale == null) {
623 | sSyriacLocale = new Locale("syr");
624 | }
625 | return sSyriacLocale;
626 | }
627 |
628 | /** 坦米尔 */
629 | private static volatile Locale sTamilLocale;
630 |
631 | public static Locale getTamilLocale() {
632 | if (sTamilLocale == null) {
633 | sTamilLocale = new Locale("ta");
634 | }
635 | return sTamilLocale;
636 | }
637 |
638 | /** 泰语 */
639 | private static volatile Locale sThailandLocale;
640 |
641 | public static Locale getThailandLocale() {
642 | if (sThailandLocale == null) {
643 | sThailandLocale = new Locale("th");
644 | }
645 | return sThailandLocale;
646 | }
647 |
648 | /** 鞑靼语 */
649 | private static volatile Locale sTatarLocale;
650 |
651 | public static Locale getTatarLocale() {
652 | if (sTatarLocale == null) {
653 | sTatarLocale = new Locale("tt");
654 | }
655 | return sTatarLocale;
656 | }
657 |
658 | /** 泰卢固语 */
659 | private static volatile Locale sTeluguLocale;
660 |
661 | public static Locale getTeluguLocale() {
662 | if (sTeluguLocale == null) {
663 | sTeluguLocale = new Locale("te");
664 | }
665 | return sTeluguLocale;
666 | }
667 |
668 | /** 土耳其语 */
669 | private static volatile Locale sTurkishLocale;
670 |
671 | public static Locale getTurkishLocale() {
672 | if (sTurkishLocale == null) {
673 | sTurkishLocale = new Locale("tr");
674 | }
675 | return sTurkishLocale;
676 | }
677 |
678 | /** 乌克兰 */
679 | private static volatile Locale sUkraineLocale;
680 |
681 | public static Locale getUkraineLocale() {
682 | if (sUkraineLocale == null) {
683 | sUkraineLocale = new Locale("uk");
684 | }
685 | return sUkraineLocale;
686 | }
687 |
688 | /** 乌尔都语 */
689 | private static volatile Locale sUrduLocale;
690 |
691 | public static Locale getUrduLocale() {
692 | if (sUrduLocale == null) {
693 | sUrduLocale = new Locale("ur");
694 | }
695 | return sUrduLocale;
696 | }
697 |
698 | /** 乌兹别克语 */
699 | private static volatile Locale sUzbekLocale;
700 |
701 | public static Locale getUzbekLocale() {
702 | if (sUzbekLocale == null) {
703 | sUzbekLocale = new Locale("uz");
704 | }
705 | return sUzbekLocale;
706 | }
707 | }
--------------------------------------------------------------------------------