diff --git a/.changeset/quiet-languages.md b/.changeset/quiet-languages.md
new file mode 100644
index 00000000..ca94d062
--- /dev/null
+++ b/.changeset/quiet-languages.md
@@ -0,0 +1,5 @@
+---
+"@workspace/web": patch
+---
+
+Add a persistent English/Chinese interface switch across setup, brand monitoring, and reports without changing collected content.
diff --git a/README.de.md b/README.de.md
new file mode 100644
index 00000000..8d35b7bd
--- /dev/null
+++ b/README.de.md
@@ -0,0 +1,119 @@
+
+
+# BeatGEO
+
+[English](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md) · [Deutsch](README.de.md) · [한국어](README.ko.md)
+
+**Die Open-Source-Alternative zu Profound für GEO & AEO.**
+
+Ein quelloffener GEO-/AEO-Arbeitsbereich von [BeatAPI](https://beatapi.io). Verfolge Markennennungen und Quellen in echten KI-Antworten, leite Verbesserungen ab und wiederhole dieselben Fragen, um Veränderungen zu vergleichen.
+
+[Schnellstart](#schnellstart) · [Ablauf](#ablauf) · [Funktionsstand](#funktionsstand) · [Roadmap](docs/roadmap.md) · [MIT-Lizenz](LICENSE.md)
+
+> **Lokales MVP / Quellcode-Vorschau.** Die Kette ChatGPT-Erfassung → Datenbank → Worker wurde in Live-Smoke-Tests geprüft. Andere Engines, kostenpflichtige Recherche und unbeaufsichtigter Dauerbetrieb haben eigene Prüfgrenzen. [Prüfprotokoll auf Englisch](docs/beat-geo/validation.md)
+
+## Von der Website zu wiederholbaren Fragen
+
+Gib deine Website und dein Geschäftsfeld an, bestätige die vorgeschlagenen Fragen und sammle Antworten in deinem angemeldeten Chrome-Profil. Prüfe die Belege, bevor du Änderungen planst.
+
+- **Kein leeres Fragenformular.** Website-Recherche erzeugt Vorschläge; Branchenvorlagen funktionieren auch ohne Recherche-API-Key.
+- **Originalbelege bleiben erhalten.** Antworten, sichtbare Quellenlinks und Erfassungsstatus sind nachvollziehbar. Ein Timeout zählt nicht als fehlende Sichtbarkeit.
+- **Verbesserungen erneut messen.** Speichere eine Maßnahme mit Ausgangswerten, dokumentiere ihre Umsetzung und wiederhole dieselben Fragen.
+- **Ein gemeinsames Produktsystem.** Marken, Fragen, Belege, Maßnahmen und Zeitplanung nutzen PostgreSQL und eine Warteschlange. BeatBrowser ist als fest versionierte Abhängigkeit enthalten.
+
+## Schnellstart
+
+Benötigt werden **Git, Node.js 24, pnpm 12.4.1, Docker mit Compose und Chrome**. Starte Docker vor der Einrichtung.
+
+```sh
+git clone https://github.com/BeatAPI/BeatGEO.git
+cd BeatGEO
+node scripts/local-doctor.mjs
+pnpm local:setup
+pnpm local:start
+```
+
+Öffne **http://localhost:3140/setup** und registriere den lokalen Administrator:
+
+1. **Recherche verbinden.** BeatAPI-Key unter Connections prüfen, Recherchemodell auswählen und speichern. Alternativ diesen Schritt überspringen und Branchenvorlagen nutzen.
+2. **Chrome verbinden.** Unter `chrome://extensions` den Entwicklermodus aktivieren und `.beat-geo/extension` entpackt laden. Verbundenes Profil auswählen und dort bei ChatGPT anmelden. Eine verbundene Erweiterung bestätigt keine Website-Anmeldung.
+3. **Marke anlegen.** Website eingeben → Fragen erzeugen und bestätigen → erste Erfassung → Antworten und Quellen prüfen → Verbesserung speichern → dieselben Fragen erneut testen.
+
+Das Setup richtet eine eigene lokale PostgreSQL-Datenbank und die Erweiterung ein. Kein zweiter Repository-Klon, kein zusätzliches BeatBrowser-Konto und keine globale CLI-Installation nötig. Eine vorhandene BeatBrowser-Verbindung kann verwendet werden; pro Chrome-Profil nur eine Erweiterungskopie aktivieren.
+
+**Für laufendes Monitoring muss die Umgebung laufen:** Computer wach und online halten; Docker/PostgreSQL, Chrome und `pnpm local:start` müssen aktiv bleiben. Der Dashboard-Tab darf geschlossen werden. Zum Neustart reicht `pnpm local:start`; die Datenbank nicht neu anlegen. Bei ausgeschaltetem oder offline befindlichem Computer findet keine Erfassung statt.
+
+[Ausführliche Einrichtung auf Chinesisch](docs/beat-geo/getting-started.md) · [Vorhandenes PostgreSQL einrichten, Chinesisch](docs/self-hosting.md)
+
+## Ablauf
+
+```mermaid
+flowchart LR
+ A[Website und Marke] --> B[Fragen erzeugen und bestätigen]
+ B --> C[KI-Antworten erfassen]
+ C --> D[Nennungen und Quellen prüfen]
+ D --> E[Maßnahme und Ausgangswerte speichern]
+ E --> F[Änderungen umsetzen]
+ F --> G[Dieselben Fragen wiederholen]
+ G --> D
+```
+
+BeatGEO verwaltet Produktdaten und Zeitplanung. **BeatBrowser** führt Aufgaben im angemeldeten Browser aus. **BeatAPI** kann Suche, Seitenabruf und Modellanalyse für Recherche und Optimierung bereitstellen.
+
+
+Den größeren Produktentwurf ansehen
+
+[](docs/product/flow.html)
+
+Den [interaktiven Entwurf](docs/product/flow.html) lokal öffnen. Er zeigt das **Zielbild** mit noch unfertigen Funktionen und synthetischen Beispielen, keine vollständig implementierte Oberfläche. [Funktionsübersicht](docs/product/features.csv)
+
+
+
+## Funktionsstand
+
+| Bereich | Aktueller Stand |
+| --- | --- |
+| ChatGPT-Webantworten | Live-Smoke-Tests über den Worker erfolgreich; Antworten und sichtbare Quellen werden gespeichert. Website-Änderungen erfordern Wartung. |
+| Gemini-Webantworten | Adapter vorhanden; zuverlässiger Abschluss im Hintergrund noch nicht bestätigt. Fehler werden angezeigt. |
+| Weitere Antwortsysteme | Keine Zusage vollständiger Browser-Unterstützung. Bitte die Roadmap prüfen. |
+| Fragen und Verbesserungen | Fragen auf Englisch/Chinesisch, Branchenvorlagen, gespeicherte Ausgangswerte und vergleichbare Wiederholungstests. |
+| Planung und Speicherung | PostgreSQL + pg-boss. Der nächste Lauf wurde im Live-Test eingereiht; unbeaufsichtigter Dauerbetrieb bleibt zu prüfen. Kein SQLite. |
+| BeatAPI-Recherche | Suche, Seitenabruf und Modellintegration implementiert; Abnahme mit echtem Key und kostenpflichtigen Aufrufen ausstehend. |
+| Technisches Website-Audit und SEO-Daten | Geplant oder nicht angeschlossen. Keine behaupteten Keyword-Volumina, Backlink-Indizes oder echten KI-Fragevolumina. |
+
+Die README gibt es auf Englisch, Chinesisch, Japanisch, Deutsch und Koreanisch. **Die Produktoberfläche lässt sich über EN / 中文 zwischen Englisch und Chinesisch umschalten. Die Auswahl wird im Browser gespeichert.** Fragen, Markennamen und gesammelte Antworten bleiben in ihrer ursprünglichen Sprache. Die README-Übersetzungen bedeuten keine fünfsprachige Oberfläche.
+
+## Was die Kennzahlen bedeuten
+
+**Nennungsrate = gültige Antworten mit Markennennung ÷ gültige, auswertbare Antworten.** Fehler, ausstehende Versuche, Timeouts und fehlende Anmeldungen zählen nicht zum Nenner. Gleiche observation IDs werden dedupliziert.
+
+Bei 100 geplanten Versuchen, 80 gültigen Antworten und 24 Nennungen beträgt die Rate **30 % (24/80)**. Die Abschlussquote ist eine eigene Kennzahl. Der Arbeitsbereich wertet gespeicherte Beobachtungen aus; eine vollständige Abdeckung aller geplanten Versuche muss separat umgesetzt werden. Eine Nennung ist keine Empfehlung, eine Kontostichprobe keine marktweite Sichtbarkeit. [Kennzahlendefinitionen](docs/metrics.md)
+
+## Wofür BeatAPI verwendet wird
+
+Key und Recherchemodell unter **Connections** speichern. BeatAPI kann Geschäfts- und Wettbewerbsinformationen suchen, Seiten lesen, Fragen generieren und Belege analysieren. Keys werden verschlüsselt gespeichert und nicht an den Browser-Client zurückgegeben. Aktuell gelten sie für die lokale Instanz, nicht getrennt pro Mandant.
+
+Browser-Erfassung, Branchenvorlagen und Grundstatistiken benötigen keine kostenpflichtigen Modellaufrufe. Recherche und Generierung verbrauchen das API-Guthaben des konfigurierten Anbieters; für Web-Erfassung gelten die Grenzen deines Website-Kontos. Spezielle SEO-Datenendpunkte sind noch nicht angeschlossen. [Integrationskonzept auf Chinesisch](docs/beat-geo/beatapi-product-loop.md)
+
+## Forks und Teamnutzung
+
+Ein Fork von BeatGEO enthält weiterhin die **fest versionierte BeatBrowser-Abhängigkeit**. Er erzeugt keinen zusätzlichen BeatBrowser-Fork. Das Setup lädt die festgelegte Revision und bereitet die Erweiterung vor. Nur wer BeatBrowser selbst ändern möchte, muss es separat forken und Abhängigkeit sowie Lockfile aktualisieren.
+
+Lokale Installationen haben getrennte Konten, Datenbanken, Keys und Chrome-Sitzungen. `.env`, `.beat-geo/`, Browserbelege und Zugangsdaten nicht committen. Dienste nur auf Loopback betreiben und nicht über öffentliche Tunnel freigeben. [Datenschutz](docs/privacy.md)
+
+## Entwicklung und Referenzen
+
+```sh
+pnpm install --frozen-lockfile
+pnpm lint
+pnpm test:browser
+pnpm test
+```
+
+Lockfile und Lieferkettenschutz beibehalten; Änderungen über PRs einreichen. Nur das Browser-Lab testen: `pnpm browser:lab`, dann `http://127.0.0.1:4318`. Diese separate Ansicht besitzt kein Produktdatenbank- oder Kontosystem.
+
+[Architektur auf Chinesisch](docs/beat-geo/architecture-and-ego.md) · [Kennzahlen](docs/metrics.md) · [Roadmap](docs/roadmap.md) · [Validierung](docs/beat-geo/validation.md)
+
+## Lizenz und Danksagung
+
+[MIT](LICENSE.md). BeatGEO basiert auf dem MIT-lizenzierten Elmo und erhält dessen Copyright und Historie. [BeatBrowser](https://github.com/BeatAPI/BeatBrowser) ist unter MIT enthalten; siehe [Drittanbieterhinweise](THIRD_PARTY_NOTICES.md). Andere untersuchte GEO-Projekte sind Referenzen, nicht automatisch vollständig integrierte Implementierungen. BeatGEO ist unabhängig von Profound.
diff --git a/README.ja.md b/README.ja.md
new file mode 100644
index 00000000..10ccb3f4
--- /dev/null
+++ b/README.ja.md
@@ -0,0 +1,119 @@
+
+
+# BeatGEO
+
+[English](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md) · [Deutsch](README.de.md) · [한국어](README.ko.md)
+
+**GEO と AEO のための、オープンソースの Profound 代替ツール。**
+
+[BeatAPI](https://beatapi.io) が開発するオープンソースの GEO / AEO ワークスペースです。実際の AI 回答からブランドへの言及と引用を追跡し、改善事項を記録。同じ質問で再測定して変化を比較します。
+
+[クイックスタート](#クイックスタート) · [ワークフロー](#ワークフロー) · [対応状況](#対応状況) · [ロードマップ](docs/roadmap.md) · [MIT ライセンス](LICENSE.md)
+
+> **ローカル MVP / ソースプレビュー版。** ChatGPT の収集 → データベース → worker は実環境のスモークテストを通過しています。他のエンジン、有料リサーチ、長期間の無人運用は別途検証が必要です。[検証記録(英語)](docs/beat-geo/validation.md)
+
+## Web サイトから、継続して測定できる質問へ
+
+サイトと事業内容を入力し、提案された質問を確認します。ログイン済みの Chrome で回答を集め、根拠を見てから改善内容を決めます。
+
+- **質問をゼロから書く必要はありません。** サイトのリサーチから質問案を生成。リサーチ用 API キーがなくても業種別テンプレートを利用できます。
+- **回答と根拠を残します。** 元の回答、表示された引用リンク、収集状態を確認できます。タイムアウトは露出ゼロとして集計しません。
+- **改善後も同じ条件で測定します。** アクションと基準値を保存し、実施内容を記録した後、同じ質問を再実行します。
+- **製品データを一元管理します。** ブランド、質問、根拠、改善、スケジュールは PostgreSQL と単一のキューを使用。BeatBrowser はバージョン固定の依存関係として同梱されます。
+
+## クイックスタート
+
+**Git、Node.js 24、pnpm 12.4.1、Docker(Compose を含む)、Chrome** を用意し、Docker を起動してください。
+
+```sh
+git clone https://github.com/BeatAPI/BeatGEO.git
+cd BeatGEO
+node scripts/local-doctor.mjs
+pnpm local:setup
+pnpm local:start
+```
+
+**http://localhost:3140/setup** を開き、ローカル管理者を登録します。
+
+1. **リサーチを設定。** Connections で BeatAPI キーを検証し、リサーチモデルを選択して保存します。スキップして業種別の初期質問を使うこともできます。
+2. **Chrome を接続。** `chrome://extensions` でデベロッパーモードを有効にし、`.beat-geo/extension` を読み込みます。接続したプロファイルを選び、同じプロファイルで ChatGPT にログインしてください。拡張機能の接続とサイトへのログインは別です。
+3. **ブランドを作成。** サイトを入力 → 質問を生成して確認 → 初回収集 → 回答と引用を確認 → 改善を記録 → 同じ質問で再測定します。
+
+セットアップが専用のローカル PostgreSQL と拡張機能を準備します。BeatBrowser を別にクローンしたり、別アカウントやグローバル CLI を用意したりする必要はありません。既存の接続も利用できますが、同じ Chrome プロファイルで拡張機能を二重に有効化しないでください。
+
+**継続監視には稼働中の環境が必要です。** パソコンをスリープさせず、ネット接続、Docker/PostgreSQL、Chrome、`pnpm local:start` を維持します。ダッシュボードのタブは閉じても構いません。再開時は `pnpm local:start` を実行し、データベースは作り直さないでください。オフライン中の収集はできません。
+
+[詳しい導入手順(中国語)](docs/beat-geo/getting-started.md) · [既存 PostgreSQL の設定(中国語)](docs/self-hosting.md)
+
+## ワークフロー
+
+```mermaid
+flowchart LR
+ A[サイトとブランド] --> B[質問を生成して確認]
+ B --> C[AI 回答を収集]
+ C --> D[言及と引用を確認]
+ D --> E[改善と基準値を保存]
+ E --> F[変更を実施]
+ F --> G[同じ質問で再測定]
+ G --> D
+```
+
+BeatGEO が製品データとスケジュールを管理し、**BeatBrowser** がログイン済みブラウザで処理を実行します。**BeatAPI** はリサーチ向けの検索、ページ読み取り、モデル分析を提供できます。
+
+
+製品全体の構想を見る
+
+[](docs/product/flow.html)
+
+[対話型の構想図](docs/product/flow.html)はローカルで開けます。**目標とする製品像**であり、未完成の機能や合成例を含みます。実装済み画面ではありません。[機能一覧](docs/product/features.csv)
+
+
+
+## 対応状況
+
+| 項目 | 現在の状態 |
+| --- | --- |
+| ChatGPT の Web 回答 | worker を通す実環境テストに成功。回答と表示された引用を保存します。サイト変更への継続対応は必要です。 |
+| Gemini の Web 回答 | アダプターは存在しますが、バックグラウンドでの回答完了の安定性は未検証です。失敗は表示されます。 |
+| その他の回答エンジン | Web 収集の全面対応は保証していません。ロードマップを確認してください。 |
+| 質問と改善 | 英語・中国語の質問生成、業種別テンプレート、改善の基準値保存、同一質問での再測定。 |
+| スケジュールと保存 | PostgreSQL + pg-boss。実測では次回処理の予約まで確認。長期間の無人運用と SQLite には未対応です。 |
+| BeatAPI リサーチ | 検索・読み取り・モデル連携は実装済み。実キーによる有料呼び出しの受け入れ検証は未完了です。 |
+| サイト技術監査・SEO データ | 計画中または未接続。検索量、被リンク索引、実際の AI 質問量を保有しているとは表明しません。 |
+
+README は英語、中国語、日本語、ドイツ語、韓国語で提供します。**製品 UI は EN / 中文で英語と中国語を切り替えられ、選択はブラウザーに保存されます。** 質問、ブランド名、収集した回答の言語は変更しません。README の翻訳は UI の五言語対応を意味しません。
+
+## 指標の読み方
+
+**言及率 = ブランドに言及した有効回答数 ÷ 評価可能な有効回答数。** 失敗、待機、タイムアウト、未ログインは分母から除外し、同じ observation ID は重複集計しません。
+
+100 回を予定し、80 回の有効回答中 24 回に言及があれば **30%(24/80)** です。完了率は別の指標です。現在のワークベンチは保存済み観測を集計し、予定した全試行の網羅率は別途実装が必要です。言及は必ずしも推薦ではなく、単一アカウントのサンプルは市場全体の露出ではありません。[指標定義](docs/metrics.md)
+
+## BeatAPI の使いどころ
+
+**Connections** でキーとリサーチモデルを設定します。事業・競合情報の検索、ページ読み取り、質問生成、収集した根拠の分析に利用できます。キーは暗号化して保存され、ブラウザ側に返されません。現在はローカルインスタンスで共有する認証情報であり、テナント別管理ではありません。
+
+ブラウザ収集、業種テンプレート、基本集計には有料モデル呼び出しは不要です。リサーチと生成は設定した API の残高を消費し、Web 収集にはサイトアカウント側の制限が適用されます。SEO 専用データ API はまだ接続されていません。[連携設計(中国語)](docs/beat-geo/beatapi-product-loop.md)
+
+## Fork とチーム利用
+
+BeatGEO を fork すると **BeatBrowser の固定バージョン依存関係**が残ります。BeatBrowser の別 fork が自動生成されるわけではありません。セットアップ時に指定版を取得して拡張機能を準備します。BeatBrowser 自体を変更する場合だけ別途 fork し、依存関係とロックファイルを更新してください。
+
+各ローカル環境のアカウント、DB、キー、Chrome セッションは独立しています。`.env`、`.beat-geo/`、ブラウザ証拠データや認証情報をコミットしないでください。サービスは loopback に限定し、公開トンネルに接続しないでください。[プライバシー](docs/privacy.md)
+
+## 開発と参考資料
+
+```sh
+pnpm install --frozen-lockfile
+pnpm lint
+pnpm test:browser
+pnpm test
+```
+
+ロックファイルとサプライチェーン保護を維持し、変更は PR で提出します。ブラウザラボだけを試す場合は `pnpm browser:lab` → `http://127.0.0.1:4318`。この単独画面には製品の DB・アカウント機能はありません。
+
+[アーキテクチャ(中国語)](docs/beat-geo/architecture-and-ego.md) · [指標](docs/metrics.md) · [ロードマップ](docs/roadmap.md) · [検証](docs/beat-geo/validation.md)
+
+## ライセンスと謝辞
+
+[MIT](LICENSE.md)。BeatGEO は MIT ライセンスの Elmo を基にし、元の著作権と履歴を保持しています。[BeatBrowser](https://github.com/BeatAPI/BeatBrowser) も MIT で同梱されます。[第三者の権利表示](THIRD_PARTY_NOTICES.md)。調査した他の GEO プロジェクトは参考資料であり、その全実装を含むとは限りません。BeatGEO は Profound とは独立したプロジェクトです。
diff --git a/README.ko.md b/README.ko.md
new file mode 100644
index 00000000..bba40ef7
--- /dev/null
+++ b/README.ko.md
@@ -0,0 +1,119 @@
+
+
+# BeatGEO
+
+[English](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md) · [Deutsch](README.de.md) · [한국어](README.ko.md)
+
+**GEO와 AEO를 위한 오픈 소스 Profound 대안.**
+
+[BeatAPI](https://beatapi.io)가 만드는 오픈 소스 GEO / AEO 워크스페이스입니다. 실제 AI 답변에서 브랜드 언급과 인용을 추적하고, 개선 사항을 실행한 뒤 같은 질문으로 다시 측정해 변화를 비교합니다.
+
+[빠른 시작](#빠른-시작) · [제품 흐름](#제품-흐름) · [지원 현황](#지원-현황) · [로드맵](docs/roadmap.md) · [MIT 라이선스](LICENSE.md)
+
+> **로컬 MVP / 소스 미리보기 버전.** ChatGPT 수집 → 데이터베이스 → worker 경로는 실제 스모크 테스트를 통과했습니다. 다른 엔진, 유료 리서치, 장기 무인 운영은 별도로 검증해야 합니다. [검증 기록 — 영어](docs/beat-geo/validation.md)
+
+## 웹사이트 하나에서 반복 가능한 질문 세트로
+
+웹사이트와 사업 정보를 입력하고 제안된 질문을 확인하세요. 로그인된 Chrome에서 답변을 수집한 뒤, 근거를 보고 무엇을 개선할지 결정합니다.
+
+- **질문을 처음부터 쓰지 않아도 됩니다.** 웹사이트 리서치로 질문 초안을 만들거나, 리서치 API 키 없이 업종별 템플릿을 사용할 수 있습니다.
+- **원본 근거를 남깁니다.** 답변, 화면에 표시된 출처 링크, 수집 상태를 확인합니다. 시간 초과를 노출 0으로 계산하지 않습니다.
+- **개선 결과를 다시 측정합니다.** 실행 항목과 기준값을 저장하고 변경 내용을 기록한 후 같은 질문을 재실행합니다.
+- **하나의 제품 시스템을 사용합니다.** 브랜드, 질문, 근거, 개선 항목과 일정은 PostgreSQL 및 단일 큐를 공유합니다. BeatBrowser는 버전이 고정된 의존성으로 포함됩니다.
+
+## 빠른 시작
+
+**Git, Node.js 24, pnpm 12.4.1, Compose가 포함된 Docker, Chrome**을 준비하고 Docker를 먼저 실행하세요.
+
+```sh
+git clone https://github.com/BeatAPI/BeatGEO.git
+cd BeatGEO
+node scripts/local-doctor.mjs
+pnpm local:setup
+pnpm local:start
+```
+
+**http://localhost:3140/setup**을 열어 로컬 관리자를 등록합니다.
+
+1. **리서치 연결.** Connections에서 BeatAPI 키를 검증하고 리서치 모델을 선택해 저장합니다. 건너뛰고 업종별 기본 질문을 사용해도 됩니다.
+2. **Chrome 연결.** `chrome://extensions`에서 개발자 모드를 켜고 `.beat-geo/extension`을 압축 해제된 확장 프로그램으로 로드합니다. 연결된 프로필을 선택하고 같은 프로필에서 ChatGPT에 로그인하세요. 확장 프로그램 연결과 웹사이트 로그인은 별개입니다.
+3. **브랜드 생성.** 웹사이트 입력 → 질문 생성 및 확인 → 첫 수집 → 답변과 인용 확인 → 개선 항목 저장 → 같은 질문으로 재측정합니다.
+
+설치 과정에서 전용 로컬 PostgreSQL과 확장 프로그램을 준비합니다. 별도의 저장소 복제, BeatBrowser 계정, 전역 CLI 설치는 필요하지 않습니다. 기존 BeatBrowser 연결을 재사용할 수 있지만, Chrome 프로필 하나에는 확장 프로그램 한 개만 활성화하세요.
+
+**지속적인 모니터링에는 실행 중인 환경이 필요합니다.** 컴퓨터가 절전 모드에 들어가지 않도록 하고 인터넷 연결, Docker/PostgreSQL, Chrome, `pnpm local:start`를 유지하세요. 대시보드 탭은 닫아도 됩니다. 다음에는 `pnpm local:start`로 다시 시작하며, 데이터베이스를 새로 만들지 않습니다. 컴퓨터가 오프라인이면 수집도 중단됩니다.
+
+[상세 설치 안내 — 중국어](docs/beat-geo/getting-started.md) · [기존 PostgreSQL 설정 — 중국어](docs/self-hosting.md)
+
+## 제품 흐름
+
+```mermaid
+flowchart LR
+ A[웹사이트와 브랜드] --> B[질문 생성 및 확인]
+ B --> C[AI 답변 수집]
+ C --> D[언급과 인용 확인]
+ D --> E[개선 항목과 기준값 저장]
+ E --> F[변경 사항 적용]
+ F --> G[같은 질문으로 재측정]
+ G --> D
+```
+
+BeatGEO는 제품 데이터와 일정을 관리합니다. **BeatBrowser**는 로그인된 브라우저에서 작업을 실행합니다. **BeatAPI**는 리서치와 개선을 위한 검색, 페이지 읽기, 모델 분석을 제공할 수 있습니다.
+
+
+전체 제품 구상 보기
+
+[](docs/product/flow.html)
+
+[인터랙티브 설계도](docs/product/flow.html)는 로컬에서 열 수 있습니다. 미완성 기능과 합성 예시를 포함한 **목표 제품 구상**이며, 모든 기능이 구현된 화면은 아닙니다. [기능 목록](docs/product/features.csv)
+
+
+
+## 지원 현황
+
+| 영역 | 현재 상태 |
+| --- | --- |
+| ChatGPT 웹 답변 수집 | 실제 worker 경로 테스트 통과. 답변과 표시된 인용을 저장합니다. 웹사이트 변경에 따른 유지보수는 필요합니다. |
+| Gemini 웹 답변 수집 | 어댑터는 있지만 백그라운드 답변 완료의 안정성은 아직 검증되지 않았습니다. 실패 상태는 표시됩니다. |
+| 다른 답변 엔진 | 모든 엔진의 브라우저 수집을 지원한다고 보장하지 않습니다. 로드맵을 확인하세요. |
+| 질문 및 개선 | 영어·중국어 질문 생성, 업종별 템플릿, 개선 기준값 저장, 같은 질문으로 재측정. |
+| 일정 및 저장 | PostgreSQL + pg-boss. 실제 검증에서 다음 작업이 예약됨을 확인했으며 장기 무인 운영은 미검증입니다. SQLite는 지원하지 않습니다. |
+| BeatAPI 리서치 | 검색·읽기·모델 연동 구현. 실제 키를 사용한 유료 호출 검증은 아직 완료되지 않았습니다. |
+| 사이트 기술 진단 및 SEO 데이터 | 계획 중이거나 미연동입니다. 키워드 검색량, 백링크 색인, 실제 AI 질문량을 제공한다고 주장하지 않습니다. |
+
+README는 영어, 중국어, 일본어, 독일어, 한국어로 제공합니다. **제품 UI는 EN / 中文으로 영어와 중국어를 전환할 수 있으며 선택은 브라우저에 저장됩니다.** 질문, 브랜드명, 수집한 답변은 원래 언어를 유지합니다. README 번역이 UI의 5개 언어 지원을 뜻하지는 않습니다.
+
+## 지표 해석
+
+**언급률 = 브랜드를 언급한 유효 답변 수 ÷ 평가 가능한 유효 답변 수.** 실패, 대기, 시간 초과, 미로그인 시도는 분모에서 제외하고 동일한 observation ID는 중복 집계하지 않습니다.
+
+100회를 계획했더라도 유효 답변 80개 중 24개가 브랜드를 언급했다면 **30% (24/80)**입니다. 완료율은 별도 지표입니다. 현재 워크벤치는 저장된 관측을 집계하며, 계획된 전체 시도의 커버리지는 별도로 구현해야 합니다. 언급은 반드시 추천을 의미하지 않으며, 한 계정의 표본은 전체 시장 노출이 아닙니다. [지표 정의](docs/metrics.md)
+
+## BeatAPI 활용
+
+**Connections**에서 키와 리서치 모델을 설정합니다. 사업·경쟁사 정보 검색, 페이지 읽기, 질문 생성, 수집 근거 분석에 사용할 수 있습니다. 키는 암호화해 저장하고 브라우저 클라이언트에 반환하지 않습니다. 현재는 테넌트별 키가 아닌 로컬 인스턴스 공용 자격 증명입니다.
+
+브라우저 수집, 업종 템플릿, 기본 통계에는 유료 모델 호출이 필요하지 않습니다. 리서치와 생성은 설정한 제공자의 API 잔액을 사용하며, 웹 수집에는 웹사이트 계정의 제한이 적용됩니다. SEO 전용 데이터 API는 아직 연결되지 않았습니다. [연동 설계 — 중국어](docs/beat-geo/beatapi-product-loop.md)
+
+## Fork 및 팀 사용
+
+BeatGEO를 fork하면 **버전이 고정된 BeatBrowser 의존성**이 유지됩니다. BeatBrowser 저장소까지 별도로 fork되는 것은 아닙니다. 설치 시 지정된 버전을 다운로드해 확장 프로그램을 준비합니다. BeatBrowser 자체를 수정할 개발자만 별도로 fork하고 의존성과 잠금 파일을 업데이트하면 됩니다.
+
+각 로컬 설치의 계정, 데이터베이스, 키, Chrome 세션은 독립적입니다. `.env`, `.beat-geo/`, 브라우저 근거 데이터와 자격 증명을 커밋하지 마세요. 서비스는 loopback에만 바인딩하고 공개 터널에 노출하지 마세요. [개인정보 안내](docs/privacy.md)
+
+## 개발 및 참고
+
+```sh
+pnpm install --frozen-lockfile
+pnpm lint
+pnpm test:browser
+pnpm test
+```
+
+잠금 파일과 공급망 보호 설정을 유지하고 PR로 변경을 제출합니다. 브라우저 랩만 보려면 `pnpm browser:lab`을 실행한 뒤 `http://127.0.0.1:4318`을 엽니다. 이 독립 화면에는 제품 데이터베이스나 계정 시스템이 없습니다.
+
+[아키텍처 — 중국어](docs/beat-geo/architecture-and-ego.md) · [지표](docs/metrics.md) · [로드맵](docs/roadmap.md) · [검증](docs/beat-geo/validation.md)
+
+## 라이선스 및 감사
+
+[MIT](LICENSE.md). BeatGEO는 MIT 라이선스의 Elmo를 기반으로 하며 원 저작권과 이력을 보존합니다. [BeatBrowser](https://github.com/BeatAPI/BeatBrowser)도 MIT로 포함됩니다. [서드파티 고지](THIRD_PARTY_NOTICES.md)를 참고하세요. 조사한 다른 GEO 프로젝트는 참고 자료이며 모든 구현을 포함한다는 뜻은 아닙니다. BeatGEO는 Profound와 독립적인 프로젝트입니다.
diff --git a/README.md b/README.md
index 2dccb14e..b96f22db 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,29 @@
+
+
# BeatGEO
-**Open-source AI visibility monitoring and optimization, by [BeatAPI](https://beatapi.io).**
+[English](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md) · [Deutsch](README.de.md) · [한국어](README.ko.md)
-从真实 AI 回答出发,了解品牌在哪里被提及、哪些页面被引用、哪里需要改进。使用 [BeatBrowser](https://github.com/BeatAPI/BeatBrowser) 在独立后台工作区采集网页回答,保留证据,明确显示超时与失败。
+**The open-source Profound alternative for GEO & AEO.**
-> **MVP / 开发预览。** 本地浏览器工作台、统一数据库/队列、优化任务与同组复测可运行,已完成一次 PostgreSQL→worker→真实 ChatGPT 验收。网站技术诊断、长期无人值守稳定性和多设备调度仍待完成。没有托管 SaaS、已发布 SDK 或全部平台已稳定支持的承诺。
+An open-source GEO / AEO workspace by [BeatAPI](https://beatapi.io). Track brand mentions and citations in real AI answers, turn gaps into actions, and rerun the same questions to compare results.
-## Product flow
+[Quick start](#quick-start) · [Product workflow](#the-product-workflow) · [Current support](#current-support) · [Roadmap](docs/roadmap.md) · [MIT license](LICENSE.md)
-这是目标产品链路,**不是所有步骤都已实现**。详见 [路线图](docs/roadmap.md) 和 [67 项功能表](docs/product/features.csv)。
+> **Local MVP / source preview.** The ChatGPT collection → database → worker path has passed live smoke tests. Other engines, paid research and unattended operation have separate acceptance boundaries. [Validation log](docs/beat-geo/validation.md)
-```mermaid
-flowchart TD
- A[输入网站与业务目标] --> B[确认品牌、受众与竞品]
- B --> C[网站初检]
- B --> D[生成并确认监测问题]
- D --> E[BeatBrowser 后台网页采集]
- C --> F[基线:诊断与实测可见度]
- E --> F
- F --> G[定位页面、事实与引用差距]
- G --> H[生成优化任务与草稿]
- H --> I[人工审核与实施]
- I --> J[同组问题定时复测]
- J --> K[比较变化与报告]
- K --> G
-```
+## From a website to a repeatable question set
-[](docs/product/flow.html)
+Start with your website and business. Review suggested monitoring questions, collect answers in your signed-in Chrome, and inspect the evidence before deciding what to change.
-下载仓库后,用浏览器打开 [交互蓝图](docs/product/flow.html),可点击步骤、切换 BeatAPI / AI 图片业务、查看指标和搜索功能。图中数字为合成示例。
+- **Start with questions, not a blank editor.** Website research generates a draft question set; industry templates work without a research API key.
+- **Keep the original evidence.** Inspect answers, visible source links and collection status. A timeout is shown as a timeout, not zero exposure.
+- **Make changes measurable.** Save an optimization action and its baseline, record what you implemented, then retest the same questions.
+- **Use one local workspace.** Brands, questions, evidence, actions and scheduling share PostgreSQL and one queue. BeatBrowser is included as a pinned dependency.
## Quick start
-需要 **Git、Node.js 24、pnpm 12.4.1、Docker 和 Chrome**。BeatBrowser 固定版本已作为依赖带入,无需另外克隆或安装全局 CLI。
+Prepare **Git, Node.js 24, pnpm 12.4.1, Docker with Compose, and Chrome**. Start Docker before setup.
```sh
git clone https://github.com/BeatAPI/BeatGEO.git
@@ -42,105 +33,75 @@ pnpm local:setup
pnpm local:start
```
-### Fork 和团队试用
-
-Fork 本仓库会保留 BeatBrowser 的固定版本依赖,**不会自动创建一个 BeatBrowser 仓库的 fork**。`pnpm local:setup` 从 GitHub 下载锁定的 BeatBrowser 源码,并把扩展准备到 `.beat-geo/extension`。使用者只需克隆 BeatGEO;首次安装需要联网,Chrome 扩展和网站登录需手动完成。
-
-```mermaid
-flowchart LR
- A[Fork 或克隆 BeatGEO] --> B[local:setup]
- B --> C[安装锁定的 BeatBrowser]
- B --> D[创建本机独立 PostgreSQL]
- C --> E[Chrome 加载扩展并登录目标网站]
- D --> F[local:start]
- E --> G[创建品牌与首次采集]
- F --> G
-```
-
-团队成员各自在本机安装时,账号、数据库、API Key 和 Chrome 登录态相互独立,不会共享维护者的数据。已有 BeatBrowser 的成员可复用连接,不要同时启用两份扩展;如需本次的任务清理功能,请使用本仓库依赖的扩展版本。修改 BeatBrowser 源码的开发者才需要另外 fork 它,再显式更新 BeatGEO 的依赖和锁文件;依赖不会自动追踪主分支。
+Open **http://localhost:3140/setup** and register your local administrator:
-打开 **http://localhost:3140/setup**,注册本地管理员,然后按页面完成:
+1. **Connect research.** In Connections, enter a BeatAPI key, validate it, select a research model and save. You can skip research and use industry starter questions.
+2. **Connect Chrome.** Load `.beat-geo/extension` via `chrome://extensions` → Developer mode → Load unpacked. Select the connected profile and sign in to ChatGPT in that profile. An extension connection does not establish website login.
+3. **Create a brand.** Enter the website, generate and confirm questions, then start the first collection. Read the answers and citations, save an improvement, and compare a retest.
-1. **连接 BeatAPI**:输入 key → 验证并读取模型 → 选择研究模型 → 保存。网站自动研究需要研究提供方;Key 加密保存后持续复用。没有 Key 也能选择行业生成备用模板题单。
-2. **连接 Chrome**:已有 BeatBrowser 直接复用;没有时,在 `chrome://extensions` 开启开发者模式,加载 `.beat-geo/extension` 文件夹。选择已连接配置,并在同一配置登录 ChatGPT。浏览器要求用户确认安装,连接成功不代表网站已经登录。
-3. **创建品牌**:填写网站 → 研究业务并生成问题 → 确认题单 → 首次采集 → 查看回答与引用 → 记录优化 → 同组复测。
-4. **保持运行**:可关闭网页,电脑需唤醒、联网,Docker、Chrome 和 `local:start` 需持续运行。再次启动保留数据库及已保存 Key;本地版不是托管的全天监控。
+The installer prepares a dedicated local PostgreSQL database and the included extension. No second repository clone, BeatBrowser account or global CLI installation is needed. Existing BeatBrowser connections can be reused; enable only one extension copy per profile.
-[完整用户流程:安装、账号、Key、浏览器、费用和持续运行](docs/beat-geo/getting-started.md)。
+**To keep monitoring:** leave the computer awake and online, with Docker/PostgreSQL, Chrome and `pnpm local:start` running. You can close the dashboard tab. Restart with `pnpm local:start`; do not recreate the database. This local edition does not provide monitoring while your computer is offline.
-`local:setup` 创建专用 PostgreSQL 容器和卷(loopback 55440)、独立随机密钥及两个忽略提交的环境文件。不会复用网站数据库;已有配置会拒绝覆盖。自动安装中断后可运行 `pnpm local:setup --resume`,它只接受未改动的自动配置。`local:start` 同时运行网页(3140)、采集服务(4319)和 worker;关闭终端即停止调度。已被其他 Agent 使用的 BeatBrowser bridge 不会随 BeatGEO 退出。
+[Detailed onboarding — Chinese](docs/beat-geo/getting-started.md) · [Manual PostgreSQL setup — Chinese](docs/self-hosting.md)
-Chrome 连接成功不等于每个网站已登录。任务在独立 TaskSpace 中执行,无法创建时明确报错,不回退到日常标签页。首次采集也会显示超时、需要登录和连接失败,失败不计作零曝光。网站账号自身限制仍然适用。
+## The product workflow
-高级配置与已有 PostgreSQL 安装见 [自托管指南](docs/self-hosting.md)。**SQLite 尚未实现**:当前查询和 pg-boss 依赖 PostgreSQL。始终保持一套产品账户、数据库与队列,本地 JSON 只是采集证据缓存。
-
-单独体验浏览器工作台(没有数据库/账户功能):
-
-```sh
-pnpm install --frozen-lockfile
-pnpm browser:lab
-# http://127.0.0.1:4318
+```mermaid
+flowchart LR
+ A[Website and brand] --> B[Generate and confirm questions]
+ B --> C[Collect AI answers]
+ C --> D[Inspect mentions and citations]
+ D --> E[Save action and baseline]
+ E --> F[Implement changes]
+ F --> G[Retest the same questions]
+ G --> D
```
-## Questions without a blank page
+BeatGEO owns the product records and schedule. **BeatBrowser** handles the signed-in browser task. **BeatAPI** can supply search, page reading and model analysis for research and optimization.
-默认由网站研究识别品类、受众和主要用途,再套用 12 类通用意图生成具体问题。通常包含 9 个不带品牌名的发现问题、3 个品牌认知/替代品问题;分别标记用途,避免把点名提问当成自然发现。支持中英文,行业不限于 AI 产品。
+
+Explore the wider product blueprint
-没有研究 API key 时,可从 8 个行业预设中选择,也可补充业务品类、受众和用途,生成行业初始题库。页面会明确区分“网站研究”与“行业模板”;两者都不是平台真实用户搜索量。问题可以修改或关闭,确认后存入现有品牌问题库,按固定问题长期复测,不在每轮监测时重新生成。
+[](docs/product/flow.html)
-## MVP status
+Open [the interactive blueprint](docs/product/flow.html) locally to explore its steps. This is the **target product**, including unfinished features and synthetic examples. It is not a screenshot of completed functionality. [Feature inventory](docs/product/features.csv)
-| 能力 | 本版状态 |
-|---|---|
-| 自己的品牌与问题 | 可用;按品牌筛选,多个品牌不混算提及率 |
-| 独立后台工作区 | BeatBrowser TaskSpace,复用网站登录,无 Ego 依赖 |
-| ChatGPT 网页采集 | 完整 worker 链路真实验收通过,答案/引用入库并安排下一周期;页面变化仍需持续验收 |
-| Gemini 网页采集 | 适配器存在,后台长回答稳定性待验证;失败明确呈现 |
-| 原回答、来源、导出 | 可用;只读取可见链接,尚未完整展开来源侧栏 |
-| 超时与失败 | 单独显示原因、耗时、尝试次数,不当成零曝光 |
-| 样本提及率 | 可用;有效分母、版本标识和观察 ID 去重 |
-| 竞品声量 | 底座及纯函数可复用,浏览器工作台展示待接通 |
-| 账户、品牌、问题、调度 | 一套 PostgreSQL + pg-boss;每次尝试独立记录,成功数据原子提交 |
-| 优化行动与复测 | 保存机会和基线 → 记录实施 → 同队列复测 → 匹配比较及缺失数 |
-| 首次使用与连接 | 网页配置 key / 模型,检测及选择 Chrome;自动安装携带固定版本扩展 |
-| BeatAPI 研究 | Web Search / Read + 模型分析代码已接入;真实 key 的付费调用待验收 |
-| 网站技术审计、推荐识别、SEO 数据 | 待开发/接入;关键词量、外链、排名不冒充已有数据 |
+
-模拟测试不等于真实平台验收。[验收记录](docs/beat-geo/validation.md)
+## Current support
-## Measurement
+| Area | Status |
+| --- | --- |
+| ChatGPT website collection | Live smoke tests passed through the worker, with answers and visible citations saved. Website changes still require maintenance. |
+| Gemini website collection | Adapter present; background completion reliability remains unverified. Failures stay visible. |
+| Other answer engines | Full browser collection parity is not claimed. Check the roadmap before relying on an engine. |
+| Questions and optimization | English/Chinese question generation, industry templates, saved action baselines and comparable retests. |
+| Scheduling and storage | PostgreSQL + pg-boss. The next cycle was queued in live validation; long unattended operation has not been established. SQLite is not supported. |
+| BeatAPI research | Search/read/model integration is implemented; acceptance with a real key and billed calls remains pending. |
+| Technical site audit and SEO datasets | Planned or not connected. No claimed keyword volumes, backlink index or real AI prompt-volume dataset. |
-提及率 = **提及品牌的有效回答 / 可评分的有效回答**。超时、未登录、失败和等待不进入分母;相同 observation ID 去重。普通提及不等于推荐,当前账户的样本不等于全市场真实曝光。
+README translations: English, Chinese, Japanese, German and Korean. **Switch the dashboard between English and Chinese using EN / 中文. Your preference is saved in this browser.** Questions, brand names and collected answers retain their original language. The translated READMEs do not imply five-language UI support.
-例如计划 100 次,80 次有效,其中 24 次提及:提及率 **30%(24/80)**,计划覆盖率另报 **80%(80/100)**。当前工作台统计的是已保存观察的完成覆盖;完整计划覆盖率仍需独立实现。[指标定义](docs/metrics.md)
+## What the numbers mean
-## One product system
+**Mention rate = valid answers mentioning the brand ÷ valid, scorable answers.** Failed, pending, timed-out or signed-out attempts do not enter that denominator. Repeated observation IDs are deduplicated.
-```text
-BeatGEO UI / API / MCP
- └─ 品牌、问题、证据、行动与报告 / PostgreSQL
- └─ pg-boss 统一业务队列
- ├─ BeatBrowser:网页观测与引用
- └─ 研究与模型能力:分析、内容和报告
-```
+For example, 24 mentions in 80 valid answers means **30% (24/80)**, even if 100 attempts were planned. Completion coverage is separate. The workbench currently summarizes saved observations, not a complete planned-attempt coverage ledger. A mention is not necessarily a recommendation; this account's sample is not market-wide exposure. [Metric definitions](docs/metrics.md)
-当前 bridge 需同机 loopback 和共享 runner token。跨设备租约、离线补跑仍待接通;电脑离线不能声称持续采集。BeatAPI 能力账户与余额由现有网关负责,此仓库不另建钱包。
+## Where BeatAPI fits
-## BeatAPI integration
+Configure the research key and model in **Connections**. BeatAPI can search for business and competitor information, read pages, generate questions and analyze collected evidence. Saved keys are encrypted and are not returned to the browser client; credentials currently apply to the local instance rather than individual tenants.
-研究提供方可选择 BeatAPI:用 Web Search 发现业务与竞品资料,读取页面后生成建议,用文本模型分析已采集的证据。在 **Connections** 填写 key 和可用研究模型,也兼容服务器端 `BEATAPI_API_KEY` / `BEATAPI_RESEARCH_MODEL`。已保存的 key 不回传前端。浏览器采集与基础统计不要求购买模型 API。SEO 专用数据接口尚未接入。
+Browser collection, starter templates and basic statistics do not require paid model calls. Research and generation use the configured provider's API balance; website collection uses your website account and its limits. Dedicated SEO data endpoints are not connected yet. [API integration design — Chinese](docs/beat-geo/beatapi-product-loop.md)
-[产品闭环、API 接入位置与商业化设计](docs/beat-geo/beatapi-product-loop.md)。当前为实例管理员统一凭据,不是每租户独立 BYOK 或完整成本账单。
+## Forking and team use
-## Privacy and independence
+Forking BeatGEO keeps the **pinned BeatBrowser dependency**; it does not create a separate fork of BeatBrowser. Setup downloads that revision and prepares the extension. Developers changing BeatBrowser itself should fork it separately and explicitly update the dependency and lockfile.
-- 没有上游默认遥测项目、广告账号或客服项目;分析服务仅由操作者显式配置启用。
-- 产品使用 BeatGEO 品牌,无上游购买入口、客户评价或评分背书。原营销站入口停用,历史商业文案不属于本项目承诺。
-- cookies 不导出。证据默认位于 gitignored `.beat-geo/runs/`;没有新目录时可兼容读取已有 `.open-profound/runs/`,不自动搬迁或删除。
-- 本地服务只绑定 loopback,校验 Host/Origin 和提交令牌;不要通过公网隧道暴露。
-- 密钥兼容旧配置,改名不重置数据库或加密密钥。[隐私与迁移](docs/privacy.md)
+Each local installation has its own account, database, keys and Chrome sessions. Do not commit `.env`, `.beat-geo/`, browser evidence or credentials. Local services bind to loopback; do not expose them through a public tunnel. [Privacy](docs/privacy.md)
-## Development
+## Development and references
```sh
pnpm install --frozen-lockfile
@@ -149,10 +110,10 @@ pnpm test:browser
pnpm test
```
-保留锁文件与供应链保护,不启用归档的上游部署或发布工作流。通过本仓库 PR 维护。
+Keep the lockfile and supply-chain protections. Submit changes through PRs. To inspect only the browser lab, run `pnpm browser:lab` and open `http://127.0.0.1:4318`; that standalone view has no product database/account system.
-## License and acknowledgements
+[Architecture — Chinese](docs/beat-geo/architecture-and-ego.md) · [Metrics](docs/metrics.md) · [Roadmap](docs/roadmap.md) · [Validation](docs/beat-geo/validation.md)
-MIT。见 [LICENSE.md](LICENSE.md)、[第三方声明](THIRD_PARTY_NOTICES.md)。
+## License and acknowledgements
-基于 Elmo 的开源产品基础修改,保留原作者版权和历史。GeoScore、oneglanse、niubigeo、Ansvisor 等为研究及模块评估参考,不代表本版已经复制其全部代码。BeatGEO 与 Profound 无隶属关系。
+[MIT](LICENSE.md). BeatGEO builds on the MIT-licensed Elmo codebase and preserves its copyright and history. [BeatBrowser](https://github.com/BeatAPI/BeatBrowser) is included under MIT; see [third-party notices](THIRD_PARTY_NOTICES.md). Other researched GEO projects are references, not a claim that their full implementations are included. BeatGEO is independent of Profound.
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 00000000..f63e1e19
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,119 @@
+
+
+# BeatGEO
+
+[English](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md) · [Deutsch](README.de.md) · [한국어](README.ko.md)
+
+**面向 GEO 与 AEO 的开源 Profound 替代方案。**
+
+[BeatAPI](https://beatapi.io) 出品的开源 GEO / AEO 工作台。从真实 AI 回答中追踪品牌提及与引用,把发现的问题变成优化行动,再用同一组问题复测效果。
+
+[快速开始](#快速开始) · [产品闭环](#产品闭环) · [支持范围](#支持范围) · [路线图](docs/roadmap.md) · [MIT 许可](LICENSE.md)
+
+> **本地 MVP / 源码预览版。** ChatGPT 采集 → 数据库 → worker 已通过真实冒烟测试。其他平台、付费研究与长期无人值守分别验收。[验收记录](docs/beat-geo/validation.md)
+
+## 从一个网站,开始持续观察
+
+填写网站和业务,确认建议题单,再通过已经登录的 Chrome 采集回答。先看原始证据,再决定优化什么。
+
+- **不用从空白题库开始。** 网站研究自动生成初始问题;没有研究 API Key,也能使用行业模板。
+- **每个结果都有出处。** 查看原回答、可见引用和采集状态。超时就是超时,不会被当成零曝光。
+- **让优化可以复测。** 保存行动和基线,记录实际修改,再用相同问题比较前后变化。
+- **一套系统持续维护。** 品牌、问题、证据、行动和调度共用 PostgreSQL 与一个队列;BeatBrowser 已按固定版本集成。
+
+## 快速开始
+
+准备 **Git、Node.js 24、pnpm 12.4.1、Docker(含 Compose)和 Chrome**,先启动 Docker。
+
+```sh
+git clone https://github.com/BeatAPI/BeatGEO.git
+cd BeatGEO
+node scripts/local-doctor.mjs
+pnpm local:setup
+pnpm local:start
+```
+
+打开 **http://localhost:3140/setup**,注册本地管理员:
+
+1. **连接研究能力。** 在 Connections 填入 BeatAPI Key → 验证 → 选择研究模型 → 保存。也可跳过,使用行业模板题库。
+2. **连接 Chrome。** 在 `chrome://extensions` 开启开发者模式,加载 `.beat-geo/extension`。选择连接的配置,并在同一配置登录 ChatGPT。扩展连接成功不代表网站已经登录。
+3. **创建品牌。** 填网站 → 生成并确认问题 → 首次采集 → 查看回答和引用 → 保存优化行动 → 同组复测。
+
+安装脚本会准备独立 PostgreSQL 和内置扩展,不必再克隆 BeatBrowser、注册另一套账号或安装全局 CLI。已有 BeatBrowser 可以复用,同一 Chrome 配置只启用一份扩展。
+
+**持续监测需要持续运行:** 电脑保持唤醒和联网,Docker/PostgreSQL、Chrome、`pnpm local:start` 保持运行。可以关闭工作台网页;下次执行 `pnpm local:start` 即可,不要重建数据库。本地版不会在电脑离线时继续采集。
+
+[详细使用引导](docs/beat-geo/getting-started.md) · [已有 PostgreSQL 的手动配置](docs/self-hosting.md)
+
+## 产品闭环
+
+```mermaid
+flowchart LR
+ A[网站与品牌] --> B[生成并确认问题]
+ B --> C[采集 AI 回答]
+ C --> D[查看提及与引用]
+ D --> E[保存行动与基线]
+ E --> F[实施修改]
+ F --> G[同组问题复测]
+ G --> D
+```
+
+BeatGEO 管理产品数据和调度,**BeatBrowser** 执行已登录浏览器任务,**BeatAPI** 为研究与优化提供搜索、网页阅读和模型分析。
+
+
+展开完整产品蓝图
+
+[](docs/product/flow.html)
+
+下载后打开[交互蓝图](docs/product/flow.html)可查看各步骤。它描述的是**目标产品**,包含未完成功能与合成示例,不是已实现功能的截图。[功能清单](docs/product/features.csv)
+
+
+
+## 支持范围
+
+| 能力 | 当前状态 |
+| --- | --- |
+| ChatGPT 网页采集 | 真实 worker 链路冒烟测试通过,保存原回答与可见引用;网站变化仍需维护。 |
+| Gemini 网页采集 | 已有适配器,后台回答完成的稳定性仍待验证,失败明确显示。 |
+| 其他回答引擎 | 尚不承诺完整网页采集支持,请以路线图为准。 |
+| 题库与优化 | 中英文问题生成、行业模板、优化行动基线与同组复测。 |
+| 调度与存储 | PostgreSQL + pg-boss;实测已排入下一轮任务,长期无人值守仍待验收。不支持 SQLite。 |
+| BeatAPI 研究 | 搜索、阅读、模型分析已接入代码,真实 Key 的付费调用仍待验收。 |
+| 网站技术审计与 SEO 数据 | 待开发或接入,不提供虚构的关键词量、外链库或真实 AI 提问量。 |
+
+README 提供英文、中文、日文、德文和韩文。**产品界面通过 EN / 中文切换,选择会保存在当前浏览器。** 问题、品牌名和采集原文保留原语言。文档五语言不代表界面五语言。
+
+## 指标怎么计算
+
+**提及率 = 提及品牌的有效回答 ÷ 可评分的有效回答。** 超时、未登录、失败和等待不进入分母,相同 observation ID 去重。
+
+例如计划 100 次,80 次有效,其中 24 次提及,则提及率为 **30%(24/80)**。完成覆盖率应另报;当前工作台汇总已保存观察,完整的计划尝试覆盖账本仍需补齐。提及不等于推荐,当前账号的样本不代表全市场曝光。[指标定义](docs/metrics.md)
+
+## BeatAPI 用在哪里
+
+在 **Connections** 保存研究 Key 和模型。BeatAPI 可以搜索业务与竞品资料、读取网页、生成问题并分析已有证据。Key 加密保存,不回传前端;目前由本地实例统一使用,并非每租户独立凭据。
+
+浏览器采集、行业模板和基础统计不要求付费模型调用。研究与生成消耗配置的提供方 API 余额;网页采集使用你的网站账号及其额度。SEO 专用数据接口尚未接入。[API 接入设计](docs/beat-geo/beatapi-product-loop.md)
+
+## Fork 与团队使用
+
+Fork BeatGEO 会保留 **BeatBrowser 的固定版本依赖**,不会额外创建一个 BeatBrowser fork。安装时下载该版本并准备扩展。只有需要改 BeatBrowser 源码的开发者才需要单独 fork,再更新依赖和锁文件。
+
+每位成员本机的账号、数据库、Key 和 Chrome 会话相互独立。不要提交 `.env`、`.beat-geo/`、浏览器证据或凭据。本地服务绑定 loopback,不要通过公网隧道暴露。[隐私说明](docs/privacy.md)
+
+## 开发与参考
+
+```sh
+pnpm install --frozen-lockfile
+pnpm lint
+pnpm test:browser
+pnpm test
+```
+
+保留锁文件及供应链保护,通过 PR 维护。只体验浏览器实验工作台可执行 `pnpm browser:lab`,打开 `http://127.0.0.1:4318`;该独立页面不包含产品数据库与账号系统。
+
+[架构](docs/beat-geo/architecture-and-ego.md) · [指标](docs/metrics.md) · [路线图](docs/roadmap.md) · [验收](docs/beat-geo/validation.md)
+
+## 许可与致谢
+
+[MIT](LICENSE.md)。BeatGEO 基于 Elmo 的 MIT 开源代码,保留原作者版权与历史。[BeatBrowser](https://github.com/BeatAPI/BeatBrowser) 以 MIT 许可集成,详见[第三方声明](THIRD_PARTY_NOTICES.md)。其他研究过的 GEO 项目是参考来源,不代表其全部实现已包含。BeatGEO 与 Profound 无隶属关系。
diff --git a/apps/web/src/components/auth/auth-split-layout.tsx b/apps/web/src/components/auth/auth-split-layout.tsx
index f2d2b1b4..68a356ba 100644
--- a/apps/web/src/components/auth/auth-split-layout.tsx
+++ b/apps/web/src/components/auth/auth-split-layout.tsx
@@ -14,6 +14,7 @@
*/
import type { ReactNode } from "react";
+import { LanguageSwitch } from "@/components/language-switch";
import { Logo } from "@/components/logo";
interface AuthSplitLayoutProps {
@@ -40,7 +41,10 @@ export function AuthSplitLayout({ title, subtitle, children, pitch, footer }: Au
-
+
+
+
+
{title}
diff --git a/apps/web/src/components/auth/sales-panel.tsx b/apps/web/src/components/auth/sales-panel.tsx
index 907ff3a2..f3ad459f 100644
--- a/apps/web/src/components/auth/sales-panel.tsx
+++ b/apps/web/src/components/auth/sales-panel.tsx
@@ -1,26 +1,29 @@
import type { ReferralSource } from "@workspace/config/referrals";
+import { useT } from "@/lib/i18n";
type SalesPanelVariant = "local" | "cloud" | "self-hosted";
export function SalesPanel(_props: { variant: SalesPanelVariant; source: ReferralSource }) {
+ const t = useT();
return (
BeatGEO
-
Understand how AI answers mention and cite your brand.
+
{t("Understand how AI answers mention and cite your brand.")}
-
Inspect original answers and citations.
-
Compare brands and track questions over time.
-
Keep failed collections separate from missing mentions.
+
{t("Inspect original answers and citations.")}
+
{t("Compare brands and track questions over time.")}
+
{t("Keep failed collections separate from missing mentions.")}
- Open-source project and setup guide
+ {t("Open-source project and setup guide")}
);
}
export function SalesFooterLinks(_props: { source: ReferralSource }) {
+ const t = useT();
return (
- BeatGEO documentation
+ {t("BeatGEO documentation")}
);
}
diff --git a/apps/web/src/components/base-chart-print.tsx b/apps/web/src/components/base-chart-print.tsx
index 5af908b4..2902d6ba 100644
--- a/apps/web/src/components/base-chart-print.tsx
+++ b/apps/web/src/components/base-chart-print.tsx
@@ -4,6 +4,7 @@ import type { Competitor } from "@workspace/lib/db/schema";
import { Badge } from "@workspace/ui/components/badge";
import { Bar, BarChart, Cell, ResponsiveContainer, XAxis, YAxis } from "recharts";
import { type ChartDataPoint, type ChartSubject, latestObservedPoint, visibilityBadgeProps } from "@/lib/chart-utils";
+import { useT } from "@/lib/i18n";
interface BaseChartPrintProps {
data: ChartDataPoint[];
@@ -61,6 +62,7 @@ export function BaseChartPrint({
brand,
competitors,
}: BaseChartPrintProps) {
+ const t = useT();
const routeContext = useRouteContext({ strict: false }) as { clientConfig?: ClientConfig };
const latestDataPoint = latestObservedPoint(data, [brand.id, ...competitors.map((c) => c.id)]);
@@ -73,7 +75,7 @@ export function BaseChartPrint({
)}
- No data available
+ {t("No data available")}
);
diff --git a/apps/web/src/components/base-chart.tsx b/apps/web/src/components/base-chart.tsx
index 1ded3801..3a95e821 100644
--- a/apps/web/src/components/base-chart.tsx
+++ b/apps/web/src/components/base-chart.tsx
@@ -14,6 +14,7 @@ import {
selectCompetitorsToDisplay,
visibilityBadgeProps,
} from "@/lib/chart-utils";
+import { useT } from "@/lib/i18n";
import type { LookbackPeriod } from "@/lib/lookback";
/** The brand's own line is the one people are looking for, so it carries more
@@ -49,10 +50,11 @@ function SeriesLegend({
onHover: (key: string | null) => void;
onPin: (key: string | null) => void;
}) {
+ const t = useT();
return (
onHover(null)}
>
diff --git a/apps/web/src/components/brand-onboarding.tsx b/apps/web/src/components/brand-onboarding.tsx
index 3658e19e..c912836e 100644
--- a/apps/web/src/components/brand-onboarding.tsx
+++ b/apps/web/src/components/brand-onboarding.tsx
@@ -8,6 +8,7 @@ import FullPageCard from "@/components/full-page-card";
import { PlatformSelectionStep } from "@/components/platform-selection-step";
import { useOrganizationsChanged } from "@/hooks/use-organizations";
import { validateWebsiteUrl } from "@/lib/brand-website";
+import { useT } from "@/lib/i18n";
import { trackEvent } from "@/lib/posthog";
import { useWriteErrorMessage } from "@/lib/write-errors";
import { createBrandFn } from "@/server/brands";
@@ -21,6 +22,7 @@ interface BrandOnboardingProps {
}
export default function BrandOnboarding({ organizationSlug, brandId, brandName, platformState }: BrandOnboardingProps) {
+ const t = useT();
const [step, setStep] = useState<"website" | "platforms">("website");
const [website, setWebsite] = useState("");
const [selected, setSelected] = useState>(
@@ -78,7 +80,10 @@ export default function BrandOnboarding({ organizationSlug, brandId, brandName,
if (step === "platforms" && platformState) {
return (
-
+ setStep("website")}
onSubmit={() => createBrand([...selected])}
- submitLabel={isLoading ? "Setting up..." : "Complete Setup"}
+ submitLabel={isLoading ? t("Setting up...") : t("Complete Setup")}
/>
);
}
return (
-
+
diff --git a/apps/web/src/components/chart-export-preview.tsx b/apps/web/src/components/chart-export-preview.tsx
index f331b9ce..92268166 100644
--- a/apps/web/src/components/chart-export-preview.tsx
+++ b/apps/web/src/components/chart-export-preview.tsx
@@ -3,6 +3,7 @@ import type { Competitor } from "@workspace/lib/db/schema";
import { Badge } from "@workspace/ui/components/badge";
import type { ChartDataPoint, ChartSubject } from "@/lib/chart-utils";
import { visibilityBadgeProps } from "@/lib/chart-utils";
+import { useT } from "@/lib/i18n";
import type { LookbackPeriod } from "@/lib/lookback";
import { BaseChart } from "./base-chart";
@@ -43,6 +44,7 @@ export function ChartExportPreview({
competitors,
branding,
}: ChartExportPreviewProps) {
+ const t = useT();
const name = branding.name || DEFAULT_APP_NAME;
const isWhitelabel = branding.isWhitelabel && branding.name !== DEFAULT_APP_NAME;
const domain = isWhitelabel
@@ -69,7 +71,8 @@ export function ChartExportPreview({
className={`${visibilityBadgeProps(visibility).className} shrink-0`}
style={{ fontSize: 16, padding: "4px 14px" }}
>
- {visibility}% Visibility
+ {visibility}
+ {t("% Visibility")}
)}
diff --git a/apps/web/src/components/chart-footer.tsx b/apps/web/src/components/chart-footer.tsx
index f712b044..fc430dc1 100644
--- a/apps/web/src/components/chart-footer.tsx
+++ b/apps/web/src/components/chart-footer.tsx
@@ -6,6 +6,7 @@ import { CardFooter } from "@workspace/ui/components/card";
import { Separator } from "@workspace/ui/components/separator";
import { Download } from "lucide-react";
import { useCallback } from "react";
+import { useT } from "@/lib/i18n";
import { isLookbackPeriod, type LookbackPeriod } from "@/lib/lookback";
import { getPromptWebQueryFn } from "@/server/prompts";
import { HistoryButton } from "./history-button";
@@ -25,6 +26,7 @@ function ChartFooter({ children, className = "" }: ChartFooterProps) {
}
function DownloadButton({ onDownload, isDownloading }: { onDownload: () => void; isDownloading: boolean }) {
+ const t = useT();
return (
);
}
diff --git a/apps/web/src/components/citations-display.tsx b/apps/web/src/components/citations-display.tsx
index 5f66696a..dced2595 100644
--- a/apps/web/src/components/citations-display.tsx
+++ b/apps/web/src/components/citations-display.tsx
@@ -17,6 +17,7 @@ import { TopDomainsCard } from "@/components/citations/top-domains-card";
import { TopUrlsCard } from "@/components/citations/top-urls-card";
import { TrendAreaChart } from "@/components/citations/trend-area-chart";
import type { CitationData } from "@/components/citations/types";
+import { useT } from "@/lib/i18n";
export type { CitationData } from "@/components/citations/types";
@@ -108,6 +109,7 @@ function CitationTrendCharts({
chartSourceCategories: CitationCategory[];
chartPageTypes: CitationPageType[];
}) {
+ const t = useT();
const sourceSeries = citationData.citationTimeSeries ?? [];
const pageTypeSeries = citationData.pageTypeTimeSeries ?? [];
@@ -115,7 +117,7 @@ function CitationTrendCharts({
<>
{sourceSeries.length > 0 && (
>}
keys={chartSourceCategories}
@@ -125,7 +127,7 @@ function CitationTrendCharts({
{pageTypeSeries.length > 0 && (
>}
keys={chartPageTypes}
diff --git a/apps/web/src/components/citations/content-gaps-card.tsx b/apps/web/src/components/citations/content-gaps-card.tsx
index 1d249a18..78b4f91b 100644
--- a/apps/web/src/components/citations/content-gaps-card.tsx
+++ b/apps/web/src/components/citations/content-gaps-card.tsx
@@ -4,6 +4,7 @@ import { Separator } from "@workspace/ui/components/separator";
import { Tooltip, TooltipContent, TooltipTrigger } from "@workspace/ui/components/tooltip";
import { BrandPromptLink } from "@/components/brand-prompt-link";
import { ListPagination, usePagedList } from "@/components/list-pagination";
+import { useT } from "@/lib/i18n";
const PAGE_SIZE = 6;
@@ -12,22 +13,24 @@ export function ContentGapsCard({
}: {
prompts: Array<{ id: string; value: string; competitorCitationCount: number; uniqueCompetitors: number }>;
}) {
+ const t = useT();
const { page, setPage, pageItems, totalItems } = usePagedList(prompts, PAGE_SIZE);
return (
- Content Gaps
+ {t("Content Gaps")}
} />
- Prompts where competitors are cited but your brand isn't — opportunities to improve your citation
- presence.
+ {t(
+ "Prompts where competitors are cited but your brand isn't — opportunities to improve your citation presence.",
+ )}
- Prompts where competitors are cited but your brand isn't
+ {t("Prompts where competitors are cited but your brand isn't")}
@@ -44,9 +47,10 @@ export function ContentGapsCard({
diff --git a/apps/web/src/components/citations/google-shopping-card.tsx b/apps/web/src/components/citations/google-shopping-card.tsx
index 35392f11..dd21e71b 100644
--- a/apps/web/src/components/citations/google-shopping-card.tsx
+++ b/apps/web/src/components/citations/google-shopping-card.tsx
@@ -7,6 +7,7 @@ import { BrandPromptLink } from "@/components/brand-prompt-link";
import { attributionDotClass } from "@/components/citations/shared";
import type { GoogleModuleData } from "@/components/citations/types";
import { ListPagination, usePagedList } from "@/components/list-pagination";
+import { useT } from "@/lib/i18n";
const PRODUCTS_PAGE_SIZE = 10;
@@ -26,6 +27,7 @@ function PromptCountList({ prompts }: { prompts: { id: string; value: string; co
}
export function GoogleShoppingCard({ googleModule }: { googleModule: GoogleModuleData }) {
+ const t = useT();
const [expandedProduct, setExpandedProduct] = useState(null);
const [productFilter, setProductFilter] = useState<"all" | "brand" | "competitor">("all");
const [expandedQuery, setExpandedQuery] = useState(null);
@@ -50,22 +52,22 @@ export function GoogleShoppingCard({ googleModule }: { googleModule: GoogleModul
- Google Shopping
+ {t("Google Shopping")}
} />
- Product cards Google AI Mode showed when answering your prompts. The number next to each is how many times
- that card appeared across results (card inclusions, not unique products). Kept separate from the citation
- mix above.
+ {t(
+ "Product cards Google AI Mode showed when answering your prompts. The number next to each is how many times that card appeared across results (card inclusions, not unique products). Kept separate from the citation mix above.",
+ )}
- Products Google AI Mode surfaced —{" "}
+ {t("Products Google AI Mode surfaced —")}{" "}
{googleModule.shopping.brandCount.toLocaleString()}{" "}
- appearances for yours vs{" "}
- {googleModule.shopping.competitorCount.toLocaleString()} for
- competitors
+ {t("appearances for yours vs")}{" "}
+ {googleModule.shopping.competitorCount.toLocaleString()}
+ {t(" for competitors")}
@@ -73,7 +75,7 @@ export function GoogleShoppingCard({ googleModule }: { googleModule: GoogleModul
{googleModule.shopping.products.length > 0 && (
diff --git a/apps/web/src/components/citations/recent-changes-card.tsx b/apps/web/src/components/citations/recent-changes-card.tsx
index acd868cf..3df4e006 100644
--- a/apps/web/src/components/citations/recent-changes-card.tsx
+++ b/apps/web/src/components/citations/recent-changes-card.tsx
@@ -12,6 +12,7 @@ import type { ReactNode } from "react";
import { useMemo, useState } from "react";
import { formatPeriodLabel, formatUrlForDisplay, UnderlineTabs } from "@/components/citations/shared";
import type { CitationData } from "@/components/citations/types";
+import { useT } from "@/lib/i18n";
type WhatsChanged = NonNullable;
@@ -106,6 +107,7 @@ function ChangeRow({ icon, label, url, description }: { icon: ReactNode } & Retu
}
export function RecentChangesCard({ whatsChanged, days }: { whatsChanged: WhatsChanged; days: number }) {
+ const t = useT();
const [changeTypeFilter, setChangeTypeFilter] = useState("new_pages");
const visibleChanges = useMemo((): Change[] => {
@@ -125,16 +127,22 @@ export function RecentChangesCard({ whatsChanged, days }: { whatsChanged: WhatsC
- Recent Changes
+ {t("Recent Changes")}
} />
- Compares this {formatPeriodLabel(days)} with the {formatPeriodLabel(days)} before it. Shows new and
- dropped pages, title changes, and new and dropped domains.
+ {t("Compares this ")}
+ {formatPeriodLabel(days)}
+ {t(" with the ")}
+ {formatPeriodLabel(days)}
+ {t(" before it. Shows new and dropped pages, title changes, and new and dropped domains.")}
- How AI citations have shifted over the past {formatPeriodLabel(days)}
+
+ {t("How AI citations have shifted over the past ")}
+ {formatPeriodLabel(days)}
+
@@ -150,7 +158,9 @@ export function RecentChangesCard({ whatsChanged, days }: { whatsChanged: WhatsC
})}
{visibleChanges.length === 0 && (
- No {activeTabLabel ?? changeTypeFilter} changes in this period.
+ {t("No ")}
+ {activeTabLabel ?? changeTypeFilter}
+ {t(" changes in this period.")}
)}
diff --git a/apps/web/src/components/citations/reddit-card.tsx b/apps/web/src/components/citations/reddit-card.tsx
index 5678b32e..efaebfb9 100644
--- a/apps/web/src/components/citations/reddit-card.tsx
+++ b/apps/web/src/components/citations/reddit-card.tsx
@@ -7,6 +7,7 @@ import { useMemo, useState } from "react";
import { extractSubreddit, formatUrlForDisplay } from "@/components/citations/shared";
import type { CitationData } from "@/components/citations/types";
import { ListPagination, usePagedList } from "@/components/list-pagination";
+import { useT } from "@/lib/i18n";
const SUBREDDITS_PAGE_SIZE = 8;
@@ -66,6 +67,7 @@ export function useSubredditData(
}
export function RedditCard({ subreddits }: { subreddits: ReturnType }) {
+ const t = useT();
const [expandedSubreddit, setExpandedSubreddit] = useState(null);
const { page, setPage, pageItems, totalItems } = usePagedList(subreddits, SUBREDDITS_PAGE_SIZE);
@@ -73,17 +75,18 @@ export function RedditCard({ subreddits }: { subreddits: ReturnType
- Reddit
+ {t("Reddit")}
} />
- Reddit communities most frequently cited by AI models. Extracted from all reddit.com URLs in your citation
- data.
+ {t(
+ "Reddit communities most frequently cited by AI models. Extracted from all reddit.com URLs in your citation data.",
+ )}
- Top cited subreddits — which Reddit communities AI models reference when answering your prompts
+ {t("Top cited subreddits — which Reddit communities AI models reference when answering your prompts")}
@@ -107,13 +110,18 @@ export function RedditCard({ subreddits }: { subreddits: ReturnType
{sub.allNew && (
- NEW
+ {t("NEW")}
)}
{!sub.allNew && sub.newPages > 0 && (
- +{sub.newPages} new
+
+ +{sub.newPages}
+ {t(" new")}
+
+ )}
+ {sub.hasDropped && (
+ {t("some dropped")}
)}
- {sub.hasDropped && some dropped}
{sub.count.toLocaleString()}
@@ -142,7 +150,7 @@ export function RedditCard({ subreddits }: { subreddits: ReturnType
- NEW
+ {t("NEW")}
)}
diff --git a/apps/web/src/components/citations/shared.tsx b/apps/web/src/components/citations/shared.tsx
index dc424f11..7fa9ccb0 100644
--- a/apps/web/src/components/citations/shared.tsx
+++ b/apps/web/src/components/citations/shared.tsx
@@ -5,6 +5,7 @@ import {
type CitationCategory,
PAGE_TYPE_CONFIG,
} from "@workspace/lib/citations/domain-categories";
+import { useT } from "@/lib/i18n";
export const getCategoryLabel = (category: string) => CATEGORY_CONFIG[category as CitationCategory]?.label ?? category;
@@ -72,8 +73,9 @@ export function UnderlineTabs({
activeKey: T;
onSelect: (key: T) => void;
}) {
+ const t = useT();
return (
-