BluePeriod Docs
開発

Locale / 言語コードシステム

BCP 47準拠の多言語対応と言語コード管理仕様

07 - Locale / 言語コードシステム

対象バージョン: BluePeriod (Tauri V2) 最終更新: 2026-03-29 関連Issue: BCP 47 Language Support


1. 概要

BluePeriodにおけるLocale(言語コード)は、UI表示、プロジェクト管理、原稿(Manuscript)の多言語管理、パブリッシュ/Explore機能など、システムのほぼ全層に深く統合されている。本ドキュメントは、言語コードが「どこで」「どのように」使われているのかを包括的に記述し、今後のBCP 47対応や言語追加時の判断基準を提供する。

1.1 三層の言語管理

BluePeriodは概念的に3つの独立した言語レイヤーを持つ:

レイヤー目的格納先デフォルト
UI言語インターフェースの表示言語Cookie (NEXT_LOCALE), i18nexten(フォールバック)
プロジェクト原典言語作品の書かれた言語(プロジェクト単位)IndexedDB structuredPlot.languageja
原稿バージョン言語各セクションの原稿がどの言語で書かれているかIndexedDB manuscripts[sectionId].versions[lang]プロジェクト原典言語に従う

2. 現在の言語コード仕様

2.1 サポート言語(UI)

コード言語翻訳ファイル
enEnglishnext-app/src/i18n/locales/en.json (1124行)
ja日本語next-app/src/i18n/locales/ja.json (1176行)
zh中国語(簡体字)next-app/src/i18n/locales/zh.json (256行)

2.2 言語コード形式

  • 現状: ISO 639-1(2文字コード)のみ
  • 課題: zh-Hans/zh-Hant等のBCP 47形式に非対応
  • ハードコード箇所: ['en', 'ja', 'zh'] が複数ファイルに分散

3. オフライン(ローカル)の言語処理

3.1 i18n設定

ファイル: next-app/src/i18n/config.ts

設定項目
ライブラリi18next + react-i18next + i18next-browser-languagedetector
フォールバック言語en
検出順序cookienavigatorhtmlTag
Cookie名NEXT_LOCALE
キャッシュ先Cookie

3.2 ミドルウェア(言語自動検出)

ファイル: next-app/src/proxy.ts

[リクエスト受信]

Accept-Language ヘッダー取得

accept-language-parser で解析

サポート言語 ['en', 'ja', 'zh'] から最適なものを選択

Cookie 'NEXT_LOCALE' に保存(1年有効)

[レスポンス返却]
  • ライブラリ: accept-language-parser
  • デフォルトフォールバック: en
  • Cookie属性: path=/, maxAge=1年, sameSite=lax

3.3 データ構造

StructuredPlot(プロジェクト原典言語)

ファイル: next-app/src/lib/types.ts (45-49行)

export interface StructuredPlot {
  language: string    // プロジェクトの原典言語(例: 'ja')
  parts?: Part[]
  chapters?: Chapter[] // 後方互換用
}

初期値: next-app/src/stores/projectStateAtoms.ts (28行)

{ language: 'ja', parts: [] }

Manuscript(原稿バージョン言語)

ファイル: next-app/src/lib/types.ts (73-79行)

export interface Manuscript {
  [sectionId: string]: {
    versions: {
      [lang: string]: ManuscriptVersion  // キーが言語コード
    }
  }
}

ManuscriptVersion は以下のメタデータを持つ:

  • content: 原稿テキスト
  • isOriginal: 原典かどうかのフラグ
  • sourceLang: 翻訳元言語(翻訳版の場合)
  • model: 生成に使用したAIモデル
  • generatedAt: 生成日時

PlotTranslation(プロット翻訳)

plotTranslations: {
  [lang: string]: {
    [id: string]: PlotTranslation  // キーが言語コード → アイテムID
  }
}

3.4 状態管理(Atoms)

Atomファイルデフォルト用途
languageAtomprojectStateAtoms.ts'en'グローバルUI言語
selectedLanguageAtomreadingStateAtoms.ts'ja'読書モードでの選択言語
structuredPlotAtomprojectStateAtoms.ts{ language: 'ja' }プロジェクトの原典言語
publishStateAtompublishStateAtoms.ts{ originalLanguage: 'ja' }パブリッシュ時の原典言語

永続化:

  • selectedLanguageAtomlocalStorage キー reading-languageatomWithStorage 使用)
  • languageAtom → i18next経由でCookieに永続化

3.5 MCPツール

ファイル: next-app/src/server/api/mcp/index.ts

以下のMCPツールが lang / language パラメータを持つ:

ツール名パラメータデフォルト必須
blueperiod_create_projectlanguage'ja'No
blueperiod_get_raw_manuscriptlang'ja'No
blueperiod_apply_manuscript_aditlang'ja'No
blueperiod_apply_manuscript_delimiterlang'ja'No
blueperiod_overwrite_manuscriptlang'ja'No
blueperiod_overwrite_manuscriptsourceLanglang と同じNo

MCPブリッジ: next-app/src/hooks/useMcpBridge.ts

  • すべての lang パラメータは (args.lang as string) \|\| 'ja' のパターンでフォールバック

AIエージェントツール定義: next-app/src/lib/features/editorial/tools/manuscript-tools.ts

  • Zodスキーマで z.string().optional().describe("Language code (default: ja).") と定義

3.6 ManuscriptService

ファイル: next-app/src/lib/features/editorial/services/manuscript-service.ts

getContent(get, sectionId, lang = 'ja')  // 言語コードで原稿を取得
updateContent(set, sectionId, lang, content, options?)  // 言語コードで原稿を更新

3.7 プロジェクト作成

ファイル: next-app/src/lib/features/projects/local-project-service.ts (55行)

language: params.language || 'ja'  // 暗黙的なデフォルト

現状: ユーザーがプロジェクト作成時に言語を選択するUIは未実装。


4. オンライン(Supabase / Explore)の言語処理

4.1 データベーステーブル定義

ファイル: doc/system/04_database_schema_supabase.md

published_projects テーブルの言語関連フィールド:

