Internationalization (i18n)
Bunny UI copy for English and French lives in a single catalog shared by the agent server, Discord bridge, and Web UI.
Catalog
- Paths:
packages/i18n/messages.json(CLI, Discord, API) andpackages/i18n/web-messages.json(Web UI) - Shape: each key maps to
{ "en": "...", "fr": "..." } - Placeholders:
{name}(simple string replace, no ICU)
Example:
{
"discord.thread.linked": {
"en": "_Thread linked to shell `{shell}`_",
"fr": "_Thread lié au shell `{shell}`_"
}
}
Rust (bunny-i18n)
Crate: crates/bunny-i18n
use bunny_i18n::{Locale, t};
let msg = t(Locale::Fr, "configure.owner_created", &[]);
Locale::En/Locale::Frparse_locale("fr")/is_valid_locale_code- Unit test: every key must have both
enandfr
Consumers: apps/server, apps/discord-bridge.
TypeScript (Web UI)
- Alias:
@bunny/i18n→packages/i18n(seeapps/web/vite.config.ts) - Helper:
apps/web/src/i18n.ts
import { t } from '../i18n';
t('fr', 'web.home.language_label');
Web UI merges both JSON files in apps/web/src/i18n.ts. Use useT() in components for translated copy.
User preference storage
- SQLite:
users.preferred_locale(endefault,frallowed) - Set during first
bunny configure(prompt at start of flow) - Web:
PATCH /api/v1/auth/mewith{ "locale": "fr" } - Discord:
/bunny language locale:fr(requires Discord account linked to a Bunny user) - Visible in
/bunny statusJSON aslocale(+locale_source)
PTY LANG / LC_ALL (UTF-8) is separate from UI locale.
Adding a string
- Add a key under the right prefix (
configure.*,discord.*,web.*,api.error.*) inmessages.jsonwith both locales. - Run
cargo test -p bunny-i18n(parity test). - Use
t(locale, "your.key", &[("var", "value")])in Rust ort(locale, 'your.key', { var: 'value' })in TS. - Optional:
./scripts/check-i18n.sh(validates JSON parity).
Discord OAuth (Web UI)
| Route | Purpose |
|---|---|
GET /api/v1/auth/discord/start | Redirect to Discord OAuth (session cookie required) |
GET /api/v1/auth/discord/callback | OAuth callback → redirect /?discord_link=success|conflict|error |
DELETE /api/v1/auth/discord/link | Unlink Discord account from current Bunny user |
GET /auth/me includes a discord object: oauth_configured, linked, discord_user_id, username.
Internal Discord API (bridge)
| Route | Purpose |
|---|---|
GET /api/v1/internal/discord/status | Link status + locale |
GET /api/v1/internal/discord/user-locale | Locale without requiring channel link |
POST /api/v1/internal/discord/locale | Set locale for linked Discord user (/bunny language) |