Skip to content

Add Russian localization (string resources + values-ru) - #143

Open
g4st3r-d3v wants to merge 2 commits into
foureight84:mainfrom
g4st3r-d3v:ru-l10n-upstream
Open

g4st3r-d3v wants to merge 2 commits into
foureight84:mainfrom
g4st3r-d3v:ru-l10n-upstream

Conversation

@g4st3r-d3v

Copy link
Copy Markdown

Hi! I made a Russian translation of PulseLoop Android for myself, with Claude (Claude Code) doing most of the work. It came out clean enough that I wanted to offer it upstream. If it's useful, feel free to merge it or suggest changes. If it isn't a direction you want for the project, no problem at all: I'll keep it in my fork.

What it does

  • Moves user-visible strings from Kotlin literals into strings.xml and adds a Russian translation in values-ru (about 1,100 strings, with proper plurals).
  • English output is unchanged. The English resources carry the original copy verbatim, and the existing unit tests (1637) pass without modification.
  • Composables use stringResource / pluralStringResource. Code without a Context (view-model state builders, VitalsThresholdEngine, formatters, notifications) goes through a small util/L10n helper. In JVM unit tests there is no Context, so L10n reads the default values/strings.xml directly, and tests asserting English text keep working.
  • util/LocaleFormats picks locale-aware date/time patterns via Android's best-pattern skeletons (24h clock and day-month order where the locale uses them). en-US output looks the same as before.
  • Coach: prompts, tool schemas and parsers stay in English. For a non-English locale, coach/context/CoachLanguage appends one sentence to the chat, summary, check-in and meal-estimate system prompts, asking the model to answer in the app's language. For English the directive is empty, so prompts are byte-for-byte what they were.
  • res/xml/locales_config.xml enables the Android 13+ per-app language picker.
  • README.ru.md plus a one-line link from README.md.
  • A small layout fix that the translation surfaced: the onboarding welcome tiles had a fixed height that clipped a two-line title, and they now grow to fit.

Left in English on purpose: the Debug console, local-LLM probe notes, model names/blurbs, and diagnostics exports, since those are meant for bug reports.

Adding another language now only takes a values-xx/strings.xml and one line in locales_config.xml.

Scope / caveats

  • This touches many files (mostly string-literal → resource swaps). No protocol, BLE, sync or persistence logic was changed; the edits in ring/ are limited to error-message strings.
  • I know the README asks for UI changes to go through iOS first. This doesn't change any UI, only where the text comes from, but I understand if you'd rather handle localization differently.
  • Tested: testDebugUnitTest and assembleDebug pass; I checked the Russian build on my phone.

🤖 Generated with Claude Code

Aleksandr Poryadin added 2 commits November 11, 2026 14:32
Move user-visible strings out of Kotlin literals into string resources
and add a Russian translation (values-ru). English output is unchanged.

- Composables use stringResource / pluralStringResource.
- Code without a Context (view-model state builders, threshold engine,
  formatters, notifications) goes through util/L10n. In JVM unit tests it
  reads the default values/strings.xml, so existing English assertions
  pass without modification.
- util/LocaleFormats picks locale-aware date/time patterns (24h clock and
  day-month order where the locale uses them; en-US output is unchanged).
- coach/context/CoachLanguage appends an output-language directive to the
  chat, summary, check-in and meal-estimate system prompts for non-English
  locales. Prompts, tool schemas and parsers stay English; for an English
  locale the directive is empty and prompts are byte-for-byte unchanged.
- res/xml/locales_config.xml enables the Android 13+ per-app language picker.
- README.ru.md.

Left in English on purpose: the Debug console, local-LLM probe notes,
model names/blurbs, and diagnostics exports.
The tiles had a fixed 118dp height, which clipped the subtitle whenever a
title wrapped to two lines (common in Russian). Rows now size to the taller
tile; shorter Russian labels also keep most tiles on one line.
@foureight84

Copy link
Copy Markdown
Owner

Hey @g4st3r-d3v, thanks a ton for this! 🙌 Getting ~1,100 strings out of Kotlin and into resources is a big, boring job, and you did all of it. The English and Russian files line up really nicely: placeholders match, every R.string / R.plurals you reference exists, and the test suite passes on your branch. Really solid work.

I went through it carefully and found a few things that only show up at runtime, so neither the build nor the tests catch them. None of them are big, just a handful of edge cases:

Worth fixing before we merge

  1. Possible crash in LocaleFormats.kt:24. The runCatching wraps the getBestDateTimePattern lookup but not the DateTimeFormatter.ofPattern(...) call after it. On some locales (zh-TW, for example) the skeletons hand back patterns with B in them (day period, like Bh:mm), and java.time on Android 12/13 throws on that. The device locale gets used even for languages we don't translate, so the sleep/chart/activity screens could crash for those folks. Catching ofPattern too and falling back to fallback should do it.
  2. Coach language comes from the device, not the app (CoachLanguage.kt:16). Locale.getDefault() is the phone's language, so on a German phone the UI falls back to English but the coach starts answering in German. Reading the locale the app actually resolved should keep them in sync: resources.configuration.locales[0], or even just a coach_language string resource.
  3. A few Russian strings need <plurals>. Some put %d in front of a noun with a fixed ending, so you get things like "за 3 попыток", "1 шагов" / "2 шагов", "за 2 записанных ночей". The ones I spotted: notif_goal_left, notif_evening_steps, fb_steps_avg, fb_today, hero_steps_only, sc_week_incomplete, and the max-attempts string. You'll know the grammar way better than me 🙂

Spots that are still half-English

  1. PendingActionExecutor.kt:51 still uses its own English activityLabels map, so confirmations come out as "Удалена тренировка Walking." Switching to ActivityMeta.label(session.type), while keeping the outdoor_run / outdoor_walk aliases, lets you drop the map. HealthConnectRemovalWorker.kt:37 also still has an English "Health Connect isn't available right now…".
  2. The glucose card uses gUnit.labelRes for the value but gUnit.label for the trend line (VitalCardState.kt:436), so you see "ммоль/л" next to "mmol/L".
  3. Now that the coach replies in Russian, chart titles come back as "Сон" / "Шаги", but CoachChartView.kt:34-37 picks colours by matching English words ("HR", "bpm", "sleep", "step"). So every chart ends up in the default colour.
  4. A couple of things get translated once and then stick. SleepInsights.dayNoDataCoach and the unit labels in MetricThresholdTable are resolved when they're first created, so after a per-app language switch they stay in the old language until the app restarts. Coach summaries are cached by data signature only, so they don't regenerate after a language change either (CoachSummaryService.kt:50). Adding the language to the cache key should fix that.

Small stuff, not blocking

  1. LocaleFormats builds a new formatter on every call, and it now runs per chart tick and per sleep row. A little cache keyed on (skeleton, locale) would help.
  2. The find-and-replace reached into a few comments (e.g. VitalsThresholdEngine.kt:14, which now reads "Labels are conservative (L10n.get(R.string.zl_typical), …)"). It also left val direction unused around line 311. Period.label and GlucoseUnit.label are still sitting next to the new labelRes fields, which is how Updates charts to be more usable. #5 crept in, so it'd be nice to drop the English ones.

Process stuff

No rush at all. Happy to help with any of it, and thanks again for doing this! 🇷🇺

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.

2 participants