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
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'], // 개발 환경에서는 두 언어만 추적
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',
},
});
사용자 정의 현지화 전략
전략 인터페이스 설계
사용자 정의 현지화 전략의 인터페이스 정의:
// 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'>;
}
사용자 정의 전략 구현
콘텐츠 복잡도 기반 전략 생성:
// 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 과다 | 파일을 하나씩 읽음 | 병렬 읽기 |
| 설정 파싱 느림 | 복잡한 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,
// 기본 인증
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({
// ...기타 설정
// 통합 설정
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,
},
},
});
요약
이 글에서는 LunariaJS의 고급 설정과 기업급 응용을 탐구했습니다:
| 주제 | 핵심 내용 |
|---|---|
| 사용자 정의 상태 규칙 | 일수 임계값, 사용자 정의 검사 함수 |
| 복잡한 경로 매핑 | 다중 파일 패턴, 다중 원본 언어 |
| 다중 저장소 관리 | Monorepo, 교차 저장소 의존성 |
| 사용자 정의 전략 | 전략 인터페이스, 구현, 등록 |
| 성능 최적화 | 점진적 빌드, 병렬 처리, 캐시 |
| 보안 권한 | 민감 파일, 접근 제어, 감사 |
| 기업 통합 | TMS, CMS, 내부 도구 |
핵심 포인트:
- 조건 설정을 유연하게 활용해 다양한 환경에 적응
- 사용자 정의 전략으로 특정 비즈니스 요구 충족
- 성능 최적화로 대규모 파일 처리
- 보안 조치로 민감 정보 보호
다음 단계
다음 글에서는 완전한 기업급 실전을 진행하고, 처음부터 완전한 다국어 문서 플랫폼을 구축하여 본 시리즈의 모든 지식을 종합적으로 활용합니다.
기대해 주세요!
💡 추천 읽기: