Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,35 @@ The design system provides:

`SessionMaterialTheme` installs both the custom tokens and Material 3. Most Compose entry points use `setThemedContent { ... }` so screens can remain thin.

## 13. Dependency Injection
## 13. Localization and Translations

UI strings are managed on **Crowdin** (project ID `618696`), not in this repo. This matters because the string resources here are **generated artifacts**, not editable sources.

### Source of truth

Every `app/src/main/res/values*/strings.xml` — including the base English `values/strings.xml` — plus `NonTranslatableStringConstants.kt` (the `{app_name}`, `{entity_stf_short}`, … substitution values) is generated from Crowdin. **Do not hand-edit these files**; changes are overwritten on the next sync. String source text (English) and translations are both authored/managed on Crowdin.

### The sync automation lives in a different repo

There is no Crowdin workflow in this repo. The automation is in **`session-foundation/session-shared-scripts`**, workflow `.github/workflows/check_for_crowdin_updates.yml`:

- Triggers: scheduled (Mondays 00:00 UTC) or manual `workflow_dispatch` (input `UPDATE_PULL_REQUESTS=true`).
- Downloads from Crowdin, regenerates all `values*/strings.xml` + `NonTranslatableStringConstants.kt` (`crowdin/generate_android_strings.py`), validates via a gradle resource build, then opens/updates a PR using `peter-evans/create-pull-request`.
- The PR targets the fixed head branch `feature/update-crowdin-translations` → `dev`, opened by the `stfsession` service account. Because the branch is fixed, there is only ever one open translations PR at a time; a re-run updates it in place (unless the previous one was already merged).
- The same run produces the equivalent PRs for iOS and the desktop localization module.

### Approved-only export

The download requests translations with `exportApprovedOnly=true` and `skipUntranslatedStrings=true`. Consequently, machine/AI pre-translations are **excluded until a proofreader approves them** in Crowdin — unapproved strings are treated as untranslated and dropped. Approval requires a Proofreader/Manager role on the Crowdin project. (A helper for bulk-approving specific string identifiers lives at `session-shared-scripts/crowdin/approve_strings.py`.)

### Shipping a text change

1. Edit/add the source strings on Crowdin (new copy usually warrants **new string keys** — reusing a key keeps its now-stale translations, which will resurface as mismatched text).
2. Approve the translations you want (see above), or they won't export.
3. Run `check_for_crowdin_updates.yml` and merge the resulting Crowdin PR — this brings the keys into `strings.xml`.
4. Only then merge any code referencing the new keys (`R.string.<key>` won't compile until the sync has landed them).

## 14. Dependency Injection

Hilt is the application-wide DI framework.

Expand All @@ -629,7 +657,7 @@ Important modules include:

The most important architectural convention here is not just "use Hilt", but "inject long-lived managers plus a manager-scoped coroutine scope and let them expose reactive state."

## 14. Other Important Runtime Components
## 15. Other Important Runtime Components

- `ExpiringMessageManager`: disappearing-message lifecycle
- `AvatarUploadManager`: avatar upload/sync behavior
Expand All @@ -640,7 +668,7 @@ The most important architectural convention here is not just "use Hilt", but "in

If a bug seems to "happen in the background" but does not fit config, polling, notifications, or Pro, it often lives in one of these auth-aware managers.

## 15. End-to-End Flow Summaries
## 16. End-to-End Flow Summaries

### App startup and login

Expand Down Expand Up @@ -695,7 +723,7 @@ User action
-> reactive flows refresh UI
```

## 16. Practical Notes for new Developers
## 17. Practical Notes for new Developers

For a new developer, the highest-value mental model is:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,11 @@ interface TextSecurePreferences {


// Donation
const val HAS_DONATED = "has_donated_v3"
const val HAS_COPIED_DONATION_URL = "has_copied_donation_url_v3"
const val SEEN_DONATION_CTA_AMOUNT = "seen_donation_cta_amount_v3"
const val LAST_SEEN_DONATION_CTA = "last_seen_donation_cta_v3"
const val SHOW_DONATION_CTA_FROM_POSITIVE_REVIEW = "show_donation_cta_from_positive_review_v3"
const val HAS_DONATED = "has_donated_v4"
const val HAS_COPIED_DONATION_URL = "has_copied_donation_url_v4"
const val SEEN_DONATION_CTA_AMOUNT = "seen_donation_cta_amount_v4"
const val LAST_SEEN_DONATION_CTA = "last_seen_donation_cta_v4"
const val SHOW_DONATION_CTA_FROM_POSITIVE_REVIEW = "show_donation_cta_from_positive_review_v4"

const val DEBUG_HAS_DONATED = "debug_has_donated"
const val DEBUG_HAS_COPIED_DONATION_URL = "debug_has_copied_donation_url"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,11 @@ fun DonationDialog(
sendCommand(HideDonationCTADialog)
}

val title = Phrase.from(context,R.string.finalAppeal)
val title = Phrase.from(context,R.string.ongoingAppeal)
.put(StringSubstitutionConstants.APP_NAME_KEY, NonTranslatableStringConstants.APP_NAME)
.format()

val text = Phrase.from(context,R.string.finalAppealDescription)
val text = Phrase.from(context,R.string.ongoingAppealDescription)
.put(StringSubstitutionConstants.ENTITY_STF_SHORT_KEY, NonTranslatableStringConstants.ENTITY_STF_SHORT)
.put(StringSubstitutionConstants.APP_NAME_KEY, NonTranslatableStringConstants.APP_NAME)
.format()
Expand Down
Binary file modified app/src/main/res/drawable-nodpi/cta_hero_donation.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading