Skip to content

domain: add AppLanguage model and AppLanguages registry#435

Merged
rainxchzed merged 12 commits intomainfrom
feat-change-language
Apr 20, 2026
Merged

domain: add AppLanguage model and AppLanguages registry#435
rainxchzed merged 12 commits intomainfrom
feat-change-language

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented Apr 20, 2026

  • Introduce AppLanguage data class to represent user-selectable UI languages with IETF BCP 47 tags and native-script display names.
  • Create AppLanguages registry containing the list of currently supported languages (English, Arabic, Bengali, Spanish, French, Hindi, Italian, Japanese, Korean, Polish, Russian, Turkish, and Simplified Chinese).
  • Add findByTag utility to retrieve language metadata by its tag.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added language selection to Settings—users can now choose from 13 supported languages (Arabic, Bengali, Spanish, French, Hindi, Italian, Japanese, Korean, Polish, Russian, Turkish, Chinese, and English) or follow system locale.
    • Language preference persists across sessions.
    • Desktop users can restart the app after language changes apply.
  • Chores

    • Enhanced desktop crash logging and diagnostics.

- Introduce `AppLanguage` data class to represent user-selectable UI languages with IETF BCP 47 tags and native-script display names.
- Create `AppLanguages` registry containing the list of currently supported languages (English, Arabic, Bengali, Spanish, French, Hindi, Italian, Japanese, Korean, Polish, Russian, Turkish, and Simplified Chinese).
- Add `findByTag` utility to retrieve language metadata by its tag.
- Create a new `Language.kt` component in the tweaks presentation module.
- Implement `languageSection` for `LazyListScope` to display language-related settings.
- Add `LanguagePickerCard` and `LanguageDropdown` components to allow users to switch between supported app languages or follow the system default.
- Use `AppLanguages` model to populate the dropdown with native-script labels and selection indicators.
- Add "LANGUAGE" section header.
- Add strings for language picker title, description, and system default option.
- Include introductory text explaining the immediate application of language overrides.
- Introduce a new "LANGUAGE" section and related strings to support in-app language switching.
- Add strings for language picker title, description, and an option to follow the system settings.
- Include an introductory note explaining that language changes apply immediately without a restart.
- Clarify that language settings affect the UI but not content fetched from GitHub.
- Provide translations for Turkish, Italian, Simplified Chinese, Bengali, French, Russian, Japanese, Hindi, Korean, Spanish, and Polish.
- Add `getAppLanguage` and `setAppLanguage` to `TweaksRepository` and its implementation using DataStore.
- Update `TweaksState` to include `selectedAppLanguage` as a BCP 47 tag.
- Implement `OnAppLanguageSelected` action in `TweaksViewModel` to persist user language choice.
- Add `OnAppLanguageChangeRequiresRestart` event to handle platforms where in-place language switching is not supported.
- Ensure empty or blank language tags are treated as "unset" to follow the system locale.
- Add `setActiveLanguageTag` to `LocalizationManager` to allow overriding the process-wide JVM `Locale.getDefault()`.
- Implement `setActiveLanguageTag` in `AndroidLocalizationManager` and `DesktopLocalizationManager`, including logic to restore the system default locale.
- Update `MainActivity` on Android to:
    - Apply the persisted language setting via `runBlocking` before `super.onCreate` to prevent locale flickering on startup.
    - Observe runtime language changes from `TweaksRepository` and trigger `recreate()` to ensure all strings and `rememberSaveable` state are correctly updated.
- Add a new `languageSection` to the `SettingsSection` UI component.
- Add `language_restart_required` and `language_restart_action` strings for language change notifications.
- Handle `OnAppLanguageChangeRequiresRestart` event in `TweaksRoot` by showing a snackbar that triggers a JVM exit on desktop platforms.
- Update `Language` picker UI to use a tinted `surface` background for better visibility against its container.
- Set the language dropdown menu background to `surfaceContainerHigh` to provide better visual contrast against the parent card.
- Add `language_restart_required` and `language_restart_action` strings for language change notifications.
- Handle `OnAppLanguageChangeRequiresRestart` event in `TweaksRoot` by showing a snackbar that triggers a JVM exit on desktop platforms.
- Update `Language` picker UI to use a tinted `surface` background for better visibility against its container.
- Set the language dropdown menu background to `surfaceContainerHigh` to provide better visual contrast against the parent card.
…on Desktop

