LunariaJS高度な設定:エンタープライズレベルのローカライズ管理を構築
LunariaJSの高度な設定オプションとカスタムローカライズ戦略を探索。マルチリポジトリ対応、カスタム状態ルール、複雑なファイル構造処理など、エンタープライズシナリオのソリューションを学びます。
LunariaJS高度な設定:エンタープライズレベルのローカライズ管理を構築
前回の記事では、@lunariajs/coreのソースコードアーキテクチャを深掘りしました。今日は、これらの知識を実践に応用し、LunariaJSの高度な設定オプションを探索し、エンタープライズシナリオでの複雑なローカライズ管理ニーズを解決しましょう。
💡 公式ドキュメント:LunariaJS 日本語ドキュメント - 高度な設定
エンタープライズシナリオの課題
典型的なエンタープライズ要件
エンタープライズプロジェクトは通常、以下のローカライズ課題に直面します:
| 課題 | 説明 | 影響 |
|---|---|---|
| マルチリポジトリ管理 | コードが複数のGitリポジトリに分散 | 翻訳状態の統一追跡が困難 |
| 複雑なファイル構造 | 異なるモジュールが異なるディレクトリ構造を持つ | 設定が複雑、間違いやすい |
| カスタムワークフロー | 既存の開発プロセスへの適応が必要 | 標準設定で要件を満たせない |
| 権限管理 | 異なるチームが異なるアクセス権限を持つ | きめ細かい権限管理が必要 |
| 大規模ファイル | 数百から数千の翻訳ファイル | パフォーマンスがボトルネックに |
| マルチソース言語 | 異なるモジュールが異なるソース言語を持つ可能性 | 状態計算ロジックが複雑化 |
本章の学習目標
この記事を通じて、以下を習得できます:
- ✅ 複雑なマルチリポジトリプロジェクトの設定
- ✅ カスタム状態判定ルールの設計
- ✅ 大規模プロジェクトのパフォーマンス最適化
- ✅ エンタープライズツールやシステムとの統合
- ✅ 高度なセキュリティと権限管理の実装
高度な設定オプション
カスタム状態ルール
LunariaJSは翻訳状態判定ロジックのカスタマイズをサポート:
// lunaria.config.ts
import { defineConfig } from '@lunariajs/core';
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
// カスタム状態判定
statusRules: {
// 「古い」状態の判定条件をカスタマイズ
outdated: {
// 日数閾値:ソースが翻訳より何日新しければ古いと判定
dayThreshold: 7, // デフォルトは0、ここでは7日に設定
// またはカスタム関数を使用
customCheck: async (source, translation) => {
// ソースファイルに重大な変更があったかチェック
const changes = await getSignificantChanges(source.path);
return changes.length > 0;
},
},
// 「欠落」状態の判定条件をカスタマイズ
missing: {
// 特定のファイルパターンを無視
ignorePatterns: ['**/internal/**', '**/draft/**'],
// またはカスタム関数を使用
customCheck: (sourcePath, translationPath) => {
// 翻訳が必要かどうかをチェック(一部のファイルは翻訳不要)
const content = fs.readFileSync(sourcePath, 'utf-8');
return !content.includes('<!-- no-translate -->');
},
},
},
files: [
{
sourcePath: 'docs/{slug}.md',
localizationPath: 'i18n/{lang}/{slug}.md',
},
],
});
複雑なパスマッピング
複雑なプロジェクト構造を処理:
// lunaria.config.ts
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
files: [
// ドキュメントファイル
{
sourcePath: 'docs/guides/{slug}.md',
localizationPath: 'docs/{lang}/guides/{slug}.md',
include: ['**/*.md'],
exclude: ['**/draft/**'],
},
// APIリファレンス
{
sourcePath: 'docs/api/{category}/{slug}.md',
localizationPath: 'docs/{lang}/api/{category}/{slug}.md',
},
// UI翻訳ファイル(JSON)
{
sourcePath: 'src/locales/en/{module}.json',
localizationPath: 'src/locales/{lang}/{module}.json',
},
// 設定ファイル
{
sourcePath: 'config/en/{slug}.yaml',
localizationPath: 'config/{lang}/{slug}.yaml',
},
],
});
マルチソースファイル設定
一部のプロジェクトは複数のソース言語ディレクトリを持つ場合があります:
// lunaria.config.ts
export default defineConfig({
// メインソース言語
sourceLanguage: 'en',
// セカンダリソース言語(特定モジュール用)
secondarySourceLanguages: {
'packages/japanese-sdk': 'ja', // このパッケージのソースは日本語
},
languages: ['en', 'ja', 'zh-cn', 'ko'],
files: [
// メインプロジェクト - 英語がソース
{
sourcePath: 'docs/{slug}.md',
localizationPath: 'docs/{lang}/{slug}.md',
sourceLanguage: 'en', // 明示的に指定
},
// Japanese SDK - 日本語がソース
{
sourcePath: 'packages/japanese-sdk/docs/{slug}.md',
localizationPath: 'packages/japanese-sdk/docs/{lang}/{slug}.md',
sourceLanguage: 'ja', // グローバル設定をオーバーライド
},
],
});
条件付き処理
条件に応じて動的に設定を調整:
// lunaria.config.ts
const isProduction = process.env.NODE_ENV === 'production';
const isCI = process.env.CI === 'true';
export default defineConfig({
sourceLanguage: 'en',
// 環境に応じて言語リストを調整
languages: isProduction
? ['en', 'zh-cn', 'ja', 'ko', 'es', 'fr', 'de']
: ['en', 'zh-cn'], // 開発環境では2言語のみ追跡
files: [
{
sourcePath: 'docs/{slug}.md',
localizationPath: 'i18n/{lang}/{slug}.md',
// CI環境ではドラフトファイルを除外
exclude: isCI ? ['**/draft/**', '**/wip/**'] : [],
},
],
dashboard: {
outputDir: isProduction ? 'dist/i18n-status' : 'public/i18n-status',
title: isProduction ? 'Production i18n Status' : 'Development i18n Status',
},
});
マルチリポジトリローカライズ管理
Monorepoシナリオの設定
Monorepoプロジェクト(pnpm workspacesやTurborepoを使用)の場合:
my-monorepo/
├── packages/
│ ├── core/
│ │ ├── docs/
│ │ │ └── en/
│ │ └── i18n/
│ │ ├── zh-cn/
│ │ └── ja/
│ ├── cli/
│ │ ├── docs/
│ │ │ └── en/
│ │ └── i18n/
│ │ ├── zh-cn/
│ │ └── ja/
│ └── ui/
│ ├── docs/
│ │ └── en/
│ └── i18n/
│ ├── zh-cn/
│ └── ja/
├── lunaria.config.ts
└── package.json
設定例:
// lunaria.config.ts
import { defineConfig } from '@lunariajs/core';
import { glob } from 'glob';
// すべてのパッケージを自動検出
const packages = await glob('packages/*');
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
// 各パッケージに独立したファイル設定を生成
files: packages.flatMap(pkg => [
{
sourcePath: `${pkg}/docs/{slug}.md`,
localizationPath: `${pkg}/i18n/{lang}/{slug}.md`,
// パッケージ名をグループとして使用
group: pkg.replace('packages/', ''),
},
]),
dashboard: {
outputDir: 'docs/i18n-status',
title: 'My Monorepo - Translation Status',
// パッケージ別にグループ化して表示
groupBy: 'group',
},
});
複数リポジトリの統合ダッシュボード
複数の独立したGitリポジトリの場合、「アグリゲーター」プロジェクトを作成可能:
i18n-aggregator/
├── lunaria.config.ts
├── package.json
└── repos/
├── main-docs/ # メインドキュメントリポジトリ
├── api-docs/ # APIドキュメントリポジトリ
└── blog-content/ # ブログコンテンツリポジトリ
アグリゲーター設定:
// lunaria.config.ts
import { defineConfig } from '@lunariajs/core';
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
// 複数リポジトリをアグリゲート
repositories: [
{
name: 'main-docs',
path: './repos/main-docs',
files: [
{
sourcePath: 'docs/{slug}.md',
localizationPath: 'i18n/{lang}/{slug}.md',
},
],
},
{
name: 'api-docs',
path: './repos/api-docs',
files: [
{
sourcePath: 'src/content/docs/{slug}.md',
localizationPath: 'src/content/docs/{lang}/{slug}.md',
},
],
},
{
name: 'blog-content',
path: './repos/blog-content',
files: [
{
sourcePath: 'posts/{slug}.md',
localizationPath: 'posts/{lang}/{slug}.md',
},
],
},
],
dashboard: {
outputDir: 'public/i18n-status',
title: 'All Projects - Translation Status',
},
});
クロスリポジトリ依存の処理
クロスリポジトリの翻訳依存を処理:
// lunaria.config.ts
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
// 翻訳依存を定義
dependencies: {
// api-docsの翻訳はmain-docsの翻訳に依存
'api-docs': ['main-docs'],
// blogの翻訳はmain-docsとapi-docsに依存
'blog-content': ['main-docs', 'api-docs'],
},
// 依存チェック
checkDependencies: true,
// 依存が満たされない場合の処理
onDependencyMissing: 'warn', // 'warn' | 'error' | 'ignore'
files: [
// ...
],
});
カスタムローカライズ戦略
戦略インターフェース設計
カスタムローカライズ戦略のインターフェースを定義:
// src/strategies/types.ts
import { TranslationStatus, FileInfo } from '@lunariajs/core';
/**
* ローカライズ戦略インターフェース
*/
export interface LocalizationStrategy {
/** 戦略名 */
name: string;
/** 戦略の説明 */
description: string;
/**
* 翻訳状態を計算
*/
calculateStatus(
source: FileInfo,
translation: FileInfo | null,
context: StrategyContext
): Promise<TranslationStatus>;
/**
* 翻訳が必要かどうか
*/
needsTranslation?(
source: FileInfo,
context: StrategyContext
): Promise<boolean>;
/**
* 翻訳優先度を取得
*/
getPriority?(
source: FileInfo,
translation: FileInfo | null,
context: StrategyContext
): Promise<'high' | 'medium' | 'low'>;
}
/**
* 戦略コンテキスト
*/
export interface StrategyContext {
/** 言語コード */
language: string;
/** プロジェクト設定 */
config: LunariaConfig;
/** Git情報 */
git: {
lastCommit: string;
author: string;
date: Date;
};
/** カスタムデータ */
data?: Record<string, unknown>;
}
カスタム戦略の実装
コンテンツ複雑度に基づく戦略を作成:
// src/strategies/content-complexity.ts
import { LocalizationStrategy, StrategyContext, FileInfo } from './types';
/**
* コンテンツ複雑度ベースの戦略
* ソースファイルの複雑度に応じて「古い」判定基準を動的に調整
*/
export const ContentComplexityStrategy: LocalizationStrategy = {
name: 'content-complexity',
description: 'コンテンツ複雑度に応じて翻訳状態判定基準を調整',
async calculateStatus(
source: FileInfo,
translation: FileInfo | null,
context: StrategyContext
) {
// 翻訳が存在しない場合、欠落を返す
if (!translation) {
return { status: 'missing', reason: 'Translation file not found' };
}
// コンテンツ複雑度を計算
const complexity = calculateComplexity(source.content);
// 複雑度に応じて日数閾値を決定
const dayThreshold = getDayThreshold(complexity);
// 時間差をチェック
const sourceTime = source.lastModified.getTime();
const translationTime = translation.lastModified.getTime();
const daysDiff = (sourceTime - translationTime) / (1000 * 60 * 60 * 24);
if (daysDiff > dayThreshold) {
return {
status: 'outdated',
reason: `Source is ${daysDiff.toFixed(0)} days newer (threshold: ${dayThreshold})`,
complexity,
};
}
return {
status: 'done',
complexity,
};
},
async getPriority(source: FileInfo, translation: FileInfo | null) {
const complexity = calculateComplexity(source.content);
if (complexity > 0.8) return 'high';
if (complexity > 0.5) return 'medium';
return 'low';
},
};
/**
* コンテンツ複雑度を計算(0-1)
*/
function calculateComplexity(content: string): number {
const factors = {
// コードブロック数
codeBlocks: (content.match(/```/g) || []).length / 10,
// リンク数
links: (content.match(/\[.*?\]\(.*?\)/g) || []).length / 20,
// 特殊構文
specialSyntax: (content.match(/\{.*?\}/g) || []).length / 30,
// ファイル長
length: content.length / 10000,
};
// 重み付き平均
return (
factors.codeBlocks * 0.3 +
factors.links * 0.2 +
factors.specialSyntax * 0.3 +
factors.length * 0.2
);
}
/**
* 複雑度に応じて日数閾値を取得
*/
function getDayThreshold(complexity: number): number {
// 複雑度が高いほど閾値が大きい(翻訳者により多くの時間を与える)
if (complexity > 0.8) return 30;
if (complexity > 0.5) return 14;
return 7;
}
カスタム戦略の登録
// lunaria.config.ts
import { defineConfig } from '@lunariajs/core';
import { ContentComplexityStrategy } from './src/strategies/content-complexity';
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
// カスタム戦略を登録
strategies: {
// デフォルト戦略
default: 'git-timestamp',
// ファイルパターンごとに異なる戦略を適用
rules: [
{
pattern: 'docs/guides/**/*.md',
strategy: 'content-complexity',
},
{
pattern: 'docs/api/**/*.md',
strategy: 'strict', // APIドキュメントは厳格モード
},
{
pattern: '**/changelog.md',
strategy: 'lenient', // 変更履歴は寛容モード
},
],
// カスタム戦略を登録
custom: [ContentComplexityStrategy],
},
files: [
{
sourcePath: 'docs/{slug}.md',
localizationPath: 'i18n/{lang}/{slug}.md',
},
],
});
大規模プロジェクトの最適化
パフォーマンスボトルネック分析
大規模プロジェクトの一般的なパフォーマンスボトルネック:
| ボトルネック | 原因 | 解決策 |
|---|---|---|
| Git操作が多すぎる | 各ファイルでGit履歴をクエリ | Git情報を一括クエリ |
| ファイルI/Oが多すぎる | ファイルを1つずつ読み込み | 並列読み込み |
| 設定解析が遅い | 複雑なglobパターン | glob結果をキャッシュ |
| ダッシュボード生成が遅い | 大量のテンプレートレンダリング | インクリメンタルレンダリング |
インクリメンタルビルド戦略
変更されたファイルのみを処理:
// lunaria.config.ts
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
// インクリメンタルビルドを有効化
incremental: {
enabled: true,
// キャッシュディレクトリ
cacheDir: '.lunaria/cache',
// キャッシュ有効期限(秒)
cacheTTL: 3600,
// 強制フルビルドの条件
forceRebuild: {
// 設定ファイル変更時は強制フルビルド
configChanged: true,
// 新しい言語追加時は強制フルビルド
languageAdded: true,
},
},
files: [
{
sourcePath: 'docs/{slug}.md',
localizationPath: 'i18n/{lang}/{slug}.md',
},
],
});
並列処理
並列処理でビルドを高速化:
// lunaria.config.ts
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
// 並列処理設定
parallel: {
enabled: true,
// ワーカースレッド数(デフォルトはCPUコア数)
workers: 4,
// バッチごとのファイル数
batchSize: 100,
// タイムアウト(ミリ秒)
timeout: 60000,
},
files: [
// ...
],
});
キャッシュメカニズム
繰り返しビルドを高速化するキャッシュ設定:
// lunaria.config.ts
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
// キャッシュ設定
cache: {
// Git情報キャッシュ
git: {
enabled: true,
ttl: 86400, // 24時間
},
// ファイル内容キャッシュ
content: {
enabled: true,
ttl: 3600, // 1時間
},
// 状態計算キャッシュ
status: {
enabled: true,
ttl: 3600, // 1時間
},
},
files: [
// ...
],
});
セキュリティと権限
機密ファイルの処理
追跡から機密ファイルを除外:
// lunaria.config.ts
export default defineConfig({
sourceLanguage: 'en',
languages: ['en', 'zh-cn', 'ja', 'ko'],
files: [
{
sourcePath: 'docs/{slug}.md',
localizationPath: 'i18n/{lang}/{slug}.md',
// 機密ファイルを除外
exclude: [
'**/internal/**',
'**/.env*',
'**/secrets/**',
'**/admin/**',
],
},
],
// グローバル除外ルール
globalExclude: [
'**/node_modules/**',
'**/.git/**',
'**/dist/**',
'**/.env*',
'**/credentials*',
],
});
アクセス制御
ダッシュボードにアクセス制御を追加:
// lunaria.config.ts
export default defineConfig({
// ...その他の設定
dashboard: {
outputDir: 'public/i18n-status',
title: 'Translation Status',
// アクセス制御
accessControl: {
enabled: true,
// Basic認証
basicAuth: {
enabled: process.env.NODE_ENV === 'production',
username: process.env.I18N_DASHBOARD_USER,
password: process.env.I18N_DASHBOARD_PASS,
},
// IPホワイトリスト
ipWhitelist: [
'192.168.1.0/24',
'10.0.0.0/8',
],
// またはカスタム認証関数を使用
customAuth: async (request) => {
const token = request.headers.get('Authorization');
return validateToken(token);
},
},
},
});
監査ログ
監査ログ記録を有効化:
// lunaria.config.ts
export default defineConfig({
// ...その他の設定
// 監査ログ
audit: {
enabled: true,
// ログレベル
level: 'info', // 'debug' | 'info' | 'warn' | 'error'
// ログ出力
outputs: [
{ type: 'file', path: 'logs/lunaria.log' },
{ type: 'console' },
],
// 記録するイベント
events: [
'config.load',
'status.calculate',
'dashboard.generate',
'api.access',
],
},
});
エンタープライズツールとの統合
翻訳管理システムとの統合
プロフェッショナル翻訳管理プラットフォームとの統合:
// lunaria.config.ts
export default defineConfig({
// ...その他の設定
// 統合設定
integrations: {
// Crowdin統合
crowdin: {
enabled: true,
projectId: process.env.CROWDIN_PROJECT_ID,
apiToken: process.env.CROWDIN_API_TOKEN,
// 同期設定
sync: {
direction: 'bidirectional', // 'push' | 'pull' | 'bidirectional'
interval: 3600, // 同期間隔(秒)
},
},
// またはTransifexを使用
transifex: {
enabled: false,
organization: 'your-org',
project: 'your-project',
apiToken: process.env.TRANSIFEX_API_TOKEN,
},
},
});
CMSとの統合
コンテンツ管理システムとの統合:
// lunaria.config.ts
export default defineConfig({
// ...その他の設定
integrations: {
// Contentful CMS
contentful: {
enabled: true,
spaceId: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
// コンテンツマッピング
contentMapping: {
'blogPost': 'posts/{slug}.md',
'documentation': 'docs/{slug}.md',
},
},
// またはStrapiを使用
strapi: {
enabled: false,
apiUrl: process.env.STRAPI_API_URL,
apiToken: process.env.STRAPI_API_TOKEN,
},
},
});
内部ツール用カスタムアダプター
内部ツール用のカスタムアダプターを作成:
// src/adapters/internal-tool.ts
import { Adapter, FileInfo, TranslationStatus } from '@lunariajs/core';
/**
* 内部ツールアダプター
*/
export class InternalToolAdapter implements Adapter {
name = 'internal-tool';
constructor(private config: InternalToolConfig) {}
async syncTranslations(
files: FileInfo[],
statuses: TranslationStatus[]
): Promise<void> {
// 状態を内部システムに同期
const response = await fetch(`${this.config.apiUrl}/translations/sync`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiToken}`,
},
body: JSON.stringify({
files,
statuses,
timestamp: new Date().toISOString(),
}),
});
if (!response.ok) {
throw new Error(`Failed to sync: ${response.statusText}`);
}
}
async getTranslationAssignments(
file: FileInfo,
language: string
): Promise<Assignment[]> {
// 内部システムから翻訳割り当て情報を取得
const response = await fetch(
`${this.config.apiUrl}/assignments?file=${file.path}&lang=${language}`
);
return response.json();
}
}
まとめ
この記事では、LunariaJSの高度な設定とエンタープライズ応用を探索しました:
| トピック | キーコンテンツ |
|---|---|
| カスタム状態ルール | 日数閾値、カスタムチェック関数 |
| 複雑なパスマッピング | マルチファイルパターン、マルチソース言語 |
| マルチリポジトリ管理 | Monorepo、クロスリポジトリ依存 |
| カスタム戦略 | 戦略インターフェース、実装、登録 |
| パフォーマンス最適化 | インクリメンタルビルド、並列処理、キャッシュ |
| セキュリティ権限 | 機密ファイル、アクセス制御、監査 |
| エンタープライズ統合 | TMS、CMS、内部ツール |
重要なポイント:
- 異なる環境に適応する条件付き設定を柔軟に活用
- 特定のビジネスニーズを満たすカスタム戦略を設計
- 大規模ファイルを処理するパフォーマンス最適化を有効化
- 機密情報を保護するセキュリティ対策を設定
次のステップ
次の記事では、完全なエンタープライズ実践を行い、ゼロから完全な多言語ドキュメントプラットフォームを構築し、本シリーズのすべての知識を総合的に応用します。
お楽しみに!
💡 おすすめ読書: