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), i18next | en(フォールバック) |
| プロジェクト原典言語 | 作品の書かれた言語(プロジェクト単位) | IndexedDB structuredPlot.language | ja |
| 原稿バージョン言語 | 各セクションの原稿がどの言語で書かれているか | IndexedDB manuscripts[sectionId].versions[lang] | プロジェクト原典言語に従う |
2. 現在の言語コード仕様
2.1 サポート言語(UI)
| コード | 言語 | 翻訳ファイル |
|---|---|---|
en | English | next-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 |
| 検出順序 | cookie → navigator → htmlTag |
| 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 | ファイル | デフォルト | 用途 |
|---|---|---|---|
languageAtom | projectStateAtoms.ts | 'en' | グローバルUI言語 |
selectedLanguageAtom | readingStateAtoms.ts | 'ja' | 読書モードでの選択言語 |
structuredPlotAtom | projectStateAtoms.ts | { language: 'ja' } | プロジェクトの原典言語 |
publishStateAtom | publishStateAtoms.ts | { originalLanguage: 'ja' } | パブリッシュ時の原典言語 |
永続化:
selectedLanguageAtom→localStorageキーreading-language(atomWithStorage使用)languageAtom→ i18next経由でCookieに永続化
3.5 MCPツール
ファイル: next-app/src/server/api/mcp/index.ts
以下のMCPツールが lang / language パラメータを持つ:
| ツール名 | パラメータ | デフォルト | 必須 |
|---|---|---|---|
blueperiod_create_project | language | 'ja' | No |
blueperiod_get_raw_manuscript | lang | 'ja' | No |
blueperiod_apply_manuscript_adit | lang | 'ja' | No |
blueperiod_apply_manuscript_delimiter | lang | 'ja' | No |
blueperiod_overwrite_manuscript | lang | 'ja' | No |
blueperiod_overwrite_manuscript | sourceLang | lang と同じ | 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 テーブルの言語関連フィールド:
| カラム | 型 | 説明 |
|---|---|---|
title | text | グローバル(英語)のタイトル |
title_original | text | 原典言語のタイトル |
synopsis | text | グローバル(英語)のあらすじ |
synopsis_original | text | 原典言語のあらすじ |
headline_original | text | 原典言語のキャッチコピー |
original_language | text | 原典言語コード(例: 'ja', 'en') |
tags | text[] | 英語と原典言語の両方のタグが混在 |
設計上の特徴: グローバル(英語)フィールドと原典言語フィールドの二重構造。
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_originaloriginal_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)- 常に英語の言語名を表示(例:
ja→Japanese) - 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 ↔ global5.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.ts | UI翻訳のリソース定義、言語検出設定 |
| ミドルウェア | src/proxy.ts | Accept-Language解析、Cookie設定 |
| 翻訳ファイル | src/i18n/locales/{en,ja,zh}.json | UI翻訳テキスト |
7.2 型・スキーマ
| 箇所 | ファイル | 役割 |
|---|---|---|
| StructuredPlot.language | src/lib/types.ts | プロジェクト原典言語 |
| Manuscript[sectionId].versions[lang] | src/lib/types.ts | 原稿の言語バージョン |
| ManuscriptVersion.isOriginal/sourceLang | src/lib/types.ts | 原典・翻訳元の追跡 |
| PublishedProject.original_language | src/lib/types/supabase.ts | Supabase側の原典言語 |
| plot-service schema | src/lib/features/ai/plot-service.ts | z.string().length(2) で2文字固定 |
7.3 状態管理(Atoms)
| 箇所 | ファイル | デフォルト |
|---|---|---|
structuredPlotAtom | src/stores/projectStateAtoms.ts | 'ja' |
manuscriptContentChangeAtom | src/stores/projectStateAtoms.ts | lang パラメータで管理 |
languageAtom | 状態管理 | 'en' |
selectedLanguageAtom | src/stores/readingStateAtoms.ts | 'ja' |
publishStateAtom.originalLanguage | パブリッシュ状態 | 'ja' |
7.4 サービス層
| 箇所 | ファイル | 役割 |
|---|---|---|
ManuscriptService.getContent() | src/lib/features/editorial/services/manuscript-service.ts | lang指定で原稿取得 |
ManuscriptService.updateContent() | 同上 | lang指定で原稿更新 |
LocalProjectService | src/lib/features/projects/local-project-service.ts | プロジェクト作成時のlanguage設定 |
7.5 MCPツール
| 箇所 | ファイル | パラメータ |
|---|---|---|
| MCPスキーマ定義 | src/server/api/mcp/index.ts | lang: { type: "string" } |
| MCPブリッジ | src/hooks/useMcpBridge.ts | args.lang || 'ja' |
| エージェントツール定義 | src/lib/features/editorial/tools/manuscript-tools.ts | Zod z.string().optional() |
7.6 ユーティリティ
| 箇所 | ファイル | 役割 |
|---|---|---|
extractAvailableLanguages() | src/lib/publishUtils.ts | 利用可能言語の抽出 |
getDisplayMetadata() | src/lib/i18n/metadataUtils.ts | 優先言語に基づく表示データ選択 |
7.7 GUIコンポーネント
| 箇所 | ファイル | 役割 |
|---|---|---|
OriginalLanguageBadge | src/components/ui/original-language-badge.tsx | 言語名バッジ表示 |
LanguageFlag (インライン) | src/components/details/ProjectDetailsContainer.tsx | 国旗絵文字表示 |
ProjectDetailsContainer | 同上 | 3モード言語切替 |
TableOfContents | src/components/details/TableOfContents.tsx | 翻訳タイトル表示 |
SettingsPageClient | src/app/settings/SettingsPageClient.tsx | UI言語選択 |
ProjectInfoCard | src/components/details/ProjectInfoCard.tsx | 言語バッジ群表示 |
8. 既知の課題
8.1 BCP 47非対応(重要度: 中)
| 課題 | 影響 | 詳細 |
|---|---|---|
z.string().length(2) | スキーマ | zh-Hans等の入力が弾かれる |
lang.split('-')[0] | 表示 | zh-Hans → zh に正規化され区別消失 |
['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言語とコンテンツ言語でデフォルトが異なる(en と ja)ことは意図的だが、明示的なドキュメントがなかった。
9. BCP 47対応に向けた影響範囲まとめ
BCP 47対応を実施する場合、以下の層すべてに変更が必要:
| 層 | 変更内容 | 影響度 |
|---|---|---|
| スキーマ | z.string().length(2) → BCP 47正規表現 | 高 |
| データ構造 | versions[lang] のキーが長くなる可能性 | 低(文字列キーのため) |
| 正規化ロジック | split('-')[0] の廃止・修正 | 高 |
| MCPツール | デフォルト値とバリデーションの更新 | 中 |
| 言語リスト | JSONベースの管理に移行 | 中 |
| GUIコンポーネント | LanguageFlag, 言語選択の更新 | 中 |
| Supabase | original_language フィールドの文字数上限確認 | 低 |
| i18n設定 | UI言語は3言語維持の方針(変更不要) | なし |
| 既存データ | マイグレーション検討(zh → zh-Hans 等) | 中 |
10. 関連ドキュメント
| ドキュメント | 関連内容 |
|---|---|
02_technology_stack.md | i18next, accept-language-parser の技術選定 |
04_database_schema.md | IndexedDBスキーマの言語フィールド |
04_database_schema_supabase.md | Supabaseテーブルの言語フィールド |
06_development_guidelines.md | i18n実装ガイドライン(2.8, 2.9項) |
09_design_system.md | UIコンポーネントの設計方針 |
19_mcp_architecture.md | MCPツールのアーキテクチャ |
20_ai_agent_tool_architecture_overview.md | AIエージェントツールの設計 |
| BCP47 Issue | BCP 47対応の課題と改善案 |