From 5a5478cb9d3868c18a3d8af081c290e0f2f15000 Mon Sep 17 00:00:00 2001 From: Velord Date: Wed, 19 Aug 2026 23:20:25 +0300 Subject: [PATCH] Add: Firebase Remote Config localization --- .github/workflows/localization-validation.yml | 146 ++++++++++++ .gitignore | 4 + .idea/gradle.xml | 1 + app/android/build.gradle.kts | 7 +- app/android/src/main/AndroidManifest.xml | 2 +- .../com/velord/composescreenexample/App.kt | 10 + app/desktop/build.gradle.kts | 2 + .../composescreenexample/desktop/Main.kt | 25 +- .../task/GenerateAppStringResourcesTask.kt | 146 ++++++++++++ core/core-navigation/build.gradle.kts | 2 + .../navigation/voyager/SharedScreenVoyager.kt | 4 +- core/core-resource/build.gradle.kts | 29 ++- .../composeResources/files/localization.json | 194 ++++++++++++++++ .../composeResources/values/strings.xml | 152 ------------ .../velord/core/resource/StringResource.kt | 88 +++++++ .../velord/core/ui/dialog/CheckPermission.kt | 68 +++--- .../CheckCameraAndAudioRecordPermission.kt | 34 +-- .../ui/compose/preview/PreviewLocalization.kt | 20 ++ .../com/velord/core/ui/theme/AppThemeHost.kt | 12 +- .../data/appstate/AppStateDataSource.kt | 4 + .../data/datastore/DataStoreDataSource.kt | 8 + data/gateway/build.gradle.kts | 3 + .../kotlin/com/velord/data/gateway/DI.kt | 2 + .../localization/LocalizationGateway.kt | 97 ++++++++ .../setting/LanguagePreferenceGateway.kt | 22 ++ data/localization/build.gradle.kts | 58 +++++ .../data/localization/LocalizationPlatform.kt | 17 ++ .../kotlin/com/velord/data/localization/DI.kt | 10 + .../localization/LocalizationDataSource.kt | 163 +++++++++++++ .../data/localization/LocalizationPlatform.kt | 6 + .../LocalizationDataSourceTest.kt | 143 ++++++++++++ .../data/localization/LocalizationPlatform.kt | 41 ++++ .../setting/GetLanguagePreferenceUC.kt | 6 + .../usecase/setting/GetLocalizationStateUC.kt | 6 + .../setting/InitializeLocalizationUC.kt | 3 + .../setting/SetLanguagePreferenceUC.kt | 5 + gradle.properties | 2 + gradle/libs.versions.toml | 4 + gradle/wrapper/gradle-wrapper.properties | 3 +- infrastructure/config/build.gradle.kts | 21 ++ .../config/BuildConfigResolver.kt | 4 + .../config/GeneratedBuildConfigResolver.kt | 7 + infrastructure/di/build.gradle.kts | 1 + .../velord/infrastructure/di/UseCaseModule.kt | 17 ++ .../infrastructure/di/ViewModelModule.kt | 2 + .../BottomNavigationGraphDestinations.kt | 16 +- .../graph/BottomNavigationGraphVanilla.kt | 9 +- .../compose/vanilla/graph/MainGraphVanilla.kt | 11 +- .../inDevelopment/InDevelopmentFragment.kt | 7 +- .../jetpack/inDevelopment/TestFragment.kt | 5 +- .../infrastructure/navigation/TestScreen.kt | 13 +- .../nav3/graph/BottomNavigationGraphNav3.kt | 9 +- .../compose/nav3/graph/MainGraphNav3.kt | 7 +- .../nav3/navigator/BottomNavigatorNav3.kt | 15 +- .../voyager/screen/SettingsVoyagerScreen.kt | 9 +- .../voyager/screen/TestVoyagerScreen.kt | 4 +- ...hemeCapabilities.kt => ThemeCapability.kt} | 6 +- .../velord/model/localization/LanguageCode.kt | 12 + .../localization/LocalizationDocument.kt | 51 ++++ .../model/localization/LocalizationState.kt | 17 ++ .../com/velord/model/setting/AppSetting.kt | 6 +- .../velord/model/setting/AppThemeConfig.kt | 8 +- .../model/setting/LanguagePreference.kt | 20 ++ .../velord/model/setting/ThemeCapabilities.kt | 11 - .../velord/model/setting/ThemeCapability.kt | 11 + .../velord/model/setting/ThemeCapabilities.kt | 6 - .../velord/model/setting/ThemeCapability.kt | 6 + scripts/firebase/publish-localization.ps1 | 219 ++++++++++++++++++ settings.gradle.kts | 1 + ui/feature-bottomnavigation/build.gradle.kts | 1 + .../screen/BottomNavigationJetpackScreen.kt | 7 +- .../viewmodel/BottomNavigationJetpackVM.kt | 19 +- .../navigation/BottomNavigationItem.kt | 15 +- .../screen/component/Content.kt | 2 +- .../screen/component/ScreenSetup.kt | 7 +- .../voyager/VoyagerBottomNavigationTab.kt | 19 +- .../viewmodel/BottomNavigationVM.kt | 16 +- .../BottomNavigationBackBehaviorTest.kt | 2 +- .../viewmodel/BottomNavigationTestFixture.kt | 5 + ui/feature-camerarecording/build.gradle.kts | 1 + .../CameraRecordingFragment.kt | 1 + .../camerarecording/component/Content.kt | 7 +- .../component/PermissionInfo.kt | 14 +- .../component/button/OpenVideoFolderButton.kt | 7 +- .../button/StartStopRecordingButton.kt | 10 +- .../switcher/audio/CameraAudioLabel.kt | 7 +- .../switcher/audio/CameraAudioSwitch.kt | 7 +- .../switcher/audio/CameraAudioSwitcher.kt | 10 +- .../switcher/lens/CameraLensLabel.kt | 7 +- .../switcher/lens/CameraLensLabelRow.kt | 10 +- .../switcher/lens/CameraLensSwitchSurface.kt | 7 +- .../videoQuality/CameraVideoQualityChooser.kt | 7 +- .../CameraVideoQualityMenuItem.kt | 7 +- .../videoQuality/CameraVideoQualityTrigger.kt | 7 +- .../util/CameraVideoQualityLabel.kt | 30 +-- .../viewModel/CameraRecordingVM.kt | 10 +- .../feature/demo/dialog/DialogDemoScreen.kt | 13 +- .../feature/demo/dialog/component/Dialog.kt | 22 +- .../hintphonenumber/HintPhoneNumberScreen.kt | 16 +- ui/feature-demo-shape/build.gradle.kts | 1 + .../ui/feature/demo/shape/ShapeDemoScreen.kt | 8 +- ui/feature-demo/build.gradle.kts | 1 + .../com/velord/ui/feature/demo/DemoScreen.kt | 28 +-- .../com/velord/ui/feature/demo/DemoVM.kt | 10 +- .../flowsummator/FlowSummatorScreen.kt | 18 +- .../ui/feature/movie/component/MovieAction.kt | 10 +- .../movie/component/MovieBottomSheet.kt | 13 +- .../ui/feature/movie/component/MovieHeader.kt | 2 +- .../ui/feature/movie/component/MoviePager.kt | 7 +- .../movie/model/MovieFilterOptionUI.kt | 12 +- .../ui/feature/movie/model/MoviePage.kt | 12 +- .../feature/movie/model/MovieSortOptionUI.kt | 12 +- ui/feature-setting/build.gradle.kts | 2 +- .../ui/feature/setting/SettingFragment.kt | 7 +- .../ui/feature/setting/SettingScreen.kt | 187 +++++++++++---- ui/feature-splash/build.gradle.kts | 1 - ui/sharedviewmodel/build.gradle.kts | 5 +- .../velord/ui/sharedviewmodel/LanguageVM.kt | 73 ++++++ .../ui/sharedviewmodel/LocalizationVM.kt | 17 ++ ui/widget-counter/build.gradle.kts | 9 + .../velord/ui/widget/counter/CounterWidget.kt | 18 +- .../ui/widget/counter/CounterWidgetScreen.kt | 16 +- ui/widget-refreshableimage/build.gradle.kts | 9 +- .../RefreshableImageWidget.kt | 17 +- .../RefreshableImageWidgetScreen.kt | 16 +- 125 files changed, 2276 insertions(+), 608 deletions(-) create mode 100644 .github/workflows/localization-validation.yml create mode 100644 build-logic/src/main/kotlin/com/velord/buildlogic/task/GenerateAppStringResourcesTask.kt create mode 100644 core/core-resource/src/commonMain/composeResources/files/localization.json delete mode 100644 core/core-resource/src/commonMain/composeResources/values/strings.xml create mode 100644 core/core-resource/src/commonMain/kotlin/com/velord/core/resource/StringResource.kt create mode 100644 core/core-ui/src/commonMain/kotlin/com/velord/core/ui/compose/preview/PreviewLocalization.kt create mode 100644 data/gateway/src/commonMain/kotlin/com/velord/data/gateway/localization/LocalizationGateway.kt create mode 100644 data/gateway/src/commonMain/kotlin/com/velord/data/gateway/setting/LanguagePreferenceGateway.kt create mode 100644 data/localization/build.gradle.kts create mode 100644 data/localization/src/androidMain/kotlin/com/velord/data/localization/LocalizationPlatform.kt create mode 100644 data/localization/src/commonMain/kotlin/com/velord/data/localization/DI.kt create mode 100644 data/localization/src/commonMain/kotlin/com/velord/data/localization/LocalizationDataSource.kt create mode 100644 data/localization/src/commonMain/kotlin/com/velord/data/localization/LocalizationPlatform.kt create mode 100644 data/localization/src/commonTest/kotlin/com/velord/data/localization/LocalizationDataSourceTest.kt create mode 100644 data/localization/src/desktopMain/kotlin/com/velord/data/localization/LocalizationPlatform.kt create mode 100644 domain/usecase-setting/src/commonMain/kotlin/com/velord/usecase/setting/GetLanguagePreferenceUC.kt create mode 100644 domain/usecase-setting/src/commonMain/kotlin/com/velord/usecase/setting/GetLocalizationStateUC.kt create mode 100644 domain/usecase-setting/src/commonMain/kotlin/com/velord/usecase/setting/InitializeLocalizationUC.kt create mode 100644 domain/usecase-setting/src/commonMain/kotlin/com/velord/usecase/setting/SetLanguagePreferenceUC.kt rename model/src/androidMain/kotlin/com/velord/model/setting/{ThemeCapabilities.kt => ThemeCapability.kt} (66%) create mode 100644 model/src/commonMain/kotlin/com/velord/model/localization/LanguageCode.kt create mode 100644 model/src/commonMain/kotlin/com/velord/model/localization/LocalizationDocument.kt create mode 100644 model/src/commonMain/kotlin/com/velord/model/localization/LocalizationState.kt create mode 100644 model/src/commonMain/kotlin/com/velord/model/setting/LanguagePreference.kt delete mode 100644 model/src/commonMain/kotlin/com/velord/model/setting/ThemeCapabilities.kt create mode 100644 model/src/commonMain/kotlin/com/velord/model/setting/ThemeCapability.kt delete mode 100644 model/src/desktopMain/kotlin/com/velord/model/setting/ThemeCapabilities.kt create mode 100644 model/src/desktopMain/kotlin/com/velord/model/setting/ThemeCapability.kt create mode 100644 scripts/firebase/publish-localization.ps1 create mode 100644 ui/sharedviewmodel/src/commonMain/kotlin/com/velord/ui/sharedviewmodel/LanguageVM.kt create mode 100644 ui/sharedviewmodel/src/commonMain/kotlin/com/velord/ui/sharedviewmodel/LocalizationVM.kt diff --git a/.github/workflows/localization-validation.yml b/.github/workflows/localization-validation.yml new file mode 100644 index 00000000..42d8d3e2 --- /dev/null +++ b/.github/workflows/localization-validation.yml @@ -0,0 +1,146 @@ +name: Localization Validation + +on: + push: + branches: + - feature/firebase-remote-localization + pull_request: + branches: + - develop + workflow_dispatch: + +concurrency: + group: localization-validation-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + runs-on: ubuntu-latest + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '21' + + - name: Make Gradle wrapper executable + run: chmod +x gradlew + + - name: Verify localization architecture + shell: bash + run: | + if grep -R --line-number --include='*.kt' 'Res\.string' app core data domain infrastructure model ui; then + echo 'Found remaining Compose Res.string usage.' + exit 1 + fi + if grep -R --line-number --include='*.kt' 'org\.jetbrains\.compose\.resources\.stringResource' app core data domain infrastructure model ui; then + echo 'Found remaining JetBrains Compose stringResource import.' + exit 1 + fi + if grep -R --line-number --include='*.kt' 'com\.google\.firebase' data/localization; then + echo 'Localization data code must use Firebase-KMP-Kit.' + exit 1 + fi + if ! grep -R --line-number --include='*.kt' 'com\.firebasekit\.remoteconfig' data/localization/src/commonMain; then + echo 'Firebase-KMP-Kit Remote Config must be used from commonMain.' + exit 1 + fi + if grep -R --line-number --include='*.kt' 'dev\.gitlive\.firebase' data/localization; then + echo 'Found obsolete GitLive localization integration.' + exit 1 + fi + if [ -d data/firebase ]; then + echo 'Obsolete data/firebase module still exists.' + exit 1 + fi + if find data/localization/src -type f -name '*.kt' | grep -v '/com/velord/data/localization/' | grep -q .; then + echo 'Found Kotlin source in data/localization outside com.velord.data.localization.' + find data/localization/src -type f -name '*.kt' | grep -v '/com/velord/data/localization/' + exit 1 + fi + if find core/core-resource/src/androidMain/res -path '*/values-*/strings.xml' | grep -q .; then + echo 'Translated Android strings.xml files are forbidden; localization.json is canonical.' + find core/core-resource/src/androidMain/res -path '*/values-*/strings.xml' + exit 1 + fi + + - name: Validate localization publishing script + shell: pwsh + run: ./scripts/firebase/publish-localization.ps1 -ValidateOnly + + - name: Verify QA reuses Develop Firebase client + shell: bash + run: | + trap 'rm -f app/android/google-services.json' EXIT + cat > app/android/google-services.json <<'JSON' + { + "project_info": { + "project_number": "1", + "project_id": "firebase-mapping-test" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:1:android:develop-test", + "android_client_info": { + "package_name": "com.velord.composescreenexample.develop" + } + }, + "api_key": [ + { + "current_key": "test-api-key" + } + ] + } + ] + } + JSON + ./gradlew :app:android:processQaDebugGoogleServices --stacktrace + if [ -e app/android/src/qa/google-services.json ]; then + echo 'QA must not generate or rewrite a Firebase client configuration.' + exit 1 + fi + + - name: Compile and test localization + run: >- + ./gradlew + :data:localization:desktopTest + :infrastructure:di:desktopTest + :app:desktop:compileKotlinDesktop + :app:android:processDevelopDebugMainManifest + :app:android:compileDevelopDebugKotlin + -x :app:android:processDevelopDebugGoogleServices + --stacktrace + + - name: Compile QA + run: >- + ./gradlew + :app:android:compileQaDebugKotlin + -x :app:android:processQaDebugGoogleServices + --stacktrace + + - name: Compile Stage + run: >- + ./gradlew + :app:android:compileStageDebugKotlin + -x :app:android:processStageDebugGoogleServices + --stacktrace + + - name: Compile Production + run: >- + ./gradlew + :app:android:compileProductionDebugKotlin + -x :app:android:processProductionDebugGoogleServices + --stacktrace + + - name: Run project quality checks + run: >- + ./gradlew + :infrastructure:konsist:test + detekt + --stacktrace diff --git a/.gitignore b/.gitignore index 8dcf1228..c6bc753a 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,7 @@ CLAUDE.local.md .claude/ .claudeignore .mcp.json + +# Desktop runtime cache +app/desktop/cache/ + diff --git a/.idea/gradle.xml b/.idea/gradle.xml index d9fce01a..a0ddb190 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -34,6 +34,7 @@