- Introduce `restartAppAfterLanguageChange` expect/actual function to handle platform-specific restart logic.
- Implement JVM-specific logic using `ProcessHandle` and `ProcessBuilder` to attempt a fresh relaunch of the app with original arguments before exiting.
- Add a no-op implementation for Android, where language changes are already handled via `Activity.recreate()`.
- Update `TweaksRoot` to invoke the new restart logic when the user performs the snackbar action.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 88b93be5-5a0c-4dd5-89d6-069207b0e5fe

📥 Commits

Reviewing files that changed from the base of the PR and between ad81620 and b362dc2.

📒 Files selected for processing (2)
  • composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt
  • composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt

Walkthrough

This PR implements runtime app language selection and localization. Users can override the UI language in Tweaks settings, with platform-specific restart handling for Android and JVM. Language preferences persist via TweaksRepository, LocalizationManager implementations apply selected languages, and localized strings for language UI are added across 13 languages.

Changes

Cohort / File(s) Summary
App Startup & Locale Initialization
composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt, composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt
Read persisted app language synchronously during startup, apply it via LocalizationManager before content initialization, and listen for runtime changes to trigger UI updates/restart.
LocalizationManager Interface & Implementations
core/data/src/commonMain/kotlin/.../LocalizationManager.kt, core/data/src/androidMain/kotlin/.../AndroidLocalizationManager.kt, core/data/src/jvmMain/kotlin/.../DesktopLocalizationManager.kt
Add setActiveLanguageTag(tag: String?) method to apply locale changes and restore system default when tag is null/empty.
Data Layer: Language Persistence
core/data/src/commonMain/kotlin/.../TweaksRepository.kt, core/data/src/commonMain/kotlin/.../TweaksRepositoryImpl.kt
Extend repository interface and implementation with getAppLanguage(): Flow<String?> and setAppLanguage(tag: String?) to persist language preference in DataStore.
Domain Model
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/AppLanguage.kt
Introduce AppLanguage data class with tag and displayName, plus AppLanguages singleton registry providing lookup utilities (findByTag, containsTag).
Tweaks Presentation State & Actions
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksState.kt, feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksAction.kt, feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksEvent.kt
Add selectedAppLanguage state property, OnAppLanguageSelected action, and OnAppLanguageChangeRequiresRestart event to support language selection flow.
Tweaks ViewModel & Integration
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt, feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksRoot.kt
Initialize language state from repository, handle language selection action with persistence, emit restart event (non-Android), and show snackbar with restart action.
Language Picker UI Component
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Language.kt, feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/SettingsSection.kt
Implement language section with dropdown picker showing available languages and "follow system" option; integrate into Settings UI.
Platform-Specific Restart Handling
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/RestartApp.kt, feature/tweaks/presentation/src/androidMain/kotlin/zed/rainxch/tweaks/presentation/RestartApp.android.kt, feature/tweaks/presentation/src/jvmMain/kotlin/zed/rainxch/tweaks/presentation/RestartApp.jvm.kt
Establish expect/actual pattern: Android provides no-op, JVM attempts process relaunch via ProcessBuilder with original command-line arguments.
Localized String Resources
core/presentation/src/commonMain/composeResources/values*/strings*.xml
Add 7 language-related string keys (section_language, language_intro, language_picker_title, language_picker_description, language_follow_system, language_restart_required, language_restart_action) across 13 languages (default, Arabic, Bengali, Spanish, French, Hindi, Italian, Japanese, Korean, Polish, Russian, Turkish, Simplified Chinese).
Crash Reporting Infrastructure
composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/CrashReporter.kt
Add JVM crash reporter with session logging, automatic log rotation, uncaught exception handler, and platform-aware log directory resolution.
Documentation
CLAUDE.md
Update supported language count from 11 to 13 in module overview table.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Language Picker UI
    participant VM as TweaksViewModel
    participant Repo as TweaksRepository
    participant LM as LocalizationManager
    participant App as MainActivity/DesktopApp

    User->>UI: Select language
    UI->>VM: OnAppLanguageSelected(tag)
    VM->>Repo: setAppLanguage(tag)
    Repo->>Repo: Persist to DataStore
    VM->>VM: Update selectedAppLanguage state
    VM->>VM: Emit OnAppLanguageChangeRequiresRestart (non-Android)
    
    alt Desktop
        VM->>App: Show restart snackbar
        User->>App: Tap restart action
        App->>App: restartAppAfterLanguageChange()
        App->>App: ProcessBuilder relaunch + exitProcess
    else Android
        VM->>Repo: Observe getAppLanguage() changes
        Repo->>LM: setActiveLanguageTag(tag)
        LM->>LM: Locale.setDefault(newLocale)
        App->>App: recreate()
        App->>App: Recompose with new Locale
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hops with joy through languages galore,
Thirteen tongues now grace the door,
From Japanese to Turkish speech,
The global users we can reach,
Restart button, language flow,
Watch the world in colors glow! 🌍✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the primary change: introduction of AppLanguage data class and AppLanguages registry in the domain layer, which is confirmed by the file summaries.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-change-language

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt (1)

