ブログ

LunariaJS高度な設定:エンタープライズレベルのローカライズ管理を構築

LunariaJSの高度な設定オプションとカスタムローカライズ戦略を探索。マルチリポジトリ対応、カスタム状態ルール、複雑なファイル構造処理など、エンタープライズシナリオのソリューションを学びます。

LibDoc Team 2026年3月6日 LunariaJS 連載 92 分で読める
#LunariaJS #高度な設定 #エンタープライズ #カスタム戦略 #Monorepo

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、内部ツール

重要なポイント

  • 異なる環境に適応する条件付き設定を柔軟に活用
  • 特定のビジネスニーズを満たすカスタム戦略を設計
  • 大規模ファイルを処理するパフォーマンス最適化を有効化
  • 機密情報を保護するセキュリティ対策を設定

次のステップ

次の記事では、完全なエンタープライズ実践を行い、ゼロから完全な多言語ドキュメントプラットフォームを構築し、本シリーズのすべての知識を総合的に応用します。

お楽しみに!


💡 おすすめ読書