Translating
How Teak's web app and browser extension are translated, how to add or change text, and the glossary every language uses
The web app (apps/web) and the browser extension (apps/extension) are translated into Spanish, Portuguese (Brazil), German, French, Japanese, Korean and Simplified Chinese. Every word people read comes from one catalog in packages/i18n (@teak/i18n). The native apps, Raycast, the Safari extension, these docs and emails are in English. To choose a language in the app, see Language.
Where things live
| What | Where |
|---|---|
Locales, cookie name, intlLocale |
packages/i18n/src/locales.ts |
| The English catalog (the source) | packages/i18n/messages/en.json |
| One catalog per language | packages/i18n/messages/<locale>.json |
Hooks: useTranslations, useUiLocale, useIntlLocale |
@teak/i18n/react |
| A translator outside React | createTeakTranslator from @teak/i18n |
| Term glossary | Glossary on this page |
| Server error messages | errors.* keys, apps/web/src/lib/serverErrors.ts, hooks/useServerErrors.ts |
The signed-in web app gets its locale from the teak-locale cookie (read in AuthenticatedAppProvider), which mirrors the account’s Language setting. Without a provider, for example in tests or a screen outside the app shell, components render English.
Adding or changing text
- Add the English text to
en.jsonunder the component’s namespace, for examplecardModal.menu.replaceImage. Namespaces follow the component or area (settings.footer,sidebar,cardModal.menu). Reuse acommon.*key only when the word means the same everywhere (Cancel, Close, Undo). - Read it with
const t = useTranslations("cardModal.menu")andt("replaceImage"). - Add the same key to all seven other catalogs in the same pull request, translated with the glossary.
scripts/i18n-catalog.test.tsfails when a catalog lacks a key or its placeholders differ from English. - Run
bun test scripts/i18n-catalog.test.ts, then look at the screen in the language (Settings → Language), and in the pseudo-locale (?__locale=en-XA, development only).
Writing messages
- Whole sentences, never concatenated. Put values in placeholders:
"Moved {count, plural, one {# card} other {# cards}} to {folder}". - Plurals use ICU:
{count, plural, one {# card} other {# cards}}. Japanese, Korean and Chinese only useother. - Markup inside a sentence uses tags and
t.rich:"Read the <link>docs</link>"witht.rich("key", { link: (chunks) => <a href="…">{chunks}</a> }). - Attributes and toasts count too:
aria-label,title,placeholder,alt, and the text oftoast(...),toast.success(...)andtoast.error(...). - Data arrays (menu items, options, shortcut lists) keep a stable
idand a message key, not an Englishlabel. Build the visible label withtwhere it renders. - Helpers outside React (in
lib/) that return text take a translator argument, typedTeakTranslator<"namespace">, instead of returning English. - Server errors: show
useServerErrors().message(error, fallback). It translates every known server message and card code and falls back to the server’s English text.
What stays English
- “Teak”, plan names where the glossary says so, and search syntax: qualifiers (
is:fav,type:image,tag:) and date words, which people type. - Text the server stores on cards: link facts, bylines, price labels, import errors.
- Developer surfaces: the public API, MCP tool descriptions and CLI output.
Dates and numbers
Format with Intl, passing useIntlLocale() as the locale (or intlLocale(locale, …) outside React). In English it returns the locale the code used before translation: useIntlLocale() gives undefined (the browser’s locale) and useIntlLocale("en-US") gives "en-US", so English output doesn’t change. Never write a fixed "en-US"; lint flags it.
Lint
- Biome
noJsxLiteralsflags text between JSX tags inapps/web/srcand the extension’sentrypointsandcomponents. scripts/lint/no-ui-string.gritflags words inaria-label,title,placeholder,altand toast calls, plus fixed"en-US"locales.- Tests and
apps/web/src/components/ui/**(shadcn primitives) are exempt. Punctuation and “Teak” are allowed.
Glossary
Use these terms in every catalog, so a word means the same thing on every screen.
Never translate:
- Teak, the product name.
- Pro, the paid plan’s name.
- Search syntax people type: qualifiers (
is:fav,type:image,tag:,folder:),OR, and date words (today,last week). - Key names in shortcuts (⌘, Shift, Esc, Enter).
Translate meaning, not words: short UI labels stay short. Keep ICU placeholders, tag names and plural categories exactly. Use sentence case where the language has case, and the language’s own punctuation.
| Language | Register |
|---|---|
| Spanish (es) | formal, usted |
| Portuguese, Brazil (pt-BR) | você |
| German (de) | formal, Sie |
| French (fr) | formal, vous; a narrow no-break space (U+202F) before : ? ! ; |
| Japanese (ja) | polite, です/ます; plain nouns for buttons and menu items |
| Korean (ko) | 합니다 style for sentences; plain nouns for buttons and menu items |
| Chinese, Simplified (zh-Hans) | neutral; full-width punctuation |
Terms
| English | es | pt-BR | de | fr | ja | ko | zh-Hans |
|---|---|---|---|---|---|---|---|
| card | tarjeta | card | Karte | fiche | カード | 카드 | 卡片 |
| note (text card) | nota | nota | Notiz | note | メモ | 메모 | 笔记 |
| link | enlace | link | Link | lien | リンク | 링크 | 链接 |
| image | imagen | imagem | Bild | image | 画像 | 이미지 | 图片 |
| video | vídeo | vídeo | Video | vidéo | 動画 | 동영상 | 视频 |
| audio | audio | áudio | Audio | audio | 音声 | 오디오 | 音频 |
| document | documento | documento | Dokument | document | ドキュメント | 문서 | 文档 |
| palette | paleta | paleta | Palette | palette | パレット | 팔레트 | 调色板 |
| quote | cita | citação | Zitat | citation | 引用 | 인용 | 引用 |
| folder | carpeta | pasta | Ordner | dossier | フォルダ | 폴더 | 文件夹 |
| smart folder | carpeta inteligente | pasta inteligente | intelligenter Ordner | dossier intelligent | スマートフォルダ | 스마트 폴더 | 智能文件夹 |
| tag | etiqueta | tag | Tag | tag | タグ | 태그 | 标签 |
| archive (verb) | archivar | arquivar | archivieren | archiver | アーカイブ | 보관 | 归档 |
| Archive (view) | Archivo | Arquivo | Archiv | Archives | アーカイブ | 보관함 | 归档 |
| trash | papelera | lixeira | Papierkorb | corbeille | ゴミ箱 | 휴지통 | 废纸篓 |
| move to trash | mover a la papelera | mover para a lixeira | in den Papierkorb legen | placer dans la corbeille | ゴミ箱に移動 | 휴지통으로 이동 | 移到废纸篓 |
| restore | restaurar | restaurar | wiederherstellen | restaurer | 復元 | 복원 | 恢复 |
| delete forever | eliminar definitivamente | excluir permanentemente | endgültig löschen | supprimer définitivement | 完全に削除 | 영구 삭제 | 永久删除 |
| favorite (verb) | marcar como favorita | favoritar | als Favorit markieren | ajouter aux favoris | お気に入りに追加 | 즐겨찾기에 추가 | 收藏 |
| Favorites | Favoritas | Favoritos | Favoriten | Favoris | お気に入り | 즐겨찾기 | 收藏 |
| hidden card | tarjeta oculta | card oculto | ausgeblendete Karte | fiche masquée | 非表示のカード | 숨긴 카드 | 隐藏的卡片 |
| library | biblioteca | biblioteca | Bibliothek | bibliothèque | ライブラリ | 라이브러리 | 资料库 |
| vault | bóveda | cofre | Tresor | coffre | 保管庫 | 보관소 | 资料库 |
| Revisit | Repasar | Revisitar | Wiedersehen | Revoir | 振り返り | 다시 보기 | 回顾 |
| Shuffle | Aleatorio | Aleatório | Zufällig | Aléatoire | シャッフル | 셔플 | 随机 |
| Focus mode | Modo concentración | Modo foco | Fokusmodus | Mode concentration | 集中モード | 집중 모드 | 专注模式 |
| Refresh details | Actualizar detalles | Atualizar detalhes | Details aktualisieren | Actualiser les détails | 詳細を更新 | 세부 정보 새로 고침 | 刷新详情 |
| More like this | Más como esta | Mais como este | Ähnliche Karten | Plus de ce genre | 似たカード | 비슷한 카드 | 更多类似内容 |
| highlight | resaltado | destaque | Markierung | surlignage | ハイライト | 하이라이트 | 高亮 |
| reader | lector | leitor | Leseansicht | lecteur | リーダー | 리더 | 阅读器 |
| search | buscar | pesquisar | suchen | rechercher | 検索 | 검색 | 搜索 |
| filter | filtro | filtro | Filter | filtre | フィルタ | 필터 | 筛选 |
| command palette | paleta de comandos | paleta de comandos | Befehlspalette | palette de commandes | コマンドパレット | 명령 팔레트 | 命令面板 |
| keyboard shortcuts | atajos de teclado | atalhos de teclado | Tastenkürzel | raccourcis clavier | キーボードショートカット | 키보드 단축키 | 键盘快捷键 |
| upload | subir | enviar | hochladen | importer | アップロード | 업로드 | 上传 |
| import | importar | importar | importieren | importer | インポート | 가져오기 | 导入 |
| export | exportar | exportar | exportieren | exporter | エクスポート | 내보내기 | 导出 |
| Settings | Ajustes | Configurações | Einstellungen | Réglages | 設定 | 설정 | 设置 |
| Language | Idioma | Idioma | Sprache | Langue | 言語 | 언어 | 语言 |
| Theme | Tema | Tema | Design | Thème | テーマ | 테마 | 主题 |
| Free (plan) | Gratis | Grátis | Kostenlos | Gratuit | 無料 | 무료 | 免费 |
| Pro (plan) | Pro | Pro | Pro | Pro | Pro | Pro | Pro |
| subscription | suscripción | assinatura | Abo | abonnement | サブスクリプション | 구독 | 订阅 |
| card limit | límite de tarjetas | limite de cards | Kartenlimit | limite de fiches | カードの上限 | 카드 한도 | 卡片上限 |
| sign in | iniciar sesión | entrar | anmelden | se connecter | サインイン | 로그인 | 登录 |
| sign out | cerrar sesión | sair | abmelden | se déconnecter | サインアウト | 로그아웃 | 退出登录 |
| account | cuenta | conta | Konto | compte | アカウント | 계정 | 账户 |
| API key | clave de API | chave de API | API-Schlüssel | clé d’API | API キー | API 키 | API 密钥 |
| extension | extensión | extensão | Erweiterung | extension | 拡張機能 | 확장 프로그램 | 扩展 |
| Undo | Deshacer | Desfazer | Rückgängig | Annuler | 元に戻す | 실행 취소 | 撤销 |