754-769: Consider short-circuiting when selected tag equals the current persisted value.

If the user taps the already-active language, setAppLanguage will rewrite the same tag and (on Desktop) OnAppLanguageChangeRequiresRestart will still fire, producing a spurious "restart to apply" snackbar for a no-op change. Minor UX nit — guard with a comparison against _state.value.selectedAppLanguage before persisting/emitting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt`
around lines 754 - 769, When handling TweaksAction.OnAppLanguageSelected inside
the viewModelScope.launch, short-circuit if the tapped tag equals the current
persisted selection to avoid rewriting the same value and emitting a spurious
restart prompt: compare action.tag against _state.value.selectedAppLanguage and
only call tweaksRepository.setAppLanguage(action.tag) and
_events.send(TweaksEvent.OnAppLanguageChangeRequiresRestart) (when getPlatform()
!= Platform.ANDROID) if they differ; keep the existing platform check and use
the same symbols (TweaksAction.OnAppLanguageSelected,
tweaksRepository.setAppLanguage, _state.value.selectedAppLanguage, _events.send,
TweaksEvent.OnAppLanguageChangeRequiresRestart, getPlatform, Platform.ANDROID,
viewModelScope.launch).
core/data/src/commonMain/kotlin/zed/rainxch/core/data/services/LocalizationManager.kt (1)

14-26: Interface extension looks clean.

The KDoc clearly spells out the null/blank-restore semantics and the composition-side ordering requirement. Matches the platform actuals in AndroidLocalizationManager and DesktopLocalizationManager.

Note: the existing guideline "Localization must support 11 languages" is now superseded in practice — AppLanguages registers 13. Consider updating the CLAUDE.md guideline to match so future reviews aren't confused.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/services/LocalizationManager.kt`
around lines 14 - 26, Review notes approve the interface but request updating
the project guideline to match runtime reality: change the CLAUDE.md
language-support note from "11 languages" to reflect the 13 languages registered
by AppLanguages. Locate the AppLanguages registration to confirm the exact list,
then update the CLAUDE.md text and any examples to reference the 13 supported
languages and, if present, adjust any enumerations or counts that reference "11"
so they match the AppLanguages contents; no code changes to setActiveLanguageTag
are required.
core/presentation/src/commonMain/composeResources/values-it/strings-it.xml (1)

116-122: Minor: inconsistent casing within the language section.

