xyflowのフルスタック国際化実装:ノードラベルからシステム通知まで

国際化アーキテクチャの設計

xyflowにおける多言語対応は、動的テキストマッピングシステムによる3層構造で実現します:

  1. 静的コンテンツ層:ノード表示テキストやボタンラベル
  2. インタラクティブメッセージ層:ドラッグ操作時のヒントや接続状態通知
  3. システム通知層:エラーメッセージや操作ガイドのグローバル表示

既存コードへの侵入を最小限にしつつ、型システム拡張と専用フックを活用した実装手法を解説します。

型定義の拡張

nodes.tsでノードデータに多言語対応を追加します:

interface Node<Data = Record<string, unknown>> {
  id: string;
  position: { x: number; y: number };
  data: Data & {
    langData?: {
      displayText?: { [locale: string]: string };
      tooltipText?: { [locale: string]: string };
    };
  };
  // 既存プロパティ保持
}

言語処理フック

useTranslatedData.tsで言語依存データを処理します:

import { useNodeList } from './useNodeList';
import { useActiveLanguage } from './useActiveLanguage';

export function useTranslatedNodes() {
  const nodes = useNodeList();
  const { activeLang } = useActiveLanguage();
  
  return nodes.map(node => ({
    ...node,
    data: {
      ...node.data,
      displayText: node.data.langData?.displayText?.[activeLang] ?? node.data.displayText,
      tooltipText: node.data.langData?.tooltipText?.[activeLang] ?? node.data.tooltipText
    }
  }));
}

利用例:

function FlowContainer() {
  const processedNodes = useTranslatedNodes();
  
  return (
    <ReactFlow nodes={processedNodes}>
      <!-- フローデータ処理 -->
    </ReactFlow>
  );
}

言語コンテキスト管理

LanguageContext.tsxでグローバル言語状態を管理します:

import { createContext, useContext, useState } from 'react';

const LanguageState = createContext<{
  activeLang: string;
  changeLanguage: (lang: string) => void;
}>({ activeLang: 'en', changeLanguage: () => {} });

export function LanguageProvider({ children }) {
  const [activeLang, changeLanguage] = useState('en');
  
  return (
    <LanguageState.Provider value={{ activeLang, changeLanguage }}>
      {children}
    </LanguageState.Provider>
  );
}

export const useLanguageContext = () => useContext(LanguageState);

ノードコンポーネントの実装

DefaultNode.tsxで言語依存表示を処理します:

import { useLanguageContext } from '../contexts/LanguageContext';

export default function NodeComponent({ data }) {
  const { activeLang } = useLanguageContext();
  const displayText = data.langData?.displayText?.[activeLang] ?? data.displayText;
  
  return (
    <div className="flow-node">
      <span className="node-title">{displayText}</span>
      <!-- その他のUI要素 -->
    </div>
  );
}

実際の使用シナリオ

Reactプロジェクトでの実装例:

const initialNodes = [
  {
    id: 'start',
    position: { x: 100, y: 50 },
    data: {
      langData: {
        displayText: {
          'en': 'Start',
          'zh': '开始',
          'ja': '開始'
        }
      }
    }
  }
];

function App() {
  return (
    <LanguageProvider>
      <ReactFlow nodes={initialNodes} edges={connectionList} />
      <LanguageSelector />
    </LanguageProvider>
  );
}

Svelte実装のポイント

Svelte版ではtypes/nodes.tsで同様の型定義を行い、storeを活用した言語状態管理を実装します。コンポーネント内では$store構文で言語状態を参照します。

パフォーマンス最適化戦略

  • オブジェクトのシャローコンパリソンで不要な再描画を防止
  • 大規模フローの言語切替時にデバウンス処理を適用
  • 言語パッケージを分割して非同期ロード

タグ: React svelte internationalization flow-diagram node-based-ui

6月13日 16:19 投稿