export type Lang = "ja" | "en";

/**
 * モック（d-watch-mock.jsx）の対訳テーブル。
 * キーは日本語原文。EN時のみ差し替える。
 */
const EN: Record<string, string> = {
  "DFAロボット監視システム": "DFA Robot Monitoring System",
  "DFA運用担当": "DFA Operator",
  "📊 ダッシュボード": "📊 Dashboard",
  "📋 清掃詳細ログ": "📋 Cleaning Logs",
  "🔔 アラート": "🔔 Alerts",
  "📈 レポート": "📈 Reports",
  // ★清掃面積（指示書AN 段階5）★ — メニューの並びに合わせてここに置く
  "📐 清掃面積": "📐 Cleaning Area",
  "🔧 点検記録": "🔧 Inspections",
  "🔒 D-Secログ": "🔒 D-Sec Logs",
  "⚠ D-Secアラート": "⚠ D-Sec Alerts",
  "🏢 施設ロボットマスタ": "🏢 Facility Master",
  "📅 スケジュール": "📅 Schedules",
  "⚙ システム管理": "⚙ System Admin",
  モニタリング: "MONITORING",
  設定: "SETTINGS",
  施設名: "Facility",
  機種: "Model",
  シリアル: "Serial",
  日付: "Date",
  予定: "Scheduled",
  出発: "Dep.",
  清掃結果: "Clean Result",
  完了率: "Completion",
  判定: "Grade",
  ダッシュボード: "Dashboard",
  清掃詳細ログ: "Cleaning Detail Log",
  "アラート（事前チェック）": "Alerts (Pre-check)",
  レポート: "Reports",
  "D-Sec ログ": "D-Sec Logs",
  "D-Security アラート": "D-Security Alerts",
  施設ロボットマスタ: "Facility Robot Master",
  スケジュール管理: "Schedule Mgmt",
  システム管理: "System Admin",
  点検記録: "Inspection Records",
  CSV: "CSV",
  // ---- 分類（ホールディングス / 顧客 / 店舗）。指示書AK §2-5 ----
  // ★第11回 P-15=B（`customers` は法人の単位）で確定。2026-10-05 に見出し語を「屋号」から「顧客」に変えた（D-158。指示書AU 段階4）★。
  //   ★キーごと改名した★（旧キーは残さない。片方だけ直した画面が「屋号」のまま出ても気づけなくなるため）。
  //   旧: 2026-09-15「屋号でよいです、ただ顧客によってはない場合もあります」で「屋号」にしていた（TeamWorks 第9回 Q7-1 の回答待ち）。
  // ★英語は仮★（"Customer" / "All Customers"。大文字は既存の "All Holdings" にそろえた。§11 Q7）—
  //   ★英語は後でユーザーがまとめて見直す★。
  // ★見出し語をここ1箇所に集めてある★（呼び出し側は `t("顧客")` / `t("全顧客")`）。
  // ★旧キー「全グループ」「全会社」は削除した★（指示書AK 段階7 の判断）—
  //   モックの文言で、5画面すべてが「全ホールディングス」「全屋号」に差し替わり、
  //   ★アプリ側の参照が1件も無くなった★（残る出現箇所は `d-watch-mock.jsx` だけで、
  //   モックは自前の辞書を持つためこの定義には依存しない）。
  ホールディングス: "Holdings",
  顧客: "Customer",
  全ホールディングス: "All Holdings",
  全顧客: "All Customers",
  全件: "All",
  "△×のみ": "△× Only",
  "×のみ": "× Only",
  問題エリアのみ: "Problems Only",
  "エリア別詳細を見る →": "View Area Details →",
  エリア別清掃完了率: "Area Completion Rates",
  // ★エリア別完了率モーダル（F-02。指示書Z 段階6）★ — 件数行は画面が {n}/{m} を差し込む
  //   （`{n}日連続で出発失敗` 等と同じ方式A）。★キーは日本語原文と1文字も違わないこと★
  "問題エリア（{n} / {m}エリア）": "Problem areas ({n} / {m})",
  "全{n}エリア（完了率昇順）": "All {n} areas (by completion rate)",
  "問題エリアはありません（全エリア◎○）": "No problem areas (all ◎○)",
  // ★モーダルのヘッダ（施設名 | 日付 | 全体 xx%）★ — ここだけ対訳が無く EN で和文が残っていた
  全体: "Overall",
  // ★「全部◎○だった」と区別するための文言★ — エリアが1件も無いのはデータ側の問題なので
  //   「問題エリアはありません」と同じ表示にしない
  "エリアのデータがありません": "No area data",
  "読み込み中…": "Loading…",
  閉じる: "Close",
  準備中: "Coming soon",

  // ---- レポート（F-04）。指示書F 5-8 ----
  // モックの T 辞書（d-watch-mock.jsx:138-147）からの移植。
  // ★`完了率`（= "Completion"）は上に既にあるため再掲しない★
  CSVエクスポート: "CSV Export",
  検索: "Search",
  全ステータス: "All Status",
  週次: "Weekly",
  月次: "Monthly",
  // ★レポート画面の粒度タブ3つ目（2026-09-30。指示書AQ 決定1）★
  年次: "Yearly",
  // ★清掃詳細ログの事前チェック列の見出し（ユーザー判断 2026-10-16「案1」。D-178）★ —
  //   列は「その清掃の何回目の事前チェックか」（早い方が1回目）。何分前かはセルの中に出す
  //   （事前チェックの時刻は設定で変わるため、見出しに分数を書かない）。
  //   ★旧キー「1h前ST」「30分前ST」は削除した★（画面の参照が無くなった。CSV の見出しは `lib/clean-logs/csv.ts` が直書きで、辞書を通らない）
  事前チェック1回目: "Pre-check 1",
  事前チェック2回目: "Pre-check 2",
  稼働率: "Uptime",
  出発成功率: "Dep. Rate",
  清掃完了率: "Completion",
  エラー発生率: "Error Rate",
  連続問題施設: "Recurring Issues",
  エラー種別の内訳: "Error Breakdown",
  "施設別パフォーマンス（稼働率ワースト順）": "Facility Performance (Worst Uptime)",
  要改善: "Needs Fix",
  注意: "Caution",
  良好: "Good",
  // ★施設別パフォーマンスの4値目（D-109 決定1'）★ — 当期に実施済みの清掃が1件も無く
  //   稼働率が算出できない施設。★週の初め・月の初めにだけ現れる★ので見落としやすい。
  //   ★`translate()` は未登録キーを日本語のまま返すため、足さないと EN で1つだけ和文が混ざる★
  判定なし: "No Data",
  前週: "Prev Week",
  前月: "Prev Month",
  // ★年次の解禁に伴って追加（2026-09-30。指示書AQ 決定1）★ —
  //   `ReportsScreen` は `t(prevPeriodLabel(period))` と、★関数の戻り値をそのまま辞書キーにする★
  //   （`lib/format/period.ts` の `prevPeriodLabel()`）。★ここに無いと EN で「前年」だけ和文で残る★
  前年: "Prev Year",
  該当施設なし: "No matching facilities",
  上位10件に戻す: "Back to top 10",
  エラー率: "Error Rate",
  変化: "Change",
  ステータス: "Status",
  // ★指示書O 段階3★: エラー種別の内訳ラベルが空の場合の代替文言(`lib/format/raw-label.ts`)
  "(値なし)": "(no value)",

  // ---- 数値が文中に入るラベル（指示書F 5-5 / 5-7 のフッタ）----
  // `translate()` はキーの完全一致でしか引けないため、`他5件を表示 →` のように
  // 数値が混ざったラベルはそのままではキーにできない。
  // 画面担当が選べるよう **2方式ぶんのキーを用意している**。どちらか一方だけを使い、
  // 使わなかった方は画面実装が固まった時点で削除してよい。
  //
  //   方式A（プレースホルダ + replace）★推奨★
  //     t("他{n}件を表示 →").replace("{n}", String(n))
  //     → ja: 「他5件を表示 →」 / en: "View 5 more →"
  //     語順が言語ごとに違っても対訳側で吸収できるため、こちらを推す。
  //
  //   方式B（前後を分割して連結）
  //     t("他") + n + t("件を表示 →")
  //     → ja: 「他5件を表示 →」 / en: "View 5 more →"
  //     ★注意★ `他` / `全` のような1文字キーは他画面の文言と衝突しやすい
  //     （将来 `全` 単体を別の意味で使うと巻き込まれる）。方式Aなら衝突しない。
  "他{n}件を表示 →": "View {n} more →",
  "全{n}施設を表示 →": "View all {n} facilities →",
  他: "View ",
  "件を表示 →": " more →",
  全: "View all ",
  "施設を表示 →": " facilities →",

  // ---- D-Secログ（F-06）の「備考」（編集モード。D-183）----
  備考を入力: "Enter a note",
  "保存中…": "Saving…",
  // 画面側の既定文言（サーバーが `{ error: { message } }` を返さなかった場合に出る）
  保存に失敗しました: "Failed to save",
  // PATCH /ds-devices/{serial_number}/note がエラー時に返す文言。
  // セル下の赤字にはサーバーの文言がそのまま入るため、ここに載せた分だけ EN になる
  // （`translate()` はキーが無ければ原文を返すので、未収録でも表示は壊れない）
  デバイスが見つかりません: "Device not found",
  備考の保存に失敗しました: "Failed to save the note",
  "noteが長すぎます": "Note is too long",

  // ---- アラート（F-03）の対応状況（指示書AT §3-8。D-105 で「キーが無い」と申し送られていたもの）----
  // ★足すと既存のバッジ・サマリカードの「未解消」も英語になる★
  未解消: "Unresolved",
  対応中: "In progress",
  解消: "Resolved",
  対応状況の更新に失敗しました: "Failed to update the status",

  // ---- 施設ロボットマスタ（F-08）----
  // ★`施設名` / `シリアル` / `機種` は上に既にあるため再掲しない★
  "施設名 / シリアル / 住所": "Facility / Serial / Address",
  該当する施設はありません: "No matching facilities",
  都道府県: "Prefecture",
  住所: "Address",
  電話番号: "Phone",
  責任者名: "Manager",
  担当者名: "Contact",
  メール: "Email",
  監視: "Monitor",
  // 製品名なので EN でも同じ。`translate()` は未収録でも原文を返すため表示は変わらないが、
  // 「訳し漏れ」と「意図的に同じ」を区別できるように載せておく
  "D-Sec": "D-Sec",
  // 監視・D-Sec 列の見出しの並べ替えボタン（aria-label / title。ユーザー指示 2026-10-30）
  監視で並べ替え: "Sort by monitoring",
  "D-Secで並べ替え": "Sort by D-Sec",
  // 清掃詳細ログの見出し並べ替え（指示書BH 段階4）
  機種で並べ替え: "Sort by model",
  シリアルで並べ替え: "Sort by serial",
  日付で並べ替え: "Sort by date",
  出発で並べ替え: "Sort by departure",
  清掃結果で並べ替え: "Sort by clean result",
  完了率で並べ替え: "Sort by completion rate",
  判定で並べ替え: "Sort by grade",
  // エラー一覧の見出し並べ替え・予定絞り込み（指示書BH 段階6）
  検知時刻で並べ替え: "Sort by detected time",
  種類で並べ替え: "Sort by type",
  レベルで並べ替え: "Sort by level",
  対応状況で並べ替え: "Sort by status",
  備考で並べ替え: "Sort by note",
  "予定：すべて": "Schedule: All",
  予定あり: "Scheduled",
  "ロボット（メーカー / 機種 / シリアル）": "Robot (Maker / Model / Serial)",
  // 対応機種レジストリ（lib/config/index.ts の ROBOT_MODELS）に無い機種のバッジ（指示書N）
  未対応: "Unsupported",
  // ★旧「全{n}施設」は削除した（指示書AA 段階5）★ — 施設一覧のフッタを
  //   共通部品 `components/ui/Pagination` に置き換え、件数表示を
  //   「全{total}件中 {from}〜{to}件」に6画面で統一したため使われなくなった。
  //   ★`"全{n}施設を表示 →"` は別のキー★（レポート画面の
  //   `FacilityPerformanceTable` が使用中）なので消さないこと。

  // ---- スケジュール管理（F-09）----
  // ★`シリアル` / `施設名` は上に既にあるため再掲しない★
  "施設名 / シリアル": "Facility / Serial",
  該当するスケジュールはありません: "No matching schedules",
  曜日: "Days",
  時間: "Time",
  有効: "Active",

  // ---- システム管理（F-10）運用設定 ----
  "清掃監視の運用パラメータ。変更はDFAシステム管理者のみ":
    "Cleaning monitoring parameters. Changes by DFA system administrators only",
  運用設定: "Operation Settings",
  // ★旧「この画面は現在 参照のみ です。」「設定値の編集はP1後半で対応します。」は削除した
  //   （指示書AF 段階7-3）★ — 連続日数の表示上限だけが編集できるようになり、
  //   文言が事実と食い違うようになったため。他に使っている箇所が無いことを確認済み。
  "この画面は一部の設定のみ編集できます。":
    "Only some settings on this screen can be edited.",
  "編集できない設定には「編集不可」と表示します。":
    "Settings that cannot be edited are marked “Read-only”.",
  事前チェック設定: "Pre-check Settings",
  清掃開始予定時刻の何分前にロボット状態をチェックするか:
    "How many minutes before the scheduled cleaning start time the robot status is checked",
  // 数値混じり（方式A: プレースホルダ + replace）。1文字キー（第/分前/日以上連続）にしない
  "第{n}回チェック": "Check #{n}",
  "{n}分前": "{n} min before",
  // ★アラート（F-03）の種別ラベル（指示書S 2-3 / D-105）★
  //   分数を enum に持てないため、画面が `precheck_minutes` から組み立てる（方式A）。
  //   ★従来 `lib/i18n.ts` にキーが無く、EN でも日本語のまま出ていた★
  //   （`AlertsScreen.tsx` は `t()` を通していたが対訳が無かった）。
  //   ★旧「事前チェック異常」（分数が null のときの退避）は 2026-10-29 に消した（アラートは事前チェック異常だけ）★
  "{n}分前異常": "Error {n} min before",
  // ★「出発失敗」は清掃詳細ログの清掃結果列（`CLEAN_RESULT_NOT_DEPARTED_LABEL`）が使う★（アラートは 2026-10-29 から使わない）
  出発失敗: "Launch failed",
  // ★清掃結果（`clean_task_reports.status`）の表示名（第10回 N-2。D-155 項2）★ —
  //   ★英語は仮。ユーザーが後でまとめて見直す★。6 は上の「清掃キャンセル」、7 以上は「不明({id})」
  清掃開始前: "Not started",
  清掃中: "Cleaning",
  清掃中断: "Interrupted",
  清掃キャンセル: "Cancelled",
  清掃正常終了: "Completed",
  清掃異常終了: "Ended abnormally",
  // ★結果待ち（ユーザー判断 2026-10-07。D-173）★ — 詳細ログの清掃結果列（`CLEAN_RESULT_PENDING_LABEL`）と
  //   ダッシュボードの「結果待ち」カード（副題「レポートの到着待ち」）が共有する。★英語は仮★
  結果待ち: "Pending",
  レポートの到着待ち: "Waiting for report",
  // ★同じ画面（`AlertsScreen.tsx`）でキーが無かったもの（指示書S 2-3）★
  残量: "Battery",
  検知: "Detected",
  "{n}日以上連続": "{n}+ consecutive days",
  // ★連続問題施設（F-04）の summary（指示書W）★ — サーバはテンプレート文字列のまま返し
  //   （方式A）、画面が {n}/{m} を差し込む。lib/db/report-mapping.ts の
  //   SUMMARY_TEMPLATE_COMPLETION / SUMMARY_TEMPLATE_LAUNCH_FAILED / SUMMARY_TEMPLATE_MIXED
  //   と★1文字も違わないこと★（違うと translate() が未登録キーとして日本語のまま返る）
  "{n}日連続で完了率が△以下": "Completion rate △ or below for {n} consecutive days",
  "{n}日連続で出発失敗": "Launch failed for {n} consecutive days",
  "{n}日連続で△以下（うち{m}日は出発失敗）":
    "△ or below for {n} consecutive days ({m} with launch failure)",
  // ★連続問題施設（F-04）のバッジ（指示書AF 段階5-3）★ — 従来 `日` を t() に通しておらず
  //   EN でも「5日」と出ていた（指示書AF §H-4）。既存キー `"{n}日以上連続"`（上）は
  //   システム管理画面が使っているので★書き換えない★。バッジには短い方を使う。
  "{n}日": "{n} days",
  "{n}日以上": "{n}+ days",
  // ★連続問題施設の summary が表示上限で頭打ちになったとき（指示書AF 段階5-3）★ —
  //   `lib/db/report-mapping.ts` の SUMMARY_TEMPLATE_CAPPED_COMPLETION /
  //   _CAPPED_LAUNCH_FAILED / _CAPPED_MIXED と★1文字も違わないこと★。
  //   ★混在の上限用テンプレートには {m} が無い★（指示書AF §B-5）
  "{n}日以上連続で完了率が△以下":
    "Completion rate △ or below for {n}+ consecutive days",
  "{n}日以上連続で出発失敗": "Launch failed for {n}+ consecutive days",
  "{n}日以上連続で△以下（出発失敗を含む）":
    "△ or below for {n}+ consecutive days (including launch failures)",

  // ---- ダッシュボードの見出し（F-01。D-159 / 指示書AV 段階5-2）----
  // ★数値カードと清掃詳細ログ（コンパクト版）は「各清掃エリアの直近の清掃」★（7日以内）。
  //   {n} には `DASHBOARD_LATEST_RUN.lookbackDays` を差し込む（方式A。日数を直書きしない）。
  //   ★モック（`d-watch-mock.jsx:476`。旧見出し「昨夜の…」）とは文言が違う★（D-159 でユーザーが変えた）。
  //   ★EN は暫定★（ユーザーが後でまとめて見直す）
  "直近の清掃結果サマリー（各清掃エリアの最後の清掃・{n}日以内）":
    "Latest cleaning results (last run per area, within {n} days)",
  // ★スケジュール進捗カード（ユーザー判断 2026-10-29「案B」）★ — {date} には API の `schedule_progress.date` を
  //   `formatMd()`（10/28 の形）にして差し込む（方式A）。直近モードでは JST の前日。★EN は暫定★
  "スケジュール進捗（{date}）": "Schedule progress ({date})",
  "{date} の清掃予定はありません": "No cleaning scheduled for {date}",
  "/ {n} 完了": "/ {n} departed",

  // ---- 要レスキュー店舗（F-01。ユーザー指示 2026-09-15 / 指示書AI 段階6）----
  // ★API が返すのは識別子（reasons）と数値だけ★（D-111）。文言を作るのは
  //   `components/features/dashboard/RescueRequiredCard.tsx` の1箇所で、
  //   数値は方式A（プレースホルダ + replace）で差し込む。
  // ★`完了率` は上に既にあるため再掲しない★（重複キーは lint が落とす）
  要レスキュー店舗: "Facilities Needing Rescue",
  // ★0件でもカードを消さないので、この文言が必ず要る★（理由はカード側の docstring）
  要レスキューの店舗はありません: "No facilities need rescue",
  // ★リクエストした日とは限らない★（予定の無い施設はそれ以前の最新スケジュール存在日）
  判定日: "Evaluated on",
  前回: "Previous",
  "{n}件": "{n} facilities",
  "{n}日連続で未実施": "Not executed for {n} consecutive days",
  "連続問題レベル3（{n}日連続）": "Escalation level 3 ({n} consecutive days)",
  "完了率が前回比{n}%低下": "Completion rate down {n}% vs. previous",
  // ★低下の比が算出できないのに急落と判定された場合の保険★（0% と書かないため）
  完了率が前回比で急落: "Completion rate dropped sharply vs. previous",
  // ★初期表示は5件で、超えるぶんはこのボタンで開く★（指示書AJ 段階1 / §4-4）—
  //   ★API は件数で切らない★ので、{n} には「まだ見えていない件数」が入る（方式A。D-111）。
  //   ★閉じる側は既存キー（モーダルの `閉じる`）を使い回す★ — 同じ語・同じ意味なので
  //   キーを増やすと対訳が二重管理になる（重複キーは lint が落とす）。
  "もっと表示（あと{n}件）": "Show all ({n} more)",
  完了率の判定閾値: "Completion Rate Thresholds",
  "清掃詳細ログの◎○△×判定に使用する閾値。上から順に評価します":
    "Thresholds for the ◎○△× grade in the cleaning detail log. Evaluated from the top",
  // ★境界は「上のランクに入る」（D-139。2026-09-16）★ — 90.0 は ◎、70.0 は ○、10.0 は △。
  //   旧規則（下位に落ちる）の文言に戻さないこと。`lib/judge/grade.ts` の比較は `>=`。
  "判定は丸める前の生値で行います（表示は小数第1位を四捨五入した整数）。境界値ちょうどは上位の判定になります。":
    "Grading uses the raw value before rounding (the display is rounded to the nearest integer). A value exactly on a boundary is included in the higher grade.",
  "アドバイス エスカレーション閾値": "Advice Escalation Thresholds",
  "同一施設で問題（出発失敗 または △以下）が連続した場合のレベル引き上げ基準":
    "Criteria for raising the level when problems (departure failure or △ and below) continue at the same facility",
  "「連続」はカレンダー日ではなく、清掃スケジュールが存在する日の連続で数えます。清掃予定のない日は連続を途切れさせません。":
    "“Consecutive” is counted over days that have a cleaning schedule, not calendar days. Days with no cleaning scheduled do not break the streak.",
  // ★連続日数の表示上限（指示書AF 段階7）★ — この画面で唯一編集できる設定。
  //   `日` は単位のラベル（入力欄の右）で、`t("日")` を呼ぶのはこの1箇所だけ。
  //   数値と連結する文言（`{n}日` / `{n}日以上` / `{n}日以上連続`）は別キー（方式A）。
  連続日数の表示上限: "Consecutive Days Display Cap",
  "連続問題施設などで、連続日数がこの日数を超えたとき「{n}日以上」と表示します":
    "When a streak exceeds this number of days, it is shown as “{n}+ days” (e.g. in Facilities with Continuous Problems)",
  日: "days",
  保存: "Save",
  既定値に戻す: "Reset to default",
  保存しました: "Saved",
  // 空欄のまま保存した場合の画面側の文言（`Number("") === 0` を送らないための足切り）
  日数を入力してください: "Enter the number of days",
  表示上限の取得に失敗しました: "Failed to load the display cap",
  // PATCH /settings/display-limits がエラー時に返す文言。
  // 入力欄の下の赤字にはサーバーの文言がそのまま入るため、ここに載せた分だけ EN になる
  // （`translate()` はキーが無ければ原文を返すので、未収録でも表示は壊れない）
  "JSONの形式が不正です": "Invalid JSON format",
  "keyの値が不正です": "Invalid key",
  "valueの値が不正です": "Invalid value",
  設定の保存に失敗しました: "Failed to save the setting",
  アドバイス文言: "Advice Text",
  清掃詳細ログに表示される推定原因と推奨アクション:
    "Estimated causes and recommended actions shown in the cleaning detail log",
  "発火条件（どのルールがいつ出るか）は仕様のため編集不可です。":
    "Trigger conditions (which rule fires when) are part of the specification and cannot be edited.",
  アドバイス文言は登録されていません: "No advice text registered",
  // アドバイスのレベル表示（`LEVEL_LABEL`）。★`注意` は上に既にあるため再掲しない★
  警告: "Warning",
  重大: "Critical",
  // アドバイスのフェーズ表示（`PHASE_LABEL`）
  "Phase A（完了率・連続日数）": "Phase A (Completion rate / Consecutive days)",
  "Phase B（状態・エラー種別）": "Phase B (Status / Error type)",
  編集済み: "Customized",
  推奨アクション: "Recommended Actions",
  発火条件: "Trigger",
  編集不可: "Read-only",

  // ---- 一覧のページ送り（指示書AA）----
  // ★プレースホルダが3つある（既存の {n} 1つより多い）。replace を3回つなげる★
  //   t("全{total}件中 {from}〜{to}件")
  //     .replace("{total}", String(total))
  //     .replace("{from}", String(from))
  //     .replace("{to}", String(to))
  // ★この文言を組み立てるのは components/ui/Pagination.tsx の1箇所だけ★
  //   （各画面で組み立てると .replace() の連鎖が6箇所に増える）
  "全{total}件中 {from}〜{to}件": "{from}–{to} of {total}",
  前へ: "Prev",
  次へ: "Next",
  // ★<nav> の aria-label★（画面には出ない。スクリーンリーダー向け）
  ページ送り: "Pagination",
  // ★CSVは表示中のページではなく全件★
  //   （app/api/clean-logs/export/route.ts が page / per_page を無視する契約）
  "CSVは絞り込み条件に一致する全件を出力します（表示中のページだけではありません）":
    "CSV exports all rows matching the filters, not just the current page",
  // ---- CSVの行数上限（ユーザー指示 2026-09-15 / 指示書AI 段階8）----
  // ★プレースホルダは {total} と {max} の2つ。数値は画面側で差し込む（D-111）★
  //   t("この期間だとCSV出力できません（{total}件／上限{max}件）。期間を短くするか、店舗で絞り込んでください。")
  //     .replace("{total}", String(total))
  //     .replace("{max}", String(MAX_CSV_ROWS))
  // ★★`disabled` だけにして理由を出さないことはしない★★ —
  //   押せないボタンは、理由が書いていないと不具合と区別がつかない（D-053 と同じ理由）。
  "この期間だとCSV出力できません（{total}件／上限{max}件）。期間を短くするか、店舗で絞り込んでください。":
    "CSV export is unavailable for this range ({total} rows / limit {max}). Narrow the date range or filter by facility.",
  // ---- 清掃詳細ログの期間が空のとき（D-159 追記 / 指示書AV 段階5b-5）----
  // ★既定は「期間を指定しない（全期間）」★（ユーザー指示 2026-10-06「C」）。
  //   `DetailLogScreen.tsx` の日付入力の placeholder と、両方空のときに出す表示で使う。★EN は暫定★
  "指定なし（全期間）": "Not set (all)",
  // ---- レポート画面を1本ずつ出す（指示書AL 段階2 / §3-4）----
  // ★KPIだけが 422（対象顧客が1社に定まらない）のとき、そのブロックにだけ出す誘導★。
  //   ★件数 {n} は `details.customer_ids` の長さ★ — ★文言を読ませるだけにしない★ため、
  //   「何社の中から選ぶのか」を数で見せる（`lib/reports/block-visibility.ts`）。
  KPI: "KPI",
  "稼働率・出発成功率・清掃完了率・エラー発生率":
    "Uptime / Launch / Completion / Error rates",
  顧客を選んでください: "Select a customer",
  "選べる顧客が{n}件あります": "{n} customers to choose from",
  顧客を選ぶ: "Choose a customer",
  この条件では選べる顧客がありません: "No customers available for this filter",
  データの取得に失敗しました: "Failed to load data",

  // ---- 清掃面積（指示書AN 段階5。ユーザー指示 2026-09-16「新画面」）----
  // ★`完了率` / `検索` / `ホールディングス` / `顧客` / `施設名` / `読み込み中…` /
  //   `閉じる` / `もっと表示（あと{n}件）` / `{n}件` は上に既にあるため再掲しない★
  //   （重複キーは lint が落とす）。
  // ★単位（㎡）は辞書に入れない★ — `lib/config` の `AREA_UNIT` が持つ（指示書AN §2-4）。
  //   ★対訳が要るのは見出しの語だけ★で、単位は言語で変わらない。
  清掃面積: "Cleaning Area",
  清掃対象面積: "Target Area",
  清掃実施回数: "Cleanings",
  // ★日付入力の aria-label★（画面には出ない。スクリーンリーダー向け）
  開始日: "From",
  終了日: "To",
  // ★粒度タブ★。`ホールディングス` / `顧客` は分類の見出し語（上）を使い回す
  店舗: "Stores",
  // ★★ホールディングスに属さない顧客をまとめる行★★（`CLEAN_AREA_GROUP_NONE_LABEL`）—
  //   ★「グループ0」を作らない★（D-011）。★API が返す `name` は使わず、画面が
  //   `scope === "group" && id === null` を見てこのキーを引く★ — 生文字列を
  //   そのまま `t()` に通すと、同名の実在する屋号名があったときに誤訳するため。
  グループなし: "No Holdings",
  該当データがありません: "No data",
  // ★「直近2年ぶん」の明記★（指示書AN §2-2 / 落とし穴10）—
  //   ★消さないこと★。書かないと3年目に数字が減ったときに問い合わせが来る。
  "期間を指定しないときは全期間の合計です。ただしデータの保持は2年なので、実際には直近2年ぶんの合計になります。":
    "With no date range, this is the total for all stored data. Data is retained for 2 years, so in practice it covers the most recent 2 years.",
  // ★総清掃面積の母集団の注記（D-156 論点7。指示書BG §2-5 で完了率・実施回数にも予定外を含めた）。★英訳は暫定★（ユーザーが後でまとめて見直す）
  //   ★画面の文言は「予定なし」★（ユーザー指示 2026-11-04「予定なし、予定ありに統一して」）
  "清掃面積・完了率・実施回数には予定なしの清掃も含みます":
    "Area, completion rate and run count include runs with no schedule.",

  // ---- エラー一覧（D-160 / 指示書AW 段階5-5）----
  // ★EN は暫定★（ユーザーが後でまとめて見直す）。
  // ★`施設名` / `シリアル` / `未解消` / `対応中` / `解消` / `備考を入力` /
  //   `保存中…` / `保存に失敗しました` / `対応状況の更新に失敗しました` / `読み込み中…` /
  //   `指定なし（全期間）` / `ホールディングス` / `顧客` / `データの取得に失敗しました` は上に既にあるため再掲しない★
  //   （重複キーは lint が落とす）。
  // ★件数は `全{n}件` を使う★ — `{n}件` は EN が "{n} facilities"（施設数の文言）なので件数表示に使えない（指示書AW 4-18）。
  // ★`error_type` / `error_level` / `serial_number` の値は辞書に入れない★（生値のまま出す。D-077）。
  "🚨 エラー一覧": "🚨 Errors",
  エラー一覧: "Errors",
  "未対応のエラー（ロボット・制御盤・セキュリティ・清掃開始失敗）":
    "Open errors (robots, control panels, security, start failures)",
  "全{n}件": "{n} total",
  検知時刻: "Detected at",
  種類: "Type",
  レベル: "Level",
  紐づく予定: "Schedule",
  対応状況: "Status",
  備考: "Note",
  // ★「詳細」の列（エラー内容。指示書BE §12。2026-10-27 ユーザー指示で種類の2行目から独立した列にした）★
  詳細: "Details",
  // ★「紐づく予定」のラベル（`errorScheduleLabel()`。指示書AW 表3）★ — `{n}` は replace（`"{n}分前"` と同じ方式A）
  本清掃: "Main run",
  "事前チェック（{n}分前）": "Pre-check ({n} min before)",
  事前チェック: "Pre-check",
  // ★詳細ログの予定列（予定外タスクの行。`CLEAN_LOG_UNSCHEDULED_LABEL`。指示書BG §2-1）・
  //   「紐づく予定」のプルダウン（`ScheduleFilterSelect`）でも使う★（ユーザー指示 2026-11-04「予定なし、予定ありに統一して」。
  //   旧キー `予定外: "Unscheduled"` は廃止）
  予定なし: "No schedule",
  予定不明: "Unknown schedule",
  解消済みも表示: "Show resolved",
  該当するエラーはありません: "No matching errors",
  エラーが見つかりません: "Error not found",
  // ★エラー内容の選択（指示書BE。D-185）★
  // ★分類名・詳細の文字列は辞書に入れない★（`t()` に通すと「該当するエラーはありません」等が誤訳される）。
  // ★EN は暫定★。
  未選択: "Not selected",
  "不明({id})": "Unknown ({id})",
  選択: "Select",
  エラー内容を選ぶ: "Choose an error description",
  すべての分類: "All categories",
  分類: "Category",
  キーワード: "Keyword",
  "{n}件 / {total}件": "{n} / {total}",
  クリア: "Clear",
  備考必須: "Note required",
  該当するエラー内容がありません: "No matching error descriptions",
  エラー内容の選択肢を読み込めませんでした: "Could not load error descriptions",
  その他を選んだときは備考を入力してください: "Enter a note when you choose Other",
  "備考を入力（必須）": "Enter a note (required)",
  // ★保存できなかった原因（指示書BF §1。`lib/errors/save-error-message.ts`）★ — EN は暫定。
  // ★`保存に失敗しました` / `その他を選んだときは備考を入力してください` は上に既にあるため再掲しない★
  この行ではエラー内容を選べません: "An error description cannot be chosen for this row",
  "選んだエラー内容は今は使えません。選び直してください":
    "The chosen error description is no longer available. Please choose again",
  "備考は{n}文字以内で入力してください": "Enter a note of {n} characters or fewer",
  入力内容を確かめてください: "Please check your input",
  "このエラーは削除されたため保存できません。画面を読み込み直してください":
    "This error was deleted and cannot be saved. Please reload the page",
  ログインし直してください: "Please sign in again",
  "サーバーで保存に失敗しました（DB への書き込みエラー）。時間をおいて保存し直し、続くときは管理者に連絡してください":
    "The server failed to save (database write error). Please try again later, and contact an administrator if it continues",
  "通信に失敗しました。接続を確かめて保存し直してください":
    "Network error. Please check your connection and save again",

  // ---- 対応状況・備考の編集モード（エラー一覧・アラート。ユーザー指示 2026-10-06。D-165）----
  // ★EN は暫定★。★`保存` / `保存中…` は上に既にあるため再掲しない★（重複キーは lint が落とす）。
  編集: "Edit",
  キャンセル: "Cancel",
  "保存（{n}件）": "Save ({n})",
  "変更を破棄しますか？": "Discard changes?",
  "{n}件を保存できませんでした": "{n} could not be saved",
  編集中は絞り込みとページ送りを使えません: "Filters and paging are disabled while editing",

  // ---- アラート・D-Sec アラート: 状態ボタンと数値カード（D-181。D-Sec はアラートの文言に揃えた）----
  // ★EN はモック（d-watch-mock.jsx:140）の対訳★。★`未解消` / `対応中` / `解消` は上に既にあるため再掲しない★。
  // ★足すとアラート画面のボタン・数値カードも EN になる（いままで日本語のまま出ていた）★
  未解消のみ: "Unresolved",
  解消済み: "Resolved",

  // ---- アラート: 本清掃が成功した事前チェック異常（ユーザー指示 2026-10-07。D-171）----
  // ★EN は暫定★
  本清掃は成功: "Main run succeeded",

  // ---- 画面の失敗・空の表示（指示書BC 段階2。D-183）----
  // ★EN は暫定★
  アドバイスの取得に失敗しました: "Failed to load advice",
  該当するアドバイスはありません: "No advice for this run",
  該当するログはありません: "No matching logs",
  "全{n}施設のうち上位{m}施設を表示しています（表示の上限）": "Showing the top {m} of {n} facilities (display limit)",
  "全{n}件のうち{m}件を表示しています（表示の上限）": "Showing {m} of {n} (display limit)",

  // ---- 文言・a11y（指示書BC 段階5。D-183）----
  // ★EN は暫定★
  "D-Secログ": "D-Sec Logs",
  "施設名・シリアルで検索": "Search by facility or serial",
  "1行＝1ロボット×1スケジュール実行": "1 row = 1 robot × 1 scheduled run",
  "← 戻る": "← Back",
  "シリアルNo.": "Serial No.",
  前の期間: "Previous period",
  次の期間: "Next period",
  "{n}日以上連続で△以下または出発失敗の施設": "Facilities rated △ or worse, or failing to launch, for {n}+ consecutive days",
  "この期間のエラー発生件数（種別ごと）": "Errors in this period (by type)",
  該当なし: "None",
  "{n}施設": "{n} facilities",
  判定基準: "Grading criteria",
  // ログイン画面（ログイン画面も ThemeProvider の中。段階3 の後は保存した言語で出る）
  アクセストークン: "Access token",
  ログイン: "Sign in",
  "確認中…": "Checking…",
  "社内利用のみ。接続元IPも制限されています。": "Internal use only. Source IP addresses are restricted.",
  トークンが違います: "Invalid token",
  トークンを入力してください: "Please enter the token",
  このIPアドレスからは利用できません: "Access from this IP address is not allowed",
  "試行回数が多すぎます。しばらくしてから再度お試しください": "Too many attempts. Please try again later",
  "ログインできませんでした。時間をおいて再度お試しください": "Could not sign in. Please try again later",
  "通信に失敗しました。時間をおいて再度お試しください": "Network error. Please try again later",
};

export function translate(lang: Lang, text: string): string {
  if (lang === "en" && EN[text]) return EN[text];
  return text;
}