カラム説明
titletextグローバル(英語)のタイトル
title_originaltext原典言語のタイトル
synopsistextグローバル(英語)のあらすじ
synopsis_originaltext原典言語のあらすじ
headline_originaltext原典言語のキャッチコピー
original_languagetext原典言語コード(例: 'ja', 'en'
tagstext[]英語と原典言語の両方のタグが混在

設計上の特徴: グローバル(英語)フィールドと原典言語フィールドの二重構造

4.2 TypeScript型定義

ファイル: next-app/src/lib/types/supabase.ts

export type PublishedProject = {
  id: string
  title: string
  title_original: string | null
  synopsis: string | null
  synopsis_original: string | null
  original_language: string | null
  // ...
}

4.3 メタデータ表示ロジック

ファイル: next-app/src/lib/i18n/metadataUtils.ts

[ユーザーの優先言語] === [プロジェクトの原典言語] ?
    ↓ Yes                        ↓ No
title_original を表示          title(英語)を表示
synopsis_original を表示        synopsis(英語)を表示

4.4 利用可能言語の抽出

ファイル: next-app/src/lib/publishUtils.ts

export function extractAvailableLanguages(manuscripts: Manuscript | PublishedManuscriptItem[]): string[]

Manuscriptの versions オブジェクトのキー(言語コード)を走査し、利用可能な言語リストを返す。

4.5 パブリッシュ処理

ファイル: next-app/src/lib/features/publish/service.ts

パブリッシュ時に以下の言語関連データをSupabaseに Upsert:

  • title_original, headline_original, synopsis_original
  • original_language

5. GUIコンポーネント

5.1 言語選択(設定画面)

ファイル: next-app/src/app/settings/SettingsPageClient.tsx (200-208行)

  • Selectコンポーネントで3言語(en/ja/zh)から選択
  • 選択結果は languageAtom に保存
  • i18next経由でCookieに永続化

5.2 OriginalLanguageBadge

ファイル: next-app/src/components/ui/original-language-badge.tsx

// Intl.DisplayNames API で言語コード→言語名変換
new Intl.DisplayNames(['en'], { type: 'language' }).of(languageCode)
  • 常に英語の言語名を表示(例: jaJapanese
  • Globe アイコン付きバッジ
  • 言語コードが不正な場合はコードそのものをフォールバック表示

5.3 言語切替ボタン(プロジェクト詳細)

ファイル: next-app/src/components/details/ProjectDetailsContainer.tsx (38-96行)

3モードの切替:

モード動作
auto現在のUI言語に基づいて自動判定
originalプロジェクトの原典言語で表示
global英語で表示

言語正規化: lang.split('-')[0] で2文字コードに切り詰め(BCP47対応の課題点)

切替フロー:

auto → [現在のUI言語]
     ↓ クリック
     ↓ isOriginal ? global : original
     ↓ トグル
     original ↔ global

5.4 目次翻訳表示

ファイル: next-app/src/components/details/TableOfContents.tsx (25-29行)

const getTranslatedTitle = (id, originalTitle) => {
  if (isOriginal) return originalTitle;
  const translation = plotTranslations?.[displayLanguage]?.[id];
  return translation?.title || originalTitle;  // フォールバック: オリジナルタイトル
};

5.5 LanguageFlag(インライン定義)

ファイル: next-app/src/components/details/ProjectDetailsContainer.tsx 等でインライン定義

const LanguageFlag = ({ lang }: { lang: string }) => {
  switch (lang) {
    case 'ja': return <span>🇯🇵</span>;
    case 'en': return <span>🇬🇧</span>;
    case 'zh': return <span>🇨🇳</span>;
    default: return <span>🌐</span>;
  }
};

課題: switch文によるハードコード。新しい言語のたびにコード修正が必要。

5.6 ProjectInfoCard

ファイル: next-app/src/components/details/ProjectInfoCard.tsx

  • OriginalLanguageBadge で原典言語を表示
  • extractAvailableLanguages で利用可能言語のバッジを表示

6. 言語コードのデータフロー

6.1 プロジェクト作成フロー

[ユーザー] → 新規プロジェクト作成

local-project-service.ts
  language: params.language || 'ja'  ← 暗黙的デフォルト

structuredPlot.language = 'ja'

IndexedDB (projects) に保存

6.2 原稿書き込みフロー(MCP経由)

[AIエージェント] → blueperiod_overwrite_manuscript
    projectId, sectionId, content, lang='ja'

MCP Bridge (useMcpBridge.ts)
    lang = args.lang || 'ja'

ManuscriptService.updateContent()
    manuscripts[sectionId].versions['ja'] = { content, isOriginal: true }

IndexedDB (manuscripts) に保存

6.3 言語検出フロー(初回アクセス)

[ブラウザ] → HTTPリクエスト (Accept-Language: ja,en-US;q=0.9,zh-CN;q=0.7)

proxy.ts (ミドルウェア)
    accept-language-parser.pick(['en', 'ja', 'zh'], acceptLanguage)

検出結果: 'ja'

Cookie 'NEXT_LOCALE' = 'ja' (1年有効)

i18next が Cookie を読み取り UI言語を設定

6.4 読書モードでの言語切替フロー

[読者] → 言語切替ボタンをクリック

ProjectDetailsContainer.toggleLanguage()
    auto → original | global

displayLanguage = lang.split('-')[0]  ← 2文字に正規化

TableOfContents.getTranslatedTitle()
    plotTranslations[displayLanguage][id]?.title

Manuscript[sectionId].versions[displayLanguage]?.content

[表示更新]

6.5 パブリッシュフロー

[作者] → パブリッシュ実行

publish/service.ts
    title_original = プロジェクト原典言語のタイトル
    original_language = structuredPlot.language

Supabase: published_projects に Upsert

[読者] → Explore で閲覧

metadataUtils.getDisplayMetadata()
    preferredLang === original_language ?
        title_original を表示 : title(英語)を表示

7. 言語コードが使用される全箇所のマッピング

7.1 定義・設定

箇所ファイル役割
i18n設定src/i18n/config.tsUI翻訳のリソース定義、言語検出設定
ミドルウェアsrc/proxy.tsAccept-Language解析、Cookie設定
翻訳ファイルsrc/i18n/locales/{en,ja,zh}.jsonUI翻訳テキスト

7.2 型・スキーマ

箇所ファイル役割
StructuredPlot.languagesrc/lib/types.tsプロジェクト原典言語
Manuscript[sectionId].versions[lang]src/lib/types.ts原稿の言語バージョン
ManuscriptVersion.isOriginal/sourceLangsrc/lib/types.ts原典・翻訳元の追跡
PublishedProject.original_languagesrc/lib/types/supabase.tsSupabase側の原典言語
plot-service schemasrc/lib/features/ai/plot-service.tsz.string().length(2) で2文字固定

7.3 状態管理(Atoms)

箇所ファイルデフォルト
structuredPlotAtomsrc/stores/projectStateAtoms.ts'ja'
manuscriptContentChangeAtomsrc/stores/projectStateAtoms.tslang パラメータで管理
languageAtom状態管理'en'
selectedLanguageAtomsrc/stores/readingStateAtoms.ts'ja'
publishStateAtom.originalLanguageパブリッシュ状態'ja'

7.4 サービス層

箇所ファイル役割
ManuscriptService.getContent()src/lib/features/editorial/services/manuscript-service.tslang指定で原稿取得
ManuscriptService.updateContent()同上lang指定で原稿更新
LocalProjectServicesrc/lib/features/projects/local-project-service.tsプロジェクト作成時のlanguage設定

7.5 MCPツール

箇所ファイルパラメータ
MCPスキーマ定義src/server/api/mcp/index.tslang: { type: "string" }
MCPブリッジsrc/hooks/useMcpBridge.tsargs.lang || 'ja'
エージェントツール定義src/lib/features/editorial/tools/manuscript-tools.tsZod z.string().optional()

7.6 ユーティリティ

箇所ファイル役割
extractAvailableLanguages()src/lib/publishUtils.ts利用可能言語の抽出
getDisplayMetadata()src/lib/i18n/metadataUtils.ts優先言語に基づく表示データ選択

7.7 GUIコンポーネント

箇所ファイル役割
OriginalLanguageBadgesrc/components/ui/original-language-badge.tsx言語名バッジ表示
LanguageFlag (インライン)src/components/details/ProjectDetailsContainer.tsx国旗絵文字表示
ProjectDetailsContainer同上3モード言語切替
TableOfContentssrc/components/details/TableOfContents.tsx翻訳タイトル表示
SettingsPageClientsrc/app/settings/SettingsPageClient.tsxUI言語選択
ProjectInfoCardsrc/components/details/ProjectInfoCard.tsx言語バッジ群表示

8. 既知の課題

8.1 BCP 47非対応(重要度: 中)

課題影響詳細
z.string().length(2)スキーマzh-Hans等の入力が弾かれる
lang.split('-')[0]表示zh-Hanszh に正規化され区別消失
['en', 'ja', 'zh'] ハードコード拡張性言語追加のたびに複数ファイル修正

8.2 GUIの指針欠如(重要度: 中)

課題影響詳細
プロジェクト作成時の言語選択不可UXユーザーが原典言語を選択できない
LanguageFlag の分散定義保守性同一ロジックが複数箇所にインライン
翻訳言語リストのハードコード拡張性LANGUAGES 定数がコンポーネント内に埋め込み

8.3 デフォルト値の不一致

Atom / 設定デフォルト備考
languageAtom'en'UI言語のフォールバック
selectedLanguageAtom'ja'読書モードのデフォルト
structuredPlotAtom.language'ja'プロジェクト原典言語
MCPツール lang'ja'サーバー側のデフォルト
ミドルウェアフォールバック'en'言語検出失敗時

UI言語とコンテンツ言語でデフォルトが異なる(enja)ことは意図的だが、明示的なドキュメントがなかった。


9. BCP 47対応に向けた影響範囲まとめ

BCP 47対応を実施する場合、以下の層すべてに変更が必要:

変更内容影響度
スキーマz.string().length(2) → BCP 47正規表現
データ構造versions[lang] のキーが長くなる可能性低(文字列キーのため)
正規化ロジックsplit('-')[0] の廃止・修正
MCPツールデフォルト値とバリデーションの更新
言語リストJSONベースの管理に移行
GUIコンポーネントLanguageFlag, 言語選択の更新
Supabaseoriginal_language フィールドの文字数上限確認
i18n設定UI言語は3言語維持の方針(変更不要)なし
既存データマイグレーション検討(zhzh-Hans 等)

10. 関連ドキュメント

ドキュメント関連内容
02_technology_stack.mdi18next, accept-language-parser の技術選定
04_database_schema.mdIndexedDBスキーマの言語フィールド
04_database_schema_supabase.mdSupabaseテーブルの言語フィールド
06_development_guidelines.mdi18n実装ガイドライン(2.8, 2.9項)
09_design_system.mdUIコンポーネントの設計方針
19_mcp_architecture.mdMCPツールのアーキテクチャ
20_ai_agent_tool_architecture_overview.mdAIエージェントツールの設計
BCP47 IssueBCP 47対応の課題と改善案

On this page

07 - Locale / 言語コードシステム
1. 概要
1.1 三層の言語管理
2. 現在の言語コード仕様
2.1 サポート言語(UI)
2.2 言語コード形式
3. オフライン(ローカル)の言語処理
3.1 i18n設定
3.2 ミドルウェア(言語自動検出)
3.3 データ構造
StructuredPlot(プロジェクト原典言語)
Manuscript(原稿バージョン言語)
PlotTranslation(プロット翻訳)
3.4 状態管理(Atoms)
3.5 MCPツール
3.6 ManuscriptService
3.7 プロジェクト作成
4. オンライン(Supabase / Explore)の言語処理
4.1 データベーステーブル定義
4.2 TypeScript型定義
4.3 メタデータ表示ロジック
4.4 利用可能言語の抽出
4.5 パブリッシュ処理
5. GUIコンポーネント
5.1 言語選択(設定画面)
5.2 OriginalLanguageBadge
5.3 言語切替ボタン(プロジェクト詳細)
5.4 目次翻訳表示
5.5 LanguageFlag(インライン定義)
5.6 ProjectInfoCard
6. 言語コードのデータフロー
6.1 プロジェクト作成フロー
6.2 原稿書き込みフロー(MCP経由)
6.3 言語検出フロー(初回アクセス)
6.4 読書モードでの言語切替フロー
6.5 パブリッシュフロー
7. 言語コードが使用される全箇所のマッピング
7.1 定義・設定
7.2 型・スキーマ
7.3 状態管理(Atoms)
7.4 サービス層
7.5 MCPツール
7.6 ユーティリティ
7.7 GUIコンポーネント
8. 既知の課題
8.1 BCP 47非対応(重要度: 中)
8.2 GUIの指針欠如(重要度: 中)
8.3 デフォルト値の不一致
9. BCP 47対応に向けた影響範囲まとめ
10. 関連ドキュメント