BluePeriod Docs
開発

Locale / 言語コードシステム

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

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

対象バージョン: BluePeriod (Tauri V2) 最終更新: 2026-04-24 関連Issue: BCP 47 Language Support 実装Report: BCP 47実装レポート


1. 概要

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

1.1 三層の言語管理

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

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

2. 言語コード仕様

2.1 BCP 47形式

BluePeriodはBCP 47言語タグ形式を採用している:

形式用途
言語のみen, ja, ko一般的な言語指定
言語 + スクリプトzh-Hans, zh-Hant中国語の簡体字/繁体字区別
言語 + 地域en-US, pt-BR地域バリエーション

バリデーション正規表現: /^[a-z]{2,3}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$/

後方互換性: 既存のISO 639-1コード(en, ja 等)はBCP 47としても有効なため、既存データの移行なしで動作する。

2.2 サポート言語の一元管理

ファイル: next-app/public/languages/supported-languages.json

言語データはJSONファイルで一元管理され、原典言語・翻訳言語の両方で使用する:

[
  { "code": "en", "name": "English", "nativeName": "English", "uiSupported": true },
  { "code": "ja", "name": "Japanese", "nativeName": "日本語", "uiSupported": true },
  { "code": "zh-Hans", "name": "Chinese (Simplified)", "nativeName": "简体中文", "uiSupported": true },
  { "code": "zh-Hant", "name": "Chinese (Traditional)", "nativeName": "繁體中文", "uiSupported": false },
  { "code": "ko", "name": "Korean", "nativeName": "한국어", "uiSupported": false }
]
フィールド説明
codeBCP 47言語タグ
name英語名(フォールバック表示用)
nativeNameネイティブ名(ネイティブ名表示用)
uiSupportedUI言語として対応しているか(i18nextリソース存在)

初期10言語: en, ja, zh-Hans, zh-Hant, ko, es, fr, de, pt, ru UI対応: en, ja, zh-Hans の3言語のみ(uiSupported: true

2.3 UI言語(i18n)

コード言語翻訳ファイル
enEnglishnext-app/src/i18n/locales/en.json
ja日本語next-app/src/i18n/locales/ja.json
zh简体中文next-app/src/i18n/locales/zh.json

注意: i18nextのリソースキーは zh だが、supported-languages.json では zh-Hans として登録。toUiLanguageKey()zh-Hanszh にフォールバックする。

翻訳ファイルには language.{code} キーが含まれ、フル表示モードの括弧内に使用される:

// ja.json の例
"language.en": "英語",
"language.ja": "日本語",
"language.zh-Hans": "簡体字中国語",
"language.ko": "韓国語"

3. BCP 47ユーティリティ

3.1 コア関数(src/lib/i18n/bcp47.ts

関数役割
isValidBcp47(tag)BCP 47タグのバリデーション
toUiLanguageKey(bcp47)i18next用フォールバック(zh-Hanszh
getNativeName(tag)ネイティブ名取得(未登録ならIntl.DisplayNamesフォールバック)
getLanguageDisplayName(tag, t)フル表示名の生成

3.2 サポート言語アクセス(src/lib/i18n/supportedLanguages.ts

JSONを直接importする同期API:

関数役割
getSupportedLanguages()全言語リスト取得
getUiLanguages()uiSupported: true の言語のみ
getLanguageByCode(code)コードから言語情報取得

3.3 言語名の3層表示方式

表示コンテキストに応じて3つのモードを使い分ける:

モード形式例(UI日本語時)
フル{nativeName} ({i18n名})한국어 (韓国語)
ショートBCP 47コード大文字KO
フォールバックIntl.DisplayNamesItalian

ネイティブ名とi18n名が同一の場合(例: UI日本語時の 日本語)は括弧を省略してnativeNameのみ表示。

フォールバック順序:

  1. i18n翻訳キー language.{code} → 2. JSON name → 3. Intl.DisplayNames

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

4.1 i18n設定

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

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

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

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

[リクエスト受信]

Accept-Language ヘッダー取得

accept-language-parser で解析

サポート言語から最適なものを選択

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

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

4.3 データ構造

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

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

export interface StructuredPlot {
  /** BCP 47 language tag @example 'ja' @example 'zh-Hans' */
  language: string
  parts?: Part[]
  chapters?: Chapter[] // 後方互換用
}

初期値: next-app/src/stores/projectStateAtoms.ts

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

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

export interface Manuscript {
  [sectionId: string]: {
    versions: {
      [lang: string]: ManuscriptVersion  // キーがBCP 47言語タグ
    }
  }
}

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

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

PlotTranslation(プロット翻訳)

plotTranslations: {
  [lang: string]: {
    [id: string]: PlotTranslation  // キーがBCP 47言語タグ → アイテムID
  }
}

4.4 状態管理(Atoms)

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

永続化:

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

全デフォルト値('ja', 'en')はBCP 47として有効。

4.5 MCPツール

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

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

ツール名パラメータ説明
blueperiod_create_projectlanguageBCP 47 language tag (default: ja)
blueperiod_get_raw_manuscriptlangBCP 47 language tag (default: ja)
blueperiod_edit_manuscript_structuredlangBCP 47 language tag (default: ja)
blueperiod_edit_manuscript_delimiterlangBCP 47 language tag (default: ja)
blueperiod_update_manuscriptlangBCP 47 language tag (default: ja)
blueperiod_create_manuscriptlangBCP 47 language tag (default: ja)

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("BCP 47 language tag (default: ja)...") と定義

4.6 ManuscriptService

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

getContent(get, sectionId, lang = 'ja')  // BCP 47言語タグで原稿を取得
updateContent(set, sectionId, lang, content, options?)  // BCP 47言語タグで原稿を更新

4.7 プロジェクト作成

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

  • 新規プロジェクト作成時に言語選択ドロップダウンを表示
  • detectBrowserLanguage()navigator.languages から最も近いサポート言語を自動判定
  • getSupportedLanguages() から動的に選択肢を生成
  • 編集モード時は言語選択フィールドは非表示

サービス層: next-app/src/lib/features/projects/local-project-service.ts

language: params.language || 'ja'  // フォームからの入力、未指定時は'ja'

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

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

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

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

カラム説明
titletextグローバル(英語)のタイトル(フォールバック値)
synopsistextグローバル(英語)のあらすじ(フォールバック値)
headlinetextグローバル(英語)の見出し(フォールバック値)
original_languagetextBCP 47言語タグ(例: 'ja', 'zh-Hans'
localizationsJSONB言語別メタデータマップ(唯一のソース)
default_languagetextデフォルト表示言語
title_originaltext(DB未DROP: コードからは完全に除去済み)
synopsis_originaltext(DB未DROP: コードからは完全に除去済み)
headline_originaltext(DB未DROP: コードからは完全に除去済み)
tagstext[](DB未DROP: コードからは完全に除去済み)

設計上の特徴: localizations JSONBが唯一の多言語メタデータソース。各言語のデータは localizations[BCP47_TAG] でアクセスする。

Supabase text 型のため文字数制限なし。BCP 47タグをそのまま格納可能。

5.2 TypeScript型定義

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

export type PublishedProject = {
  id: string
  title: string
  synopsis: string | null
  headline: string | null
  original_language: string | null
  localizations: Json | null   // Record<string, ProjectLocalization>
  default_language: string | null
  // ...
}

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

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

getDisplayMetadata(project, preferredLang) は以下のフォールバックチェーンで表示メタデータを決定:

localizations[preferredLang]
  → localizations[default_language || original_language]
    → localizations["en"] (GLOBAL_LANGUAGE)
      → トップレベル title/headline/synopsis (localizationsがnull/emptyの場合のみ)

原典言語のメタデータは localizations[original_language] で直接アクセス可能。

5.4 利用可能言語の抽出

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

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

Manuscriptの versions オブジェクトのキーを走査し、利用可能な言語リストを返す。BCP 47タグをそのまま返却。

5.5 パブリッシュ処理

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

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

  • localizations(唯一の多言語メタデータソース)
  • original_language
  • title, headline, synopsis(フォールバック値)

6. GUIコンポーネント

6.1 統一言語表示コンポーネント

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

コンポーネントモード表示用途
LanguageLabel mode="full"フル日本語 or 한국어 (韓国語)翻訳先選択、UI言語設定
LanguageLabel mode="code"ショートJA + Tooltipバッジ、極小領域

6.2 OriginalLanguageBadge

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

  • BCP 47コードを大文字表示(JA, ZH-HANS 等)
  • ホバーでTooltipにフル表示(side="top"collisionPadding={8}
  • Globe アイコン付きバッジ
  • 言語コードが不正な場合はコードそのものをフォールバック表示

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

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

3モードの切替:

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

BCP 47タグをそのまま保持(lang.split('-')[0] による正規化は廃止済み)。

切替フロー:

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

6.4 目次翻訳表示

ファイル: next-app/src/components/reader/TableOfContents.tsx

3段階フォールバックによる翻訳タイトル検索:

  1. BCP 47完全一致(zh-HansplotTranslations['zh-Hans']
  2. base言語フォールバック(zh-HansplotTranslations['zh']
  3. 逆方向フォールバック(zhzh-Hans / zh-Hant を順次確認)

6.5 ProjectInfoCard

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

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

6.6 言語表示の適用マッピング

箇所モード表示
ManuscriptCard バッジ・ドロップダウンフル日本語 / 한국어 (韓国語)
LeftPanel 翻訳先選択フル한국어 (韓国語)
SettingsPageClient UI言語フル日本語 / English
ReaderHeader 言語切替フル日本語 / English
OriginalLanguageBadge (Explore)ショート + TooltipJA(ホバーでフル)
ProjectInfoCard Availableショート + TooltipJA(ホバーでフル)

6.7 UI言語選択(設定画面)

ファイル: next-app/src/app/settings/SettingsPageClient.tsx

  • getUiLanguages() から動的にSelectItemを生成
  • lang.code.split('-')[0] をvalueに使用(i18nextのリソースキー zh に対応)
  • 新しいUI言語の追加: 翻訳JSON追加 + JSONの uiSupportedtrue に変更するのみ

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

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

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

ProjectMetadataDialog
  detectBrowserLanguage() → navigator.languages から最適な言語を自動判定
  言語選択ドロップダウン表示

local-project-service.ts
  language: params.language || 'ja'

structuredPlot.language = 'zh-Hans' 等

IndexedDB (projects) に保存

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

[AIエージェント] → blueperiod_update_manuscript
    projectId, sectionId, content, lang='zh-Hans'

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

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

IndexedDB (manuscripts) に保存

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

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

proxy.ts (ミドルウェア)
    accept-language-parser でサポート言語から最適なものを選択

検出結果: 'ja'

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

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

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

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

ProjectDetailsContainer.toggleLanguage()
    auto → original | global

displayLanguage = lang(BCP 47タグをそのまま保持)

TableOfContents.getTitle()
    plotTranslations[displayLanguage][id]?.title
    ↓ 未ヒット時: base言語フォールバック → 逆方向フォールバック

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

[表示更新]

7.5 パブリッシュフロー

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

publish/service.ts
    localizations = PublishWizardで構築済みの言語別メタデータマップ
    original_language = structuredPlot.language(BCP 47タグ)

Supabase: published_projects に Upsert(localizations のみ書き込み)

[読者] → Explore で閲覧

metadataUtils.getDisplayMetadata()
    フォールバックチェーン: localizations[preferredLang]
      → localizations[original_language] → localizations["en"]

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

8.1 定義・設定

箇所ファイル役割
i18n設定src/i18n/config.tsUI翻訳のリソース定義、言語検出設定
ミドルウェアsrc/proxy.tsAccept-Language解析、Cookie設定
サポート言語データpublic/languages/supported-languages.json言語一元管理
翻訳ファイルsrc/i18n/locales/{en,ja,zh}.jsonUI翻訳テキスト + language.* キー

8.2 型・スキーマ

箇所ファイル役割
StructuredPlot.languagesrc/lib/types.tsBCP 47言語タグ
Manuscript[sectionId].versions[lang]src/lib/types.ts原稿の言語バージョン(BCP 47キー)
ManuscriptVersion.isOriginal/sourceLangsrc/lib/types.ts原典・翻訳元の追跡
PublishedProject.original_languagesrc/lib/types/supabase.tsSupabase側の原典言語
plot-service schemasrc/lib/features/ai/plot-service.tsBCP 47正規表現でバリデーション

8.3 状態管理(Atoms)

箇所ファイルデフォルト
structuredPlotAtomsrc/stores/projectStateAtoms.ts'ja'
manuscriptContentChangeAtomsrc/stores/projectStateAtoms.tslang パラメータで管理
languageAtomsrc/stores/settingsAtoms.ts'en'
selectedLanguageAtomsrc/stores/readingStateAtoms.ts'ja'
publishStateAtom.originalLanguagesrc/stores/publishStateAtoms.ts'ja'

8.4 サービス層

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

8.5 MCPツール

箇所ファイルパラメータ
MCPスキーマ定義src/server/api/mcp/index.tsBCP 47 language tag
MCPブリッジsrc/hooks/useMcpBridge.ts`args.lang
エージェントツール定義src/lib/features/editorial/tools/manuscript-tools.tsBCP 47 language tag
プロジェクトツール定義src/lib/features/projects/tools.tsBCP 47 language tag

8.6 ユーティリティ

箇所ファイル役割
BCP 47ユーティリティsrc/lib/i18n/bcp47.tsバリデーション・フォールバック・表示名生成
サポート言語ローダーsrc/lib/i18n/supportedLanguages.tsJSONからの言語データアクセス
言語表示コンポーネントsrc/components/ui/language-label.tsxフル/ショート表示の統一コンポーネント
extractAvailableLanguages()src/lib/publishUtils.ts利用可能言語の抽出
getDisplayMetadata()src/lib/i18n/metadataUtils.ts優先言語に基づく表示データ選択

8.7 GUIコンポーネント

箇所ファイル役割
OriginalLanguageBadgesrc/components/ui/original-language-badge.tsxBCP 47コード表示 + Tooltip
LanguageLabelsrc/components/ui/language-label.tsxフル/ショート統一表示
ProjectDetailsContainersrc/components/details/ProjectDetailsContainer.tsx3モード言語切替
TableOfContentssrc/components/details/TableOfContents.tsx翻訳タイトル3段階フォールバック
SettingsPageClientsrc/app/settings/SettingsPageClient.tsxUI言語選択(動的生成)
ProjectInfoCardsrc/components/details/ProjectInfoCard.tsx言語バッジ群表示
ManuscriptCardsrc/components/ManuscriptCard.tsx原稿言語バージョン表示・切替
LeftPanelsrc/components/LeftPanel.tsx翻訳先言語選択
ReaderHeadersrc/components/reader/ReaderHeader.tsx読書モード言語切替

9. 解決済みの課題

以下はBCP 47対応(2026-04-24)により解決された課題の記録:

課題解決内容
z.string().length(2) でBCP 47タグが弾かれるBCP 47正規表現に緩和
lang.split('-')[0]zh-Hanszh に正規化正規化を廃止、BCP 47タグをそのまま保持
['en', 'ja', 'zh'] が複数ファイルに分散supported-languages.json で一元管理
プロジェクト作成時に言語選択不可ProjectMetadataDialog に言語選択ドロップダウン追加
LanguageFlag switch文のハードコード統一 LanguageLabel コンポーネントに集約
翻訳言語リストのハードコードgetSupportedLanguages() から動的生成
lang.toUpperCase() と3項演算子の散在getLanguageDisplayName() / LanguageLabel に集約
国旗絵文字の使用テキストのみの表示に統一(Globeアイコンは維持)
OriginalLanguageBadge が英語名のみBCP 47コード大文字 + Tooltip に変更

未解決・将来課題

課題状況
既存 zh データの zh-Hans への移行強制移行は行わない。ユーザーが必要に応じて個別対応

10. 関連ドキュメント

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

On this page

07 - Locale / 言語コードシステム
1. 概要
1.1 三層の言語管理
2. 言語コード仕様
2.1 BCP 47形式
2.2 サポート言語の一元管理
2.3 UI言語(i18n)
3. BCP 47ユーティリティ
3.1 コア関数(src/lib/i18n/bcp47.ts
3.2 サポート言語アクセス(src/lib/i18n/supportedLanguages.ts
3.3 言語名の3層表示方式
4. オフライン(ローカル)の言語処理
4.1 i18n設定
4.2 ミドルウェア(言語自動検出)
4.3 データ構造
StructuredPlot(プロジェクト原典言語)
Manuscript(原稿バージョン言語)
PlotTranslation(プロット翻訳)
4.4 状態管理(Atoms)
4.5 MCPツール
4.6 ManuscriptService
4.7 プロジェクト作成
5. オンライン(Supabase / Explore)の言語処理
5.1 データベーステーブル定義
5.2 TypeScript型定義
5.3 メタデータ表示ロジック
5.4 利用可能言語の抽出
5.5 パブリッシュ処理
6. GUIコンポーネント
6.1 統一言語表示コンポーネント
6.2 OriginalLanguageBadge
6.3 言語切替ボタン(プロジェクト詳細)
6.4 目次翻訳表示
6.5 ProjectInfoCard
6.6 言語表示の適用マッピング
6.7 UI言語選択(設定画面)
7. 言語コードのデータフロー
7.1 プロジェクト作成フロー
7.2 原稿書き込みフロー(MCP経由)
7.3 言語検出フロー(初回アクセス)
7.4 読書モードでの言語切替フロー
7.5 パブリッシュフロー
8. 言語コードが使用される全箇所のマッピング
8.1 定義・設定
8.2 型・スキーマ
8.3 状態管理(Atoms)
8.4 サービス層
8.5 MCPツール
8.6 ユーティリティ
8.7 GUIコンポーネント
9. 解決済みの課題
未解決・将来課題
10. 関連ドキュメント