Skip to content

Latest commit

 

History

History
117 lines (91 loc) · 5.11 KB

TRANSLATING.md

File metadata and controls

117 lines (91 loc) · 5.11 KB

Translating CallShield

CallShield ships in English, Simplified Chinese, Brazilian Portuguese and Spanish. New languages are welcome, and partial ones help. Android falls back to English per string, so a translation that covers the screens people actually look at is better than none.

The Portuguese and Spanish text hasn't had a native speaker's review yet. If something reads wrong, a PR that fixes a few lines is as useful as a whole new language.

The general translation thread is issue #7. Comment there to claim a language so two people don't translate the same 1,800 strings.

What to translate

The source of truth is app/src/main/res/values/strings.xml (about 1,800 strings and 50 <plurals>). Create app/src/main/res/values-<locale>/ and put your translated <string> and <plurals> elements there. Either layout works, everything in one strings.xml or split into strings.xml + plurals.xml. Resource filenames carry no meaning to the Android build.

Locale directory names use Android's qualifier form, not BCP-47:

Language Directory
Simplified Chinese values-zh-rCN
German values-de
Brazilian Portuguese values-pt-rBR
Spanish values-es

Priority order, if you are not translating everything:

  1. Call screening and blocking. The dashboard, the blocked log, the after-call notification. This is what users see when the app does its job.
  2. Onboarding and permissions. The recovery paths. A user who cannot read why call screening is off cannot fix it.
  3. Blocklist and rules. Day-to-day management.
  4. Settings, statistics, changelog. These can come later.

Register the locale

Add your language to app/src/main/res/xml/locales_config.xml:

<locale android:name="zh-CN" />

Note the BCP-47 form here (zh-CN), which differs from the resource directory (values-zh-rCN). The manifest sets android:localeConfig, so this file is what populates the per-app language picker in Android's system settings. Skip it and your translation only appears for users whose entire device is set to that language. It is the single easiest thing to forget.

Then add it to the app's own picker in ui/AppLanguage.kt, with its name in its own language as a translatable="false" string. ShippedLocalesTest fails if the two lists disagree.

Check your work

python scripts/check_translations.py                  # every locale in the repo
python scripts/check_translations.py --locale zh-rCN  # just yours
python scripts/check_translations.py --dir path/to/values-xx  # before it's in the repo

It reports coverage and fails on the things that break at runtime rather than at build time:

  • Format specifiers that don't match the English. %1$s, %d and friends must survive translation with the same count and types. Get this wrong and String.format throws the moment the string is displayed. Which in this app can be the call-screening notification, on a device in a language the maintainer cannot read. Reordering is fine and expected. That's what the positional %1$s form is for.
  • Missing plural quantities. Chinese needs only other, while Russian and Polish need one/few/many/other. The checker knows which.
  • Stale keys that no longer exist upstream (a warning, not an error).
  • Locales missing from locales_config.xml.

Missing strings are reported as coverage, never as a failure, with one exception. Each shipped locale has a recorded coverage floor in scripts/translation_floors.json, and the checker fails if coverage drops below it. A partial translation is fine and expected. A translation quietly rotting as English strings are added faster than they are translated is not.

If you improve a locale, raise its floor in the same change:

python scripts/check_translations.py --update-floors

--update-floors only ever raises a floor, and it still fails when a locale is below the floor it had. Lowering a floor means editing scripts/translation_floors.json by hand, on purpose, with the reason in the commit message. A floors file that can't be read fails the check rather than switching it off. A new locale starts with no floor and only warns until someone records one.

Style notes

  • Phone numbers are wrapped in Unicode bidi isolates before display, so you do not need to do anything special for RTL languages. But do keep %1$s placeholders intact rather than substituting a literal example number.
  • "Trusted" is used consistently for allowlisted callers. Keep one term for it in your language rather than alternating synonyms.
  • Carrier authentication (STIR/SHAKEN) copy deliberately avoids implying the caller is safe. It says the caller ID is authenticated. Please preserve that distinction, it is a correctness matter and not a stylistic one.
  • Keep CallShield untranslated as the app name.

Submitting

Open a PR with just the resource files and the locales_config.xml line. Please don't include build outputs, APKs, or version bumps. Those come from the release process and only make the diff hard to review. Your commits keep your authorship.