Skip to content

feat: localize bot menus for all !list-langs languages #27

Description

@daopunk

Summary

Bot command menus (!help, product menus, !privacy, thread !commands, etc.) are English-only. Translation already targets the full catalog in translate_lang.rs (ALL_LANGUAGES / !list-langs). UI copy lives in menu_locale.rs. The leftover per-group MenuLanguage enum (en / es) in menu_language.rs is unused by handlers.

Canonical implementation plan: docs/plans/2026-08-14-001-feat-localize-bot-menus-plan.md (implementation-ready). Execute that file; this issue restates the decisions so the ticket can stand alone.

Goals

  1. Parity with !list-langs — every catalog code is a valid menu locale (full strings or English fallback).
  2. Selectable via bot text!set-lang / !set-lang <code>, not 30 !set-xx commands.
  3. No UI enum per language — adding a code to ALL_LANGUAGES is the gate; a string bundle is optional.
  4. Preserve mobile menu layoutdocs/solutions/signal-mobile-menus.md.

Resolved design (do not re-open at implement time)

Question Decision
Storage Per-user, snapshot-level user_menu_locales map on encrypted prefs. Not per-group. Not the MenuLanguage enum.
DMs + mixed groups Same user locale in DMs and groups. Dual-key message.source and source_number (same as translate_all).
Authoring Static English source + per-locale bundles. No runtime NEAR translation. No fluent/gettext.
Fallback Missing locale or missing key → English. Commands never fail for lack of a bundle.
Discovery Hub !help / !info list !set-lang with gloss Menu language / idioma / langue.
Scaling !set-lang (picker) and !set-lang es / español. Leave !list-langs as the translation catalog; add a cross-link.
Sidecar No explicit pref → default to that Language Thread’s language code. Explicit pref (including en) wins.
Command tokens Stay English. Translate titles, descriptions, guides, confirmations only.
Coverage for v1 English complete. Spanish is the first full non-English bundle (including reviewed !privacy). Other codes persist and render English.
Legacy menu_language Keep for serde of old snapshots. Do not read for UI. Do not backfill group es onto users. Do not bump DATA_VERSION.
CVM Additive field + #[serde(default)]. In-place phala deploy --cvm-id. Never wipe volumes.
Pairing / two roles Out of scope — pairing is gone; one bot. Sidecar compact menu is !commands, not hub !help.

Stop if the work would wipe CVM volumes, translate menus via NEAR at runtime, grow MenuLanguage per language, or localize !command tokens.

Scope

Localize (or English-fallback): hub !help / !info, sidecar !commands / !info, product menus (including auto-translate disabled), feature guides, !translation redirect, !privacy, !set-lang picker + confirmation.

Out of scope: user-message translation; remaining language bundles after Spanish; operational errors (Unknown language, NOT_THREAD_MSG, on/off confirmations); !set-lang auto; RTL markup; offline translate-from-EN script.

Implementation units (dependency order)

  1. U1 — Store + resolveruser_menu_locales on GroupPreferencesSnapshot; get/set dual-key; resolve_menu_locale = explicit pref → sidecar lang → en. Tests: legacy JSON load, encrypted round-trip, DM with no group id, sidecar default vs explicit en.
  2. U2 — menu_locale API — builders take locale: &str; unknown locale equals English. Prove fallback before writing Spanish.
  3. U3 — !set-lang — picker (flag + autonym + code); persist; reject unknown / extra tokens; hub + !list-langs discovery; register handler (set_lang); flip handlers_setup “not present” assert.
  4. U4 — Wire handlers — inject store into HelpHandler, product menus, PrivacyHandler; keep hub-vs-sidecar branching.
  5. U5 — Spanish bundle — all menu surfaces including !privacy (human-check attestation scope). Layout tests: command tokens present, stacked !cmd + indent, no cmd — desc one-liners.
  6. U6 — Docs — retire “English-only / deferred” in signal-mobile-menus.md; CVM volume table says per-user locales on the same group_prefs.enc.

Files to expect: group_preferences_store.rs, menu_locale.rs, translate_lang.rs (autonyms), new set_lang handler, help.rs / product_menus.rs / privacy.rs / handlers_setup.rs, the docs above.

Test plan

  • !set-lang es / español persists; !help is Spanish; tokens like !translation-threads unchanged
  • !set-lang fr persists; menus stay English until a fr bundle exists
  • !set-lang klingon and !set-lang es en reject; no persist
  • Mixed group: user A=es, user B unset → each !help matches that sender
  • Set in DM, then !help in a group (dual identity keys)
  • No pref in an es sidecar → !commands uses sidecar default (Spanish once bundled, else English)
  • Encrypted prefs round-trip; snapshot without user_menu_locales still loads
  • npm run ci / pnpm run ci (not pnpm ci)

Acceptance criteria

  • Documented approach (this issue + docs/plans/2026-08-14-001-feat-localize-bot-menus-plan.md + U6 docs)
  • Bot-text UI to list and select menu language among supported codes
  • Selecting a language persists per user and subsequent menus render in that language (or English fallback)
  • All ALL_LANGUAGES codes work without a new Rust enum variant (full strings or explicit English fallback)
  • Mobile stacked layout preserved for localized command lists
  • Tests for selection, persistence, and at least one non-English (Spanish) menu render path

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions