├── .github └── workflows │ └── android.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── MIGRATE_TO_ANDROIDX.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── akexorcist │ │ └── localizationapp │ │ ├── AllTest.kt │ │ ├── BroadcastReceiverTest.kt │ │ ├── CustomActivityTest.kt │ │ ├── DarkThemeTest.kt │ │ ├── DialogWebViewTest.kt │ │ ├── HiltDependencyInjectionTest.kt │ │ ├── ListPreferencesTest.kt │ │ ├── NestedFragmentTest.kt │ │ ├── SimpleActivityTest.kt │ │ ├── SimpleDialogTest.kt │ │ ├── SimpleFragmentTest.kt │ │ ├── StackedActivityTest.kt │ │ ├── ViewPagerTest.kt │ │ ├── annotation │ │ └── TargetApiLevel.kt │ │ ├── data │ │ └── ExpectedContent.kt │ │ └── screen │ │ ├── CustomActivityScreen.kt │ │ ├── DarkThemeScreen.kt │ │ ├── DialogWebViewLanguageChooserScreen.kt │ │ ├── DialogWebViewMainScreen.kt │ │ ├── DialogWebViewSiteScreen.kt │ │ ├── HiltDependencyInjectionScreen.kt │ │ ├── ListPreferencesScreen.kt │ │ ├── MainActivityScreen.kt │ │ ├── NestedFragmentScreen.kt │ │ ├── SimpleActivityScreen.kt │ │ ├── SimpleBroadcastScreen.kt │ │ ├── SimpleDialogContentScreen.kt │ │ ├── SimpleDialogLanguageChooserScreen.kt │ │ ├── SimpleDialogMainScreen.kt │ │ ├── SimpleFragmentScreen.kt │ │ ├── StackedHomeScreen.kt │ │ ├── StackedLanguageChooserScreen.kt │ │ └── ViewPagerScreen.kt │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── com │ │ └── akexorcist │ │ └── localizationapp │ │ ├── MainActivity.kt │ │ ├── MainApplication.kt │ │ ├── broadcast │ │ ├── BroadcastEvent.kt │ │ ├── SimpleBroadcastActivity.kt │ │ └── SimpleBroadcastReceiver.kt │ │ ├── customactivity │ │ ├── CustomActivity.kt │ │ └── SimpleCustomActivity.kt │ │ ├── darktheme │ │ ├── DarkThemeActivity.kt │ │ └── DarkThemeFragment.kt │ │ ├── dialogwebview │ │ ├── DialogWebViewLanguageChooserFragment.kt │ │ ├── DialogWebViewMainActivity.kt │ │ └── DialogWebViewSiteActivity.kt │ │ ├── hilt │ │ ├── HiltActivity.kt │ │ ├── HiltFragmentWithViewModel.kt │ │ ├── HiltModule.kt │ │ ├── HiltViewModel.kt │ │ └── StoryProvider.kt │ │ ├── nestedfragment │ │ ├── ChildFragment.kt │ │ ├── NestedFragmentActivity.kt │ │ └── ParentFragment.kt │ │ ├── preferences │ │ ├── LanguagePreferenceFragment.kt │ │ └── ListPreferencesActivity.kt │ │ ├── simpleactivity │ │ └── SimpleActivity.kt │ │ ├── simpledialog │ │ ├── SimpleDialogContentFragment.kt │ │ ├── SimpleDialogLanguageChooserActivity.kt │ │ └── SimpleDialogMainActivity.kt │ │ ├── simplefragment │ │ ├── SimpleFragment.kt │ │ └── SimpleFragmentActivity.kt │ │ ├── stackedactivity │ │ ├── StackedHomeActivity.kt │ │ └── StackedLanguageChooserActivity.kt │ │ └── viewpager │ │ ├── HelloFragment.kt │ │ ├── OneFragment.kt │ │ ├── ThreeFragment.kt │ │ ├── TwoFragment.kt │ │ ├── ViewPagerActivity.kt │ │ └── ViewPagerAdapter.kt │ └── res │ ├── color │ ├── button_white.xml │ └── button_white_daynight.xml │ ├── drawable │ ├── shape_background_content.xml │ ├── shape_background_primary_gradient.xml │ └── shape_background_primary_gradient_daynight.xml │ ├── layout │ ├── activity_custom.xml │ ├── activity_dark_theme.xml │ ├── activity_dialog_webview_main.xml │ ├── activity_dialog_webview_site.xml │ ├── activity_hilt.xml │ ├── activity_list_preferences.xml │ ├── activity_main.xml │ ├── activity_nested_fragment.xml │ ├── activity_simple.xml │ ├── activity_simple_broadcast.xml │ ├── activity_simple_dialog_language_chooser.xml │ ├── activity_simple_dialog_main.xml │ ├── activity_simple_fragment.xml │ ├── activity_stacked_home.xml │ ├── activity_stacked_language_chooser.xml │ ├── activity_view_pager.xml │ ├── fragment_dark_theme.xml │ ├── fragment_dialog_webview_language_chooser.xml │ ├── fragment_hilt.xml │ ├── fragment_nested_child.xml │ ├── fragment_nested_parent.xml │ ├── fragment_simple.xml │ ├── fragment_simple_dialog_content.xml │ ├── fragment_view_pager_hello.xml │ ├── fragment_view_pager_one.xml │ ├── fragment_view_pager_three.xml │ ├── fragment_view_pager_two.xml │ └── layout_language_chooser.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── apple.png │ ├── country_america.png │ ├── country_china.png │ ├── country_italy.png │ ├── country_japan.png │ ├── country_korea.png │ ├── country_portugal.png │ ├── country_thai.png │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-it │ └── strings.xml │ ├── values-ja │ └── strings.xml │ ├── values-ko │ └── strings.xml │ ├── values-night │ └── colors.xml │ ├── values-pt │ └── strings.xml │ ├── values-th │ └── strings.xml │ ├── values-zh │ └── strings.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── settings_strings.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── settings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image ├── 01-header.jpg ├── 02-string_resource.jpg ├── 04-life_cycle.jpg ├── 05-step_one.jpg ├── 06-step_two.jpg ├── 07-step_three.jpg └── 08-step_four.jpg ├── localization ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── akexorcist │ │ └── localizationactivity │ │ └── LanguageSettingTest.kt │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── akexorcist │ └── localizationactivity │ ├── core │ ├── LanguageSetting.kt │ ├── LocalizationActivityDelegate.kt │ ├── LocalizationApplicationDelegate.kt │ ├── LocalizationDelegate.kt │ ├── LocalizationExtension.kt │ ├── LocalizationServiceDelegate.kt │ ├── LocalizationUtility.kt │ └── OnLocaleChangedListener.kt │ └── ui │ ├── LocalizationActivity.kt │ ├── LocalizationApplication.kt │ └── LocalizationService.kt ├── publish └── mavencentral.gradle └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | name: Unit Test 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: set up JDK 15 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: 15 20 | - name: Grant execute permission for gradlew 21 | run: chmod +x gradlew 22 | 23 | - name: Run Unit test 24 | run: ./gradlew test 25 | 26 | apk: 27 | name: Generate APK 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: set up JDK 15 33 | uses: actions/setup-java@v1 34 | with: 35 | java-version: 15 36 | - name: Grant execute permission for gradlew 37 | run: chmod +x gradlew 38 | 39 | - name: Assemble App APK 40 | run: ./gradlew assembleDebug 41 | - name: Upload App APK 42 | uses: actions/upload-artifact@v1 43 | with: 44 | name: app-debug 45 | path: app/build/outputs/apk/debug/app-debug.apk 46 | 47 | - name: Assemble Android Instrumented Unit Test 48 | run: ./gradlew assembleDebugAndroidTest 49 | - name: Upload Android Test APK 50 | uses: actions/upload-artifact@v1 51 | with: 52 | name: app-debug-androidTest 53 | path: app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk 54 | 55 | firebase: 56 | name: Run UI tests with Firebase Test Lab 57 | needs: apk 58 | runs-on: ubuntu-18.04 59 | 60 | steps: 61 | - uses: actions/checkout@v1 62 | 63 | - name: Download app APK 64 | uses: actions/download-artifact@v1 65 | with: 66 | name: app-debug 67 | 68 | - name: Download Android test APK 69 | uses: actions/download-artifact@v1 70 | with: 71 | name: app-debug-androidTest 72 | 73 | - name: Login to Google Cloud 74 | uses: google-github-actions/setup-gcloud@master 75 | with: 76 | version: '270.0.0' 77 | service_account_key: ${{ secrets.FIREBASE_SERVICES_KEY }} 78 | 79 | - name: Set current project 80 | run: "gcloud config set project ${{ secrets.FIREBASE_PROJECT_ID }}" 81 | 82 | - name: Run Instrumentation Tests in Firebase Test Lab 83 | run: "gcloud firebase test android run 84 | --type instrumentation 85 | --app app-debug/app-debug.apk 86 | --test app-debug-androidTest/app-debug-androidTest.apk 87 | --device model=redfin,version=30,locale=en,orientation=portrait 88 | --device model=greatlteks,version=28,locale=en,orientation=portrait 89 | --device model=OnePlus3T,version=26,locale=en,orientation=portrait 90 | --device model=HWMHA,version=24,locale=en,orientation=portrait 91 | --device model=grandppltedx,version=23,locale=en,orientation=portrait 92 | --device model=hwALE-H,version=21,locale=en,orientation=portrait" 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea 38 | 39 | # Keystore files 40 | # Uncomment the following line if you do not want to check your keystore files in. 41 | #*.jks 42 | 43 | # External native build folder generated in Android Studio 2.2 and later 44 | .externalNativeBuild 45 | 46 | # Google Services (e.g. APIs or Firebase) 47 | google-services.json 48 | 49 | # Freeline 50 | freeline.py 51 | freeline/ 52 | freeline_project_description.json 53 | 54 | # fastlane 55 | fastlane/report.xml 56 | fastlane/Preview.html 57 | fastlane/screenshots 58 | fastlane/test_output 59 | fastlane/readme.md 60 | *.gpg 61 | .DS_Store 62 | .cxx 63 | -------------------------------------------------------------------------------- /MIGRATE_TO_ANDROIDX.md: -------------------------------------------------------------------------------- 1 | # Migrate to AndroidX 2 | Since AndroidX supports per-app language preferences for backward compatibility. Please migrate this library to AndroidX for more stability, compatibility, and longer supports from Google team. 3 | 4 | * AppCompat `1.6.0-alpha03` or higher is required (see [AppCompat release notes](https://developer.android.com/jetpack/androidx/releases/appcompat)) 5 | * Remove `LocalizationApplicationDelegate` from Application class 6 | * Replace `LocalizationActivity` with `AppCompatActivity` or remove `LocalizationActivityDelegate` from your custom Activity 7 | * Replace `setLanguage(language)` with `AppCompatDelegate.setApplicationLocales()` 8 | ```kotlin 9 | val locales = LocaleListCompat.forLanguageTags(language) 10 | AppCompatDelegate.setApplicationLocales(locales) 11 | ``` 12 | * Remove `toLocalizedContext()` and use original Context 13 | * Remove `LocalizationServiceDelegate` from Service class 14 | * Add `androidx.appcompat.app.AppLocalesMetadataHolderService` as `` in Android Manifest (see [Support Android 12 and lower](https://developer.android.com/about/versions/13/features/app-languages#android12-impl)) 15 | ```xml 16 | 17 | 18 | 19 | 23 | 26 | 27 | 28 | 29 | ``` 30 | * Remove this library from your project (`implementation 'com.akexorcist:localization:'`) 31 | * Test your app 32 | 33 | For more information about per-app language preferences, see [Per-app language preferences](https://developer.android.com/about/versions/13/features/app-languages) 34 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'dagger.hilt.android.plugin' 5 | 6 | android { 7 | compileSdkVersion project.compileSdkVersion 8 | 9 | defaultConfig { 10 | applicationId "com.akexorcist.localizationapp" 11 | minSdkVersion 19 12 | targetSdkVersion project.targetSdkVersion 13 | versionCode project.versionCode 14 | versionName project.versionName 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | debug { 20 | minifyEnabled false 21 | shrinkResources false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | release { 25 | minifyEnabled true 26 | shrinkResources true 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | compileOptions { 32 | sourceCompatibility project.targetJavaVersion 33 | targetCompatibility project.targetJavaVersion 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = project.targetJavaVersion.toString() 38 | } 39 | 40 | buildFeatures { 41 | viewBinding true 42 | } 43 | 44 | testOptions { 45 | animationsDisabled true 46 | } 47 | } 48 | 49 | dependencies { 50 | implementation fileTree(dir: 'libs', include: ['*.jar']) 51 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 52 | implementation 'androidx.appcompat:appcompat:1.4.0' 53 | implementation 'androidx.viewpager:viewpager:1.0.0' 54 | implementation 'androidx.preference:preference-ktx:1.1.1' 55 | implementation "androidx.viewpager2:viewpager2:1.0.0" 56 | implementation 'com.google.dagger:hilt-android:2.40.4' 57 | kapt 'com.google.dagger:hilt-compiler:2.40.4' 58 | 59 | androidTestImplementation 'androidx.test:runner:1.4.0' 60 | androidTestImplementation 'androidx.test:rules:1.4.0' 61 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 62 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 63 | androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0' 64 | androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' 65 | androidTestImplementation 'com.agoda.kakao:kakao:2.4.0' 66 | 67 | implementation project(':localization') 68 | } 69 | -------------------------------------------------------------------------------- /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 /Applications/ADT/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 | -keep class com.akexorcist.localizationactivity.** { *; } 20 | -dontwarn com.akexorcist.localizationactivity.** 21 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/AllTest.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp 2 | 3 | import org.junit.runner.RunWith 4 | import org.junit.runners.Suite 5 | 6 | //@RunWith(Suite::class) 7 | //@Suite.SuiteClasses( 8 | // SimpleActivityTest::class, 9 | // CustomActivityTest::class, 10 | // StackedActivityTest::class, 11 | // SimpleFragmentTest::class, 12 | // NestedFragmentTest::class, 13 | // SimpleDialogTest::class, 14 | // DialogWebViewTest::class, 15 | // ViewPagerTest::class, 16 | // ListPreferencesTest::class, 17 | // HiltDependencyInjectionTest::class, 18 | // BroadcastReceiverTest::class, 19 | // DarkThemeTest::class, 20 | //) 21 | //class AllTest -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/BroadcastReceiverTest.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp 2 | 3 | import androidx.test.ext.junit.rules.ActivityScenarioRule 4 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner 5 | import androidx.test.platform.app.InstrumentationRegistry 6 | import androidx.test.uiautomator.UiDevice 7 | import com.agoda.kakao.screen.Screen.Companion.onScreen 8 | import com.akexorcist.localizationapp.data.ExpectedContent 9 | import com.akexorcist.localizationapp.screen.MainActivityScreen 10 | import com.akexorcist.localizationapp.screen.SimpleBroadcastScreen 11 | import org.junit.Ignore 12 | import org.junit.Rule 13 | import org.junit.Test 14 | import org.junit.runner.RunWith 15 | 16 | @RunWith(AndroidJUnit4ClassRunner::class) 17 | class BroadcastReceiverTest { 18 | @JvmField 19 | val uiDevices: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) 20 | 21 | @Rule 22 | @JvmField 23 | val rule = ActivityScenarioRule(MainActivity::class.java) 24 | 25 | @Test 26 | @Ignore("For local running only, there is some flakiness when running on Firebase Test Lab") 27 | fun broadcastReceiver() { 28 | onScreen { 29 | buttonBroadcastReceiver { 30 | scrollTo() 31 | click() 32 | } 33 | } 34 | onScreen { 35 | // Chinese 36 | buttonChinese { 37 | scrollTo() 38 | click() 39 | } 40 | textViewContent { 41 | hasText(ExpectedContent.APPLE_CHINESE) 42 | } 43 | // Italian 44 | buttonItalian { 45 | scrollTo() 46 | click() 47 | } 48 | textViewContent { 49 | hasText(ExpectedContent.APPLE_ITALIAN) 50 | } 51 | // Japanese 52 | buttonJapanese { 53 | scrollTo() 54 | click() 55 | } 56 | textViewContent { 57 | hasText(ExpectedContent.APPLE_JAPANESE) 58 | } 59 | // Korean 60 | buttonKorean { 61 | scrollTo() 62 | click() 63 | } 64 | textViewContent { 65 | hasText(ExpectedContent.APPLE_KOREAN) 66 | } 67 | // Portuguese 68 | buttonPortuguese { 69 | scrollTo() 70 | click() 71 | } 72 | textViewContent { 73 | hasText(ExpectedContent.APPLE_PORTUGUESE) 74 | } 75 | // Thai 76 | buttonThai { 77 | scrollTo() 78 | click() 79 | } 80 | textViewContent { 81 | hasText(ExpectedContent.APPLE_THAI) 82 | } 83 | // American 84 | buttonAmerican { 85 | scrollTo() 86 | click() 87 | } 88 | textViewContent { 89 | hasText(ExpectedContent.APPLE_AMERICAN) 90 | } 91 | buttonBack { click() } 92 | } 93 | onScreen { 94 | textViewTitle { 95 | hasText(ExpectedContent.HELLO_WORLD_AMERICAN) 96 | uiDevices.setOrientationNatural() 97 | hasText(ExpectedContent.HELLO_WORLD_AMERICAN) 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/DialogWebViewTest.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp 2 | 3 | import androidx.test.ext.junit.rules.ActivityScenarioRule 4 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner 5 | import androidx.test.platform.app.InstrumentationRegistry 6 | import androidx.test.uiautomator.UiDevice 7 | import com.agoda.kakao.screen.Screen.Companion.onScreen 8 | import com.akexorcist.localizationapp.data.ExpectedContent 9 | import com.akexorcist.localizationapp.screen.DialogWebViewLanguageChooserScreen 10 | import com.akexorcist.localizationapp.screen.DialogWebViewMainScreen 11 | import com.akexorcist.localizationapp.screen.DialogWebViewSiteScreen 12 | import com.akexorcist.localizationapp.screen.MainActivityScreen 13 | import org.junit.Rule 14 | import org.junit.Test 15 | import org.junit.runner.RunWith 16 | import androidx.test.espresso.web.webdriver.Locator 17 | import org.junit.Ignore 18 | 19 | @RunWith(AndroidJUnit4ClassRunner::class) 20 | class DialogWebViewTest { 21 | @JvmField 22 | val uiDevices: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) 23 | 24 | @Rule 25 | @JvmField 26 | val rule = ActivityScenarioRule(MainActivity::class.java) 27 | 28 | @Test 29 | fun dialogAndWebView() { 30 | // Italy 31 | onScreen { 32 | buttonDialogWebView { 33 | scrollTo() 34 | click() 35 | } 36 | } 37 | onScreen { 38 | buttonChangeLanguage { click() } 39 | } 40 | onScreen { 41 | buttonItalian { click() } 42 | } 43 | onScreen { 44 | buttonShowWebsite { click() } 45 | } 46 | onScreen { 47 | webViewContent { 48 | withElement(Locator.ID, "content") { 49 | hasText(ExpectedContent.HELLO_WORLD_ITALIAN) 50 | } 51 | } 52 | buttonBack { click() } 53 | } 54 | onScreen { 55 | buttonBack { click() } 56 | } 57 | onScreen { 58 | textViewTitle { 59 | hasText(ExpectedContent.HELLO_WORLD_ITALIAN) 60 | } 61 | } 62 | 63 | // American 64 | onScreen { 65 | buttonDialogWebView { 66 | scrollTo() 67 | click() 68 | } 69 | } 70 | onScreen { 71 | buttonChangeLanguage { click() } 72 | } 73 | onScreen { 74 | buttonAmerican { click() } 75 | } 76 | onScreen { 77 | buttonShowWebsite { click() } 78 | } 79 | onScreen { 80 | webViewContent { 81 | withElement(Locator.ID, "content") { 82 | hasText(ExpectedContent.HELLO_WORLD_AMERICAN) 83 | } 84 | } 85 | buttonBack { click() } 86 | } 87 | onScreen { 88 | buttonBack { click() } 89 | } 90 | onScreen { 91 | textViewTitle { 92 | hasText(ExpectedContent.HELLO_WORLD_AMERICAN) 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/annotation/TargetApiLevel.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.annotation 2 | 3 | @Target(AnnotationTarget.FUNCTION) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class TargetApiLevel(val value: Int) 6 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/CustomActivityScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class CustomActivityScreen : Screen() { 9 | val buttonAmerican = KButton { withId(R.id.btn_american) } 10 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 11 | val buttonItalian = KButton { withId(R.id.btn_italian) } 12 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 13 | val buttonKorean = KButton { withId(R.id.btn_korean) } 14 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 15 | val buttonThai = KButton { withId(R.id.btn_thai) } 16 | val textViewContent = KTextView { withId(R.id.text_view_content) } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/DarkThemeScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class DarkThemeScreen : Screen() { 9 | val buttonAmerican = KButton { withId(R.id.btn_american) } 10 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 11 | val buttonItalian = KButton { withId(R.id.btn_italian) } 12 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 13 | val buttonKorean = KButton { withId(R.id.btn_korean) } 14 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 15 | val buttonThai = KButton { withId(R.id.btn_thai) } 16 | val textViewContent = KTextView { withId(R.id.text_view_content) } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewLanguageChooserScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.akexorcist.localizationapp.R 6 | 7 | class DialogWebViewLanguageChooserScreen : Screen() { 8 | val buttonAmerican = KButton { withId(R.id.btn_american) } 9 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 10 | val buttonItalian = KButton { withId(R.id.btn_italian) } 11 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 12 | val buttonKorean = KButton { withId(R.id.btn_korean) } 13 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 14 | val buttonThai = KButton { withId(R.id.btn_thai) } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewMainScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.akexorcist.localizationapp.R 6 | 7 | class DialogWebViewMainScreen : Screen() { 8 | val buttonBack = KButton { withId(R.id.btn_back) } 9 | val buttonShowWebsite = KButton { withId(R.id.btn_show_website) } 10 | val buttonChangeLanguage = KButton { withId(R.id.btn_change_language) } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewSiteScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.web.KWebView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class DialogWebViewSiteScreen : Screen() { 9 | val buttonBack = KButton { withId(R.id.btn_back) } 10 | val webViewContent = KWebView { withId(R.id.wv_content) } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/HiltDependencyInjectionScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class HiltDependencyInjectionScreen : Screen() { 9 | val buttonAmerican = KButton { withId(R.id.btn_american) } 10 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 11 | val buttonItalian = KButton { withId(R.id.btn_italian) } 12 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 13 | val buttonKorean = KButton { withId(R.id.btn_korean) } 14 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 15 | val buttonThai = KButton { withId(R.id.btn_thai) } 16 | val textViewContent = KTextView { withId(R.id.text_view_content) } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/ListPreferencesScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class ListPreferencesScreen : Screen() { 9 | val buttonChangeLanguage = KButton { withText(R.string.change_language) } 10 | val buttonAmerican = KButton { withText(R.string.american) } 11 | val buttonChinese = KButton { withText(R.string.chinese) } 12 | val buttonItalian = KButton { withText(R.string.italian) } 13 | val buttonJapanese = KButton { withText(R.string.japanese) } 14 | val buttonKorean = KButton { withText(R.string.korean) } 15 | val buttonPortuguese = KButton { withText(R.string.portuguese) } 16 | val buttonThai = KButton { withText(R.string.thai) } 17 | val textViewTitle: KTextView 18 | get() = KTextView { 19 | withId(android.R.id.title) 20 | withIndex(0) {} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/MainActivityScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | open class MainActivityScreen : Screen() { 9 | val textViewTitle = KTextView { withId(R.id.text_view_title) } 10 | val buttonSimpleActivity = KButton { withId(R.id.btn_simple_activity) } 11 | val buttonCustomActivity = KButton { withId(R.id.btn_custom_activity) } 12 | val buttonStackedActivity = KButton { withId(R.id.btn_stacked_activity) } 13 | val buttonSimpleFragment = KButton { withId(R.id.btn_simple_fragment) } 14 | val buttonNestedFragment = KButton { withId(R.id.btn_nested_fragment) } 15 | val buttonSimpleDialog = KButton { withId(R.id.btn_dialog_fragment) } 16 | val buttonDialogWebView = KButton { withId(R.id.btn_dialog_web_view) } 17 | val buttonViewPager = KButton { withId(R.id.btn_view_pager) } 18 | val buttonListPreferences = KButton { withId(R.id.btn_list_preferences) } 19 | val buttonDarkTheme = KButton { withId(R.id.btn_dark_theme) } 20 | val buttonHilt = KButton { withId(R.id.btn_hilt) } 21 | val buttonBroadcastReceiver = KButton { withId(R.id.btn_broadcast_receiver) } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/NestedFragmentScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class NestedFragmentScreen : Screen() { 9 | val buttonAmerican = KButton { withId(R.id.btn_american) } 10 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 11 | val buttonItalian = KButton { withId(R.id.btn_italian) } 12 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 13 | val buttonKorean = KButton { withId(R.id.btn_korean) } 14 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 15 | val buttonThai = KButton { withId(R.id.btn_thai) } 16 | val textViewContent = KTextView { withId(R.id.text_view_content) } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleActivityScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class SimpleActivityScreen : Screen() { 9 | val buttonAmerican = KButton { withId(R.id.btn_american) } 10 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 11 | val buttonItalian = KButton { withId(R.id.btn_italian) } 12 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 13 | val buttonKorean = KButton { withId(R.id.btn_korean) } 14 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 15 | val buttonThai = KButton { withId(R.id.btn_thai) } 16 | val textViewContent = KTextView { withId(R.id.text_view_content) } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleBroadcastScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class SimpleBroadcastScreen : Screen() { 9 | val buttonBack = KButton { withId(R.id.btn_back) } 10 | val buttonAmerican = KButton { withId(R.id.btn_american) } 11 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 12 | val buttonItalian = KButton { withId(R.id.btn_italian) } 13 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 14 | val buttonKorean = KButton { withId(R.id.btn_korean) } 15 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 16 | val buttonThai = KButton { withId(R.id.btn_thai) } 17 | val textViewContent = KTextView { withId(R.id.text_view_content) } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogContentScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KTextView 5 | import com.akexorcist.localizationapp.R 6 | 7 | class SimpleDialogContentScreen : Screen() { 8 | val textViewContent = KTextView { withId(R.id.text_view_content) } 9 | } 10 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogLanguageChooserScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.akexorcist.localizationapp.R 6 | 7 | class SimpleDialogLanguageChooserScreen : Screen() { 8 | val buttonAmerican = KButton { withId(R.id.btn_american) } 9 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 10 | val buttonItalian = KButton { withId(R.id.btn_italian) } 11 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 12 | val buttonKorean = KButton { withId(R.id.btn_korean) } 13 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 14 | val buttonThai = KButton { withId(R.id.btn_thai) } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogMainScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.akexorcist.localizationapp.R 6 | 7 | class SimpleDialogMainScreen : Screen() { 8 | val buttonBack = KButton { withId(R.id.btn_back) } 9 | val buttonChangeLanguage = KButton { withId(R.id.btn_change_language) } 10 | val buttonShowContent = KButton { withId(R.id.btn_show_content) } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleFragmentScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class SimpleFragmentScreen : Screen() { 9 | val buttonAmerican = KButton { withId(R.id.btn_american) } 10 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 11 | val buttonItalian = KButton { withId(R.id.btn_italian) } 12 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 13 | val buttonKorean = KButton { withId(R.id.btn_korean) } 14 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 15 | val buttonThai = KButton { withId(R.id.btn_thai) } 16 | val textViewContent = KTextView { withId(R.id.text_view_content) } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/StackedHomeScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class StackedHomeScreen : Screen() { 9 | val buttonChangeLanguage = KButton { withId(R.id.btn_change_language) } 10 | val textViewContent = KTextView { withId(R.id.text_view_content) } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/StackedLanguageChooserScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.akexorcist.localizationapp.R 6 | 7 | class StackedLanguageChooserScreen : Screen() { 8 | val buttonAmerican = KButton { withId(R.id.btn_american) } 9 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 10 | val buttonItalian = KButton { withId(R.id.btn_italian) } 11 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 12 | val buttonKorean = KButton { withId(R.id.btn_korean) } 13 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 14 | val buttonThai = KButton { withId(R.id.btn_thai) } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akexorcist/localizationapp/screen/ViewPagerScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.screen 2 | 3 | import com.agoda.kakao.screen.Screen 4 | import com.agoda.kakao.text.KButton 5 | import com.agoda.kakao.text.KTextView 6 | import com.akexorcist.localizationapp.R 7 | 8 | class ViewPagerScreen : Screen() { 9 | val buttonAmerican = KButton { withId(R.id.btn_american) } 10 | val buttonChinese = KButton { withId(R.id.btn_chinese) } 11 | val buttonItalian = KButton { withId(R.id.btn_italian) } 12 | val buttonJapanese = KButton { withId(R.id.btn_japanese) } 13 | val buttonKorean = KButton { withId(R.id.btn_korean) } 14 | val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } 15 | val buttonThai = KButton { withId(R.id.btn_thai) } 16 | val buttonPrevious = KButton { withId(R.id.btn_prev) } 17 | val buttonNext = KButton { withId(R.id.btn_next) } 18 | val textViewHello = KTextView { withId(R.id.text_view_hello) } 19 | val textViewOne = KTextView { withId(R.id.text_view_one) } 20 | val textViewTwo = KTextView { withId(R.id.text_view_two) } 21 | val textViewThree = KTextView { withId(R.id.text_view_three) } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 46 | 50 | 53 | 56 | 59 | 62 | 65 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akexorcist/Localization/37713e854d350c94f0c45e2069176a247c84db85/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import com.akexorcist.localizationactivity.ui.LocalizationActivity 6 | import com.akexorcist.localizationapp.broadcast.SimpleBroadcastActivity 7 | import com.akexorcist.localizationapp.customactivity.SimpleCustomActivity 8 | import com.akexorcist.localizationapp.darktheme.DarkThemeActivity 9 | import com.akexorcist.localizationapp.databinding.ActivityMainBinding 10 | import com.akexorcist.localizationapp.dialogwebview.DialogWebViewMainActivity 11 | import com.akexorcist.localizationapp.simpledialog.SimpleDialogMainActivity 12 | import com.akexorcist.localizationapp.hilt.HiltActivity 13 | import com.akexorcist.localizationapp.nestedfragment.NestedFragmentActivity 14 | import com.akexorcist.localizationapp.preferences.ListPreferencesActivity 15 | import com.akexorcist.localizationapp.simpleactivity.SimpleActivity 16 | import com.akexorcist.localizationapp.simplefragment.SimpleFragmentActivity 17 | import com.akexorcist.localizationapp.stackedactivity.StackedHomeActivity 18 | import com.akexorcist.localizationapp.viewpager.ViewPagerActivity 19 | 20 | class MainActivity : LocalizationActivity() { 21 | private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } 22 | 23 | public override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | setContentView(binding.root) 26 | 27 | binding.textViewTitle.setText(R.string.hello_world) 28 | binding.btnSimpleActivity.setOnClickListener { goToActivity(SimpleActivity::class.java) } 29 | binding.btnCustomActivity.setOnClickListener { goToActivity(SimpleCustomActivity::class.java) } 30 | binding.btnStackedActivity.setOnClickListener { goToActivity(StackedHomeActivity::class.java) } 31 | binding.btnSimpleFragment.setOnClickListener { goToActivity(SimpleFragmentActivity::class.java) } 32 | binding.btnNestedFragment.setOnClickListener { goToActivity(NestedFragmentActivity::class.java) } 33 | binding.btnDialogFragment.setOnClickListener { goToActivity(SimpleDialogMainActivity::class.java) } 34 | binding.btnDialogWebView.setOnClickListener { goToActivity(DialogWebViewMainActivity::class.java) } 35 | binding.btnViewPager.setOnClickListener { goToActivity(ViewPagerActivity::class.java) } 36 | binding.btnListPreferences.setOnClickListener { goToActivity(ListPreferencesActivity::class.java) } 37 | binding.btnDarkTheme.setOnClickListener { goToActivity(DarkThemeActivity::class.java) } 38 | binding.btnHilt.setOnClickListener { goToActivity(HiltActivity::class.java) } 39 | binding.btnBroadcastReceiver.setOnClickListener { goToActivity(SimpleBroadcastActivity::class.java) } 40 | } 41 | 42 | private fun goToActivity(activity: Class<*>?) { 43 | startActivity(Intent(this, activity)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp 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.webkit.WebView 8 | import com.akexorcist.localizationactivity.core.LocalizationApplicationDelegate 9 | import dagger.hilt.android.HiltAndroidApp 10 | import java.util.* 11 | 12 | @HiltAndroidApp 13 | class MainApplication : Application() { 14 | private val localizationDelegate = LocalizationApplicationDelegate() 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | localizationDelegate.onCreate(this) 19 | } 20 | 21 | override fun attachBaseContext(base: Context) { 22 | localizationDelegate.setDefaultLanguage(base, Locale.ENGLISH) 23 | super.attachBaseContext(localizationDelegate.attachBaseContext(base)) 24 | } 25 | 26 | override fun onConfigurationChanged(newConfig: Configuration) { 27 | super.onConfigurationChanged(newConfig) 28 | localizationDelegate.onConfigurationChanged(this) 29 | } 30 | 31 | override fun getApplicationContext(): Context { 32 | return localizationDelegate.getApplicationContext(super.getApplicationContext()) 33 | } 34 | 35 | override fun getResources(): Resources { 36 | return localizationDelegate.getResources(baseContext, super.getResources()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/broadcast/BroadcastEvent.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.broadcast 2 | 3 | object BroadcastEvent { 4 | // Activity -> Broadcast Receiver 5 | const val ACTION_TO_BROADCAST = "com.akexorcist.localizationapp.receiver.action.TO_BROADCAST" 6 | 7 | // Broadcast Receiver -> Activity 8 | const val ACTION_TO_ACTIVITY = "com.akexorcist.localizationapp.receiver.action.TO_ACTIVITY" 9 | const val EXTRA_CONTENT = "com.akexorcist.localizationapp.receiver.extra.CONTENT" 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/broadcast/SimpleBroadcastActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.broadcast 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.os.Bundle 8 | import com.akexorcist.localizationactivity.ui.LocalizationActivity 9 | import com.akexorcist.localizationapp.databinding.ActivitySimpleBroadcastBinding 10 | 11 | class SimpleBroadcastActivity : LocalizationActivity() { 12 | private val binding by lazy { ActivitySimpleBroadcastBinding.inflate(layoutInflater) } 13 | 14 | public override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContentView(binding.root) 17 | 18 | binding.btnBack.setOnClickListener { super.onBackPressed() } 19 | binding.layoutLanguageChooser.btnAmerican.setOnClickListener { onChangeLanguageButtonClicked("en") } 20 | binding.layoutLanguageChooser.btnChinese.setOnClickListener { onChangeLanguageButtonClicked("zh") } 21 | binding.layoutLanguageChooser.btnItalian.setOnClickListener { onChangeLanguageButtonClicked("it") } 22 | binding.layoutLanguageChooser.btnJapanese.setOnClickListener { onChangeLanguageButtonClicked("ja") } 23 | binding.layoutLanguageChooser.btnKorean.setOnClickListener { onChangeLanguageButtonClicked("ko") } 24 | binding.layoutLanguageChooser.btnPortuguese.setOnClickListener { onChangeLanguageButtonClicked("pt") } 25 | binding.layoutLanguageChooser.btnThai.setOnClickListener { onChangeLanguageButtonClicked("th") } 26 | } 27 | 28 | private fun onChangeLanguageButtonClicked(language: String) { 29 | setLanguage(language) 30 | getContentFromBroadcastReceiver() 31 | } 32 | 33 | private fun getContentFromBroadcastReceiver() { 34 | val intent = Intent(BroadcastEvent.ACTION_TO_BROADCAST).apply { 35 | `package` = packageName 36 | } 37 | sendBroadcast(intent) 38 | } 39 | 40 | override fun onStart() { 41 | super.onStart() 42 | val filter = IntentFilter(BroadcastEvent.ACTION_TO_ACTIVITY) 43 | registerReceiver(broadcastReceiver, filter) 44 | } 45 | 46 | override fun onStop() { 47 | super.onStop() 48 | unregisterReceiver(broadcastReceiver) 49 | } 50 | 51 | private val broadcastReceiver = object : BroadcastReceiver() { 52 | override fun onReceive(context: Context, intent: Intent) { 53 | val content = intent.getStringExtra(BroadcastEvent.EXTRA_CONTENT) 54 | binding.textViewContent.text = content 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/broadcast/SimpleBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.broadcast 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.akexorcist.localizationactivity.core.toLocalizedContext 7 | import com.akexorcist.localizationapp.R 8 | 9 | class SimpleBroadcastReceiver : BroadcastReceiver() { 10 | override fun onReceive(context: Context, intent: Intent) { 11 | val localizedContext = context.toLocalizedContext() 12 | val content = localizedContext.getString(R.string.sample_apple_story) 13 | val broadcastIntent = Intent(BroadcastEvent.ACTION_TO_ACTIVITY).apply { 14 | `package` = context.packageName 15 | putExtra(BroadcastEvent.EXTRA_CONTENT, content) 16 | } 17 | context.sendBroadcast(broadcastIntent) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/customactivity/CustomActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.customactivity 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.res.Resources 6 | import android.os.Bundle 7 | import com.akexorcist.localizationactivity.core.LocalizationActivityDelegate 8 | import com.akexorcist.localizationactivity.core.OnLocaleChangedListener 9 | import java.util.* 10 | 11 | open class CustomActivity : Activity(), OnLocaleChangedListener { 12 | private val localizationDelegate by lazy { 13 | LocalizationActivityDelegate(this) 14 | } 15 | 16 | public override fun onCreate(savedInstanceState: Bundle?) { 17 | localizationDelegate.addOnLocaleChangedListener(this) 18 | localizationDelegate.onCreate() 19 | super.onCreate(savedInstanceState) 20 | } 21 | 22 | public override fun onResume() { 23 | super.onResume() 24 | localizationDelegate.onResume(this) 25 | } 26 | 27 | override fun attachBaseContext(newBase: Context) { 28 | applyOverrideConfiguration(localizationDelegate.updateConfigurationLocale(newBase)) 29 | super.attachBaseContext(newBase) 30 | } 31 | 32 | override fun getApplicationContext(): Context { 33 | return localizationDelegate.getApplicationContext(super.getApplicationContext()) 34 | } 35 | 36 | override fun getResources(): Resources { 37 | return localizationDelegate.getResources(super.getResources()) 38 | } 39 | 40 | fun setLanguage(language: String?) { 41 | localizationDelegate.setLanguage(this, language!!) 42 | } 43 | 44 | fun setLanguage(locale: Locale?) { 45 | localizationDelegate.setLanguage(this, locale!!) 46 | } 47 | 48 | val currentLanguage: Locale 49 | get() = localizationDelegate.getLanguage(this) 50 | 51 | // Just override method locale change event 52 | override fun onBeforeLocaleChanged() {} 53 | override fun onAfterLocaleChanged() {} 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/customactivity/SimpleCustomActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.customactivity 2 | 3 | import android.os.Bundle 4 | import com.akexorcist.localizationapp.R 5 | import com.akexorcist.localizationapp.databinding.ActivityCustomBinding 6 | 7 | class SimpleCustomActivity : CustomActivity() { 8 | private val binding: ActivityCustomBinding by lazy { ActivityCustomBinding.inflate(layoutInflater) } 9 | 10 | companion object { 11 | private const val KEY_SCROLL_X = "scroll_x" 12 | } 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContentView(binding.root) 17 | 18 | // Activity title is not change the language automatically. 19 | setTitle(R.string.hello_world) 20 | binding.btnBack.setOnClickListener { super.onBackPressed() } 21 | binding.layoutLanguageChooser.btnAmerican.setOnClickListener { setLanguage("en") } 22 | binding.layoutLanguageChooser.btnChinese.setOnClickListener { setLanguage("zh") } 23 | binding.layoutLanguageChooser.btnItalian.setOnClickListener { setLanguage("it") } 24 | binding.layoutLanguageChooser.btnJapanese.setOnClickListener { setLanguage("ja") } 25 | binding.layoutLanguageChooser.btnKorean.setOnClickListener { setLanguage("ko") } 26 | binding.layoutLanguageChooser.btnPortuguese.setOnClickListener { setLanguage("pt") } 27 | binding.layoutLanguageChooser.btnThai.setOnClickListener { setLanguage("th") } 28 | } 29 | 30 | public override fun onSaveInstanceState(outState: Bundle) { 31 | // Save x-position of horizontal scroll view. 32 | outState.putInt(KEY_SCROLL_X, binding.layoutLanguageChooser.svLanguageChooser.scrollX) 33 | super.onSaveInstanceState(outState) 34 | } 35 | 36 | public override fun onRestoreInstanceState(savedInstanceState: Bundle) { 37 | super.onRestoreInstanceState(savedInstanceState) 38 | // Restore x-position of horizontal scroll view. 39 | binding.layoutLanguageChooser.svLanguageChooser.scrollTo( 40 | savedInstanceState.getInt(KEY_SCROLL_X), 0 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/darktheme/DarkThemeActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.darktheme 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import androidx.appcompat.app.AppCompatDelegate 6 | import androidx.fragment.app.commit 7 | import com.akexorcist.localizationactivity.ui.LocalizationActivity 8 | import com.akexorcist.localizationapp.databinding.ActivityDarkThemeBinding 9 | 10 | class DarkThemeActivity : LocalizationActivity() { 11 | private val binding: ActivityDarkThemeBinding by lazy { ActivityDarkThemeBinding.inflate(layoutInflater) } 12 | 13 | companion object { 14 | private const val KEY_SCROLL_X = "scroll_x" 15 | } 16 | 17 | public override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(binding.root) 20 | 21 | binding.btnBack.setOnClickListener { super.onBackPressed() } 22 | binding.layoutLanguageChooser.btnAmerican.setOnClickListener { setLanguage("en") } 23 | binding.layoutLanguageChooser.btnChinese.setOnClickListener { setLanguage("zh") } 24 | binding.layoutLanguageChooser.btnItalian.setOnClickListener { setLanguage("it") } 25 | binding.layoutLanguageChooser.btnJapanese.setOnClickListener { setLanguage("ja") } 26 | binding.layoutLanguageChooser.btnKorean.setOnClickListener { setLanguage("ko") } 27 | binding.layoutLanguageChooser.btnPortuguese.setOnClickListener { setLanguage("pt") } 28 | binding.layoutLanguageChooser.btnThai.setOnClickListener { setLanguage("th") } 29 | 30 | if (savedInstanceState == null) { 31 | supportFragmentManager.commit { 32 | replace(binding.layoutFragmentContainer.id, DarkThemeFragment.newInstance()) 33 | } 34 | } 35 | } 36 | 37 | public override fun onSaveInstanceState(outState: Bundle) { 38 | // Save x-position of horizontal scroll view. 39 | outState.putInt(KEY_SCROLL_X, binding.layoutLanguageChooser.svLanguageChooser.scrollX) 40 | super.onSaveInstanceState(outState) 41 | } 42 | 43 | public override fun onRestoreInstanceState(savedInstanceState: Bundle) { 44 | super.onRestoreInstanceState(savedInstanceState) 45 | // Restore x-position of horizontal scroll view. 46 | binding.layoutLanguageChooser.svLanguageChooser.scrollTo(savedInstanceState.getInt(KEY_SCROLL_X), 0) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/darktheme/DarkThemeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.darktheme 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.akexorcist.localizationapp.databinding.FragmentDarkThemeBinding 9 | import com.akexorcist.localizationapp.simplefragment.SimpleFragment 10 | 11 | class DarkThemeFragment : Fragment() { 12 | private lateinit var binding: FragmentDarkThemeBinding 13 | 14 | companion object { 15 | private const val KEY_SCROLL_Y = "scroll_y" 16 | 17 | fun newInstance(): Fragment { 18 | return DarkThemeFragment() 19 | } 20 | } 21 | 22 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 23 | binding = FragmentDarkThemeBinding.inflate(layoutInflater, container, false) 24 | return binding.root 25 | } 26 | 27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 28 | if (savedInstanceState != null) { 29 | // Restore y-position of scroll view. 30 | binding.svAppleStory.scrollTo(0, savedInstanceState.getInt(KEY_SCROLL_Y)) 31 | } 32 | } 33 | 34 | override fun onSaveInstanceState(outState: Bundle) { 35 | // Save y-position of scroll view. 36 | outState.putInt(KEY_SCROLL_Y, binding.svAppleStory.scrollY) 37 | super.onSaveInstanceState(outState) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewLanguageChooserFragment.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.dialogwebview 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.DialogFragment 8 | import com.akexorcist.localizationactivity.ui.LocalizationActivity 9 | import com.akexorcist.localizationapp.databinding.FragmentDialogWebviewLanguageChooserBinding 10 | 11 | class DialogWebViewLanguageChooserFragment : DialogFragment() { 12 | private lateinit var binding: FragmentDialogWebviewLanguageChooserBinding 13 | 14 | companion object { 15 | fun newInstance(): DialogWebViewLanguageChooserFragment = DialogWebViewLanguageChooserFragment() 16 | } 17 | 18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 19 | binding = FragmentDialogWebviewLanguageChooserBinding.inflate(layoutInflater, container, false) 20 | return binding.root 21 | } 22 | 23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 24 | super.onViewCreated(view, savedInstanceState) 25 | 26 | binding.btnAmerican.setOnClickListener { changeLanguage("en") } 27 | binding.btnChinese.setOnClickListener { changeLanguage("zh") } 28 | binding.btnItalian.setOnClickListener { changeLanguage("it") } 29 | binding.btnJapanese.setOnClickListener { changeLanguage("ja") } 30 | binding.btnKorean.setOnClickListener { changeLanguage("ko") } 31 | binding.btnPortuguese.setOnClickListener { changeLanguage("pt") } 32 | binding.btnThai.setOnClickListener { changeLanguage("th") } 33 | } 34 | 35 | private fun changeLanguage(language: String) { 36 | (requireActivity() as? LocalizationActivity)?.setLanguage(language) 37 | dismiss() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewMainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.dialogwebview 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import com.akexorcist.localizationactivity.ui.LocalizationActivity 6 | import com.akexorcist.localizationapp.databinding.ActivityDialogWebviewMainBinding 7 | 8 | class DialogWebViewMainActivity : LocalizationActivity() { 9 | private val binding: ActivityDialogWebviewMainBinding by lazy { ActivityDialogWebviewMainBinding.inflate(layoutInflater) } 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(binding.root) 14 | 15 | binding.btnBack.setOnClickListener { super.onBackPressed() } 16 | binding.btnShowWebsite.setOnClickListener { 17 | startActivity(Intent(this, DialogWebViewSiteActivity::class.java)) 18 | } 19 | binding.btnChangeLanguage.setOnClickListener { 20 | DialogWebViewLanguageChooserFragment.newInstance().show(supportFragmentManager, null) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewSiteActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.dialogwebview 2 | 3 | import android.annotation.SuppressLint 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import com.akexorcist.localizationactivity.ui.LocalizationActivity 7 | import com.akexorcist.localizationapp.R 8 | import com.akexorcist.localizationapp.databinding.ActivityDialogWebviewSiteBinding 9 | 10 | class DialogWebViewSiteActivity : LocalizationActivity() { 11 | private val binding: ActivityDialogWebviewSiteBinding by lazy { ActivityDialogWebviewSiteBinding.inflate(layoutInflater) } 12 | 13 | @SuppressLint("SetJavaScriptEnabled") 14 | public override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContentView(binding.root) 17 | binding.btnBack.setOnClickListener { super.onBackPressed() } 18 | binding.wvContent.run { 19 | settings.javaScriptEnabled = true 20 | setBackgroundColor(Color.TRANSPARENT) 21 | loadData(htmlData, "text/html", "UTF-8") 22 | } 23 | } 24 | 25 | private val htmlData: String 26 | get() = """ 27 | 28 | 29 | 34 | 35 | 36 |

37 | ${getString(R.string.hello_world)} 38 |

39 | 40 | 41 | """.trimIndent() 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/hilt/HiltActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.hilt 2 | 3 | import android.os.Bundle 4 | import com.akexorcist.localizationactivity.ui.LocalizationActivity 5 | import com.akexorcist.localizationapp.databinding.ActivityHiltBinding 6 | import dagger.hilt.android.AndroidEntryPoint 7 | import javax.inject.Inject 8 | 9 | @AndroidEntryPoint 10 | class HiltActivity : LocalizationActivity() { 11 | private val binding: ActivityHiltBinding by lazy { ActivityHiltBinding.inflate(layoutInflater) } 12 | 13 | @Inject 14 | lateinit var storyProvider: StoryProvider 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(binding.root) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/akexorcist/localizationapp/hilt/HiltFragmentWithViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.akexorcist.localizationapp.hilt 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import android.widget.Button 6 | import android.widget.ImageButton 7 | import android.widget.ScrollView 8 | import android.widget.TextView 9 | import androidx.fragment.app.Fragment 10 | import androidx.fragment.app.viewModels 11 | import com.akexorcist.localizationactivity.ui.LocalizationActivity 12 | import com.akexorcist.localizationapp.R 13 | import dagger.hilt.android.AndroidEntryPoint 14 | 15 | 16 | @AndroidEntryPoint 17 | class HiltFragmentWithViewModel : Fragment(R.layout.fragment_hilt) { 18 | 19 | private val hiltViewModel: HiltViewModel by viewModels() 20 | 21 | private var languageChooserScrollView: ScrollView? = null 22 | 23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 24 | super.onViewCreated(view, savedInstanceState) 25 | setupUi(view) 26 | } 27 | 28 | private fun setupUi(view: View) { 29 | languageChooserScrollView = view.findViewById(R.id.sv_language_chooser) 30 | 31 | view.findViewById(R.id.text_view_content).text = hiltViewModel.storyProvider.getAppleStory() 32 | view.findViewById