Section headers in the Italian file are uppercase (ASPETTO, INFORMAZIONI, RETE) and section_language correctly uses LINGUA. However language_picker_title ("Lingua dell'app") and language_intro start with lowercase "Sovrascrivi" — that's consistent with other locales here, just double-check that the picker title matches the sentence-case convention used by other picker titles in this file (e.g., compare with theme_color = "Colore del Tema" which uses title case). Not a blocker.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/presentation/src/commonMain/composeResources/values-it/strings-it.xml`
around lines 116 - 122, The Italian strings have inconsistent casing: update the
values for language_picker_title and language_intro to match the existing
title-case convention used by other picker labels (see theme_color / "Colore del
Tema") and ensure section_language remains "LINGUA"; specifically change the
first letter/casing in language_picker_title and the initial word of
language_intro so both use title-case/initial-capitalization consistent with
theme_color and other picker titles (referencing the string names
language_picker_title, language_intro, theme_color, and section_language to
locate the entries).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt`:
- Around line 43-52: Bound the startup preference read so a stalled or failing
DataStore doesn't block before super.onCreate(): wrap the call inside
runBlocking with a short timeout and safe fallback and handle exceptions; e.g.
call tweaksRepository.getAppLanguage().first() inside a
withTimeout/withTimeoutOrNull and/or try/catch, fall back to a default language
tag (e.g. system default or a configured default) if the read times out or
throws, then call localizationManager.setActiveLanguageTag(tag) with that safe
tag; ensure super.onCreate() still runs even when the preference read fails.

In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt`:
- Around line 15-16: The startup blocks on runBlocking {
getAppLanguage().first() } which can hang if DataStore stalls; wrap the
preference read in a timeout using withTimeoutOrNull and fall back to a safe
default. Replace the direct runBlocking call that invokes
getAppLanguage().first() with runBlocking { withTimeoutOrNull(<timeoutMs>) {
getAppLanguage().first() } ?: <defaultLanguage> }, add the
kotlinx.coroutines.withTimeoutOrNull import, and ensure the code uses the same
timeout constant or value pattern used elsewhere (e.g.,
SearchRepositoryImpl/HomeRepositoryImpl) and a known default language symbol so
deep-linking and window creation proceed if the read fails.

In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt`:
- Around line 214-230: The repository is currently treating only blank
app-language values as unset, but it should also reject unsupported tags; update
getAppLanguage() and setAppLanguage(tag: String?) to validate against the known
AppLanguages set (use the AppLanguages lookup/enum) so unsupported tags are not
emitted or persisted: in getAppLanguage() change the map to return
prefs[APP_LANGUAGE_KEY]?.takeIf { it.isNotBlank() &&
AppLanguages.containsTag(it.trim()) } (or equivalent lookup), and in
setAppLanguage() normalize the tag then remove APP_LANGUAGE_KEY when the
normalized value is empty or not a valid AppLanguages tag, otherwise persist the
normalized value.

In `@core/presentation/src/commonMain/composeResources/values-es/strings-es.xml`:
- Around line 99-103: The string language_intro currently promises "Se aplica al
instante — no requiere reinicio" which conflicts with language_restart_required
shown elsewhere; update language_intro to a platform-neutral phrase that does
not assert "no restart" (e.g., remove the "no requiere reinicio" clause or
replace it with a neutral "Los cambios pueden requerir reinicio según la
plataforma") so it no longer contradicts language_restart_required; edit the
resource identified by name="language_intro" and ensure consistency with
name="language_restart_required" and name="language_picker_description".

In
`@core/presentation/src/commonMain/composeResources/values-zh-rCN/strings-zh-rCN.xml`:
- Around line 100-106: The two strings conflict: language_intro currently
promises "即时生效,无需重启。" while language_restart_required asks the user to restart;
update language_intro (and equivalent keys in other locale files: values/,
values-ru, values-pl, values-fr, values-zh-rCN, etc.) to a platform-aware or
softer sentence such as "大多数设备会立即生效;桌面端可能需要重启。" or create platform-specific
variants and use them where appropriate so language_intro and
language_restart_required no longer contradict each other; ensure you edit the
string named language_intro (and its locale counterparts) and keep
language_restart_required unchanged unless making matching wording adjustments.

In `@core/presentation/src/commonMain/composeResources/values/strings.xml`:
- Around line 120-124: Update the intro string to avoid asserting “no restart
needed”: change the value of language_intro to neutral wording that does not
promise immediate application across platforms (e.g., mention that changes may
apply immediately or may require restart), ensure language_restart_required
remains accurate, and propagate equivalent neutral translations in all localized
resource files so the desktop restart flow and mobile immediate-apply flow are
consistent; check strings language_picker_title, language_picker_description and
language_follow_system for any locale-specific phrasing that might contradict
the neutral intro and update those localized copies as needed.

---

Nitpick comments:
In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/services/LocalizationManager.kt`:
- Around line 14-26: Review notes approve the interface but request updating the
project guideline to match runtime reality: change the CLAUDE.md
language-support note from "11 languages" to reflect the 13 languages registered
by AppLanguages. Locate the AppLanguages registration to confirm the exact list,
then update the CLAUDE.md text and any examples to reference the 13 supported
languages and, if present, adjust any enumerations or counts that reference "11"
so they match the AppLanguages contents; no code changes to setActiveLanguageTag
are required.

In `@core/presentation/src/commonMain/composeResources/values-it/strings-it.xml`:
- Around line 116-122: The Italian strings have inconsistent casing: update the
values for language_picker_title and language_intro to match the existing
title-case convention used by other picker labels (see theme_color / "Colore del
Tema") and ensure section_language remains "LINGUA"; specifically change the
first letter/casing in language_picker_title and the initial word of
language_intro so both use title-case/initial-capitalization consistent with
theme_color and other picker titles (referencing the string names
language_picker_title, language_intro, theme_color, and section_language to
locate the entries).

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt`:
- Around line 754-769: When handling TweaksAction.OnAppLanguageSelected inside
the viewModelScope.launch, short-circuit if the tapped tag equals the current
persisted selection to avoid rewriting the same value and emitting a spurious
restart prompt: compare action.tag against _state.value.selectedAppLanguage and
only call tweaksRepository.setAppLanguage(action.tag) and
_events.send(TweaksEvent.OnAppLanguageChangeRequiresRestart) (when getPlatform()
!= Platform.ANDROID) if they differ; keep the existing platform check and use
the same symbols (TweaksAction.OnAppLanguageSelected,
tweaksRepository.setAppLanguage, _state.value.selectedAppLanguage, _events.send,
TweaksEvent.OnAppLanguageChangeRequiresRestart, getPlatform, Platform.ANDROID,
viewModelScope.launch).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 874e6070-81a0-4768-be6f-c7e5f4b29ba1

📥 Commits

Reviewing files that changed from the base of the PR and between be9cd77 and edc3e8c.

📒 Files selected for processing (31)
  • composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt
  • composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AndroidLocalizationManager.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/services/LocalizationManager.kt
  • core/data/src/jvmMain/kotlin/zed/rainxch/core/data/services/DesktopLocalizationManager.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/AppLanguage.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt
  • core/presentation/src/commonMain/composeResources/values-ar/strings-ar.xml
  • core/presentation/src/commonMain/composeResources/values-bn/strings-bn.xml
  • core/presentation/src/commonMain/composeResources/values-es/strings-es.xml
  • core/presentation/src/commonMain/composeResources/values-fr/strings-fr.xml
  • core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml
  • core/presentation/src/commonMain/composeResources/values-it/strings-it.xml
  • core/presentation/src/commonMain/composeResources/values-ja/strings-ja.xml
  • core/presentation/src/commonMain/composeResources/values-ko/strings-ko.xml
  • core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml
  • core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml
  • core/presentation/src/commonMain/composeResources/values-tr/strings-tr.xml
  • core/presentation/src/commonMain/composeResources/values-zh-rCN/strings-zh-rCN.xml
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/tweaks/presentation/src/androidMain/kotlin/zed/rainxch/tweaks/presentation/RestartApp.android.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/RestartApp.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksAction.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksEvent.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksRoot.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksState.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Language.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/SettingsSection.kt
  • feature/tweaks/presentation/src/jvmMain/kotlin/zed/rainxch/tweaks/presentation/RestartApp.jvm.kt

Comment thread composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt
Comment thread core/presentation/src/commonMain/composeResources/values-es/strings-es.xml Outdated
Comment thread core/presentation/src/commonMain/composeResources/values/strings.xml Outdated
…on Desktop

- Introduce `restartAppAfterLanguageChange` expect/actual function to handle platform-specific restart logic.
- Implement JVM-specific logic using `ProcessHandle` and `ProcessBuilder` to attempt a fresh relaunch of the app with original arguments before exiting.
- Add a no-op implementation for Android, where language changes are already handled via `Activity.recreate()`.
- Update `TweaksRoot` to invoke the new restart logic when the user performs the snackbar action.
- Implement `CrashReporter` for JVM to log stdout/stderr to a session file and write detailed crash dumps on uncaught exceptions.
- Add OS-specific log directory resolution for Windows, macOS, and Linux.
- Wrap the initial language preference read in `runBlocking` with a 2-second timeout to prevent potential DataStore stalls from blocking app launch.
- Initialize `CrashReporter` at the entry point to capture early initialization errors.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/CrashReporter.kt`:
- Around line 16-24: The current install() sets System.setOut/Err to
TeePrintStream writing all stdout/stderr into session.log (via File(logDir,
"session.log") and rotateIfLarge), which persists arbitrary output; change
install() so teeing is opt-in and protected: add a boolean/config flag (e.g.,
enableSessionTee or CrashReporter.config.enableTee) checked before creating the
PrintStream and calling System.setOut/System.setErr so crash dumps remain active
but general teeing is disabled by default, and if enabled apply
redaction/retention controls (invoke rotateIfLarge, limit file age/size, and
filter sensitive patterns before writing using TeePrintStream or a wrapper) so
only approved/filtered output is persisted, leaving crash dump paths unchanged.
- Around line 67-72: The rotateIfLarge function currently calls rotated.delete()
and file.renameTo() which can fail silently; update rotateIfLarge to check their
boolean return values and handle failures: if rotated.delete() fails, attempt to
delete via Files.deleteIfExists(Path) or create a uniquely-named temp rotated
file and fall back to truncating the original (open FileOutputStream(file,
false) to truncate to zero) so session.log cannot grow past
MAX_SESSION_LOG_BYTES; if file.renameTo(rotated) fails, attempt an atomic move
via Files.move(Path, Path, StandardCopyOption.ATOMIC_MOVE/REPLACE_EXISTING) or
again truncate the original, and log the error using your logger so failures are
visible. Ensure references to rotated ("session.1.log"), file, rotateIfLarge,
and MAX_SESSION_LOG_BYTES are used to locate where to apply these checks and
fallbacks.

In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt`:
- Around line 63-69: The try/catch around withTimeoutOrNull currently catches
Throwable (suppressing fatal JVM errors); change the catch to catch Exception so
only non-fatal exceptions are handled. Locate the try block that calls
withTimeoutOrNull(LANGUAGE_PREF_READ_TIMEOUT_MS) {
tweaksRepo.getAppLanguage().first() } (in DesktopApp.kt) and replace "catch (_:
Throwable)" with "catch (_: Exception)" or equivalent, leaving the timeout
behaviour unchanged.

In
`@core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/AppLanguage.kt`:
- Around line 14-19: Add the kotlinx.serialization annotation to the AppLanguage
data class: annotate the data class AppLanguage with `@Serializable` and add the
corresponding import from kotlinx.serialization.Serializable so the model is
serializable by Kotlinx Serialization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b4a57a30-465c-47ae-afaa-b2b1264e41ed

📥 Commits

Reviewing files that changed from the base of the PR and between edc3e8c and ad81620.

📒 Files selected for processing (20)
  • CLAUDE.md
  • composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt
  • composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/CrashReporter.kt
  • composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/AppLanguage.kt
  • core/presentation/src/commonMain/composeResources/values-ar/strings-ar.xml
  • core/presentation/src/commonMain/composeResources/values-bn/strings-bn.xml
  • core/presentation/src/commonMain/composeResources/values-es/strings-es.xml
  • core/presentation/src/commonMain/composeResources/values-fr/strings-fr.xml
  • core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml
  • core/presentation/src/commonMain/composeResources/values-it/strings-it.xml
  • core/presentation/src/commonMain/composeResources/values-ja/strings-ja.xml
  • core/presentation/src/commonMain/composeResources/values-ko/strings-ko.xml
  • core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml
  • core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml
  • core/presentation/src/commonMain/composeResources/values-tr/strings-tr.xml
  • core/presentation/src/commonMain/composeResources/values-zh-rCN/strings-zh-rCN.xml
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt
✅ Files skipped from review due to trivial changes (9)
  • CLAUDE.md
  • core/presentation/src/commonMain/composeResources/values-es/strings-es.xml
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • core/presentation/src/commonMain/composeResources/values-zh-rCN/strings-zh-rCN.xml
  • core/presentation/src/commonMain/composeResources/values-ko/strings-ko.xml
  • core/presentation/src/commonMain/composeResources/values-fr/strings-fr.xml
  • core/presentation/src/commonMain/composeResources/values-ja/strings-ja.xml
  • core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml
  • core/presentation/src/commonMain/composeResources/values-bn/strings-bn.xml
🚧 Files skipped from review as they are similar to previous changes (7)
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt
  • core/presentation/src/commonMain/composeResources/values-ar/strings-ar.xml
  • core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml
  • core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml
  • core/presentation/src/commonMain/composeResources/values-it/strings-it.xml
  • composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt
  • core/presentation/src/commonMain/composeResources/values-tr/strings-tr.xml

Comment on lines +16 to +24
fun install() {
val teed =
runCatching {
val file = File(logDir, "session.log")
rotateIfLarge(file)
PrintStream(FileOutputStream(file, true), true, Charsets.UTF_8)
.also { stream ->
System.setOut(TeePrintStream(System.out, stream))
System.setErr(TeePrintStream(System.err, stream))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid persisting unrestricted stdout/stderr by default.

Line 21–24 writes every process log line to disk, including anything accidentally printed by dependencies or debug code. Add redaction/retention controls or make session teeing explicitly opt-in while keeping crash dumps enabled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/CrashReporter.kt`
around lines 16 - 24, The current install() sets System.setOut/Err to
TeePrintStream writing all stdout/stderr into session.log (via File(logDir,
"session.log") and rotateIfLarge), which persists arbitrary output; change
install() so teeing is opt-in and protected: add a boolean/config flag (e.g.,
enableSessionTee or CrashReporter.config.enableTee) checked before creating the
PrintStream and calling System.setOut/System.setErr so crash dumps remain active
but general teeing is disabled by default, and if enabled apply
redaction/retention controls (invoke rotateIfLarge, limit file age/size, and
filter sensitive patterns before writing using TeePrintStream or a wrapper) so
only approved/filtered output is persisted, leaving crash dump paths unchanged.

Comment on lines +67 to +72
private fun rotateIfLarge(file: File) {
if (!file.exists() || file.length() <= MAX_SESSION_LOG_BYTES) return
val rotated = File(file.parentFile, "session.1.log")
if (rotated.exists()) rotated.delete()
file.renameTo(rotated)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle failed rotation so session.log cannot grow unbounded.

delete() and renameTo() can fail silently. If rotation fails, the next FileOutputStream(..., true) keeps appending beyond MAX_SESSION_LOG_BYTES.

Proposed fallback
     private fun rotateIfLarge(file: File) {
         if (!file.exists() || file.length() <= MAX_SESSION_LOG_BYTES) return
         val rotated = File(file.parentFile, "session.1.log")
-        if (rotated.exists()) rotated.delete()
-        file.renameTo(rotated)
+        if (rotated.exists() && !rotated.delete()) {
+            FileOutputStream(file, false).close()
+            return
+        }
+        if (!file.renameTo(rotated)) {
+            FileOutputStream(file, false).close()
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private fun rotateIfLarge(file: File) {
if (!file.exists() || file.length() <= MAX_SESSION_LOG_BYTES) return
val rotated = File(file.parentFile, "session.1.log")
if (rotated.exists()) rotated.delete()
file.renameTo(rotated)
}
private fun rotateIfLarge(file: File) {
if (!file.exists() || file.length() <= MAX_SESSION_LOG_BYTES) return
val rotated = File(file.parentFile, "session.1.log")
if (rotated.exists() && !rotated.delete()) {
FileOutputStream(file, false).close()
return
}
if (!file.renameTo(rotated)) {
FileOutputStream(file, false).close()
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/CrashReporter.kt`
around lines 67 - 72, The rotateIfLarge function currently calls
rotated.delete() and file.renameTo() which can fail silently; update
rotateIfLarge to check their boolean return values and handle failures: if
rotated.delete() fails, attempt to delete via Files.deleteIfExists(Path) or
create a uniquely-named temp rotated file and fall back to truncating the
original (open FileOutputStream(file, false) to truncate to zero) so session.log
cannot grow past MAX_SESSION_LOG_BYTES; if file.renameTo(rotated) fails, attempt
an atomic move via Files.move(Path, Path,
StandardCopyOption.ATOMIC_MOVE/REPLACE_EXISTING) or again truncate the original,
and log the error using your logger so failures are visible. Ensure references
to rotated ("session.1.log"), file, rotateIfLarge, and MAX_SESSION_LOG_BYTES are
used to locate where to apply these checks and fallbacks.

Comment thread composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt
Comment on lines +14 to +19
data class AppLanguage(
/** IETF BCP 47 language tag (e.g. `en`, `zh-CN`, `pt-BR`). */
val tag: String,
/** Native-script label, e.g. `简体中文`, `Español`. */
val displayName: String,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Kotlinx Serialization is configured and inspect existing annotation patterns.
set -euo pipefail

fd -e kts 'build.gradle.kts' -x rg -n 'plugin\.serialization|kotlinx-serialization|kotlinx\.serialization|serialization' {}
rg -n --type=kt -C2 '@Serializable|data\s+class\s+AppLanguage\b'

Repository: OpenHub-Store/GitHub-Store

Length of output: 176


Add the required serialization annotation to the data class.

AppLanguage is a domain model for UI language selection that should be serializable per the coding guidelines. Kotlinx Serialization is already configured in the build. Add the @Serializable annotation and import it from kotlinx.serialization.

Proposed update
 package zed.rainxch.core.domain.model
 
+import kotlinx.serialization.Serializable
+
 /**
  * A user-selectable UI language for the app. Each entry corresponds to a
@@
  * to escape.
  */
+@Serializable
 data class AppLanguage(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/AppLanguage.kt`
around lines 14 - 19, Add the kotlinx.serialization annotation to the
AppLanguage data class: annotate the data class AppLanguage with `@Serializable`
and add the corresponding import from kotlinx.serialization.Serializable so the
model is serializable by Kotlinx Serialization.

- Change caught exception type from `Throwable` to `Exception` when reading the app language preference in both `DesktopApp.kt` and `MainActivity.kt`.
@rainxchzed rainxchzed merged commit 45eaaa9 into main Apr 20, 2026
1 check was pending
@rainxchzed rainxchzed deleted the feat-change-language branch April 20, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant