博客
深入 @lunariajs/core 源码:理解 LunariaJS 的核心架构
深入剖析 LunariaJS 核心库的架构设计和实现原理,从源码层面理解文件解析、状态追踪、配置处理等核心模块,为自定义扩展和高级用法打下基础。
LibDoc Team 2026年3月6日 LunariaJS 专栏 131 分钟阅读
#LunariaJS
#源码解析
#核心架构
#@lunariajs/core
#TypeScript
深入 @lunariajs/core 源码:理解 LunariaJS 的核心架构
在前面七篇入门文章中,我们全面学习了 LunariaJS 的安装、配置、CLI 命令、仪表板、Git 工作流、Starlight 集成和 CI/CD 集成。现在,让我们深入源码层面,理解 LunariaJS 的核心架构和实现原理。
💡 官方文档:LunariaJS 中文文档 - API 参考
核心架构概览
整体架构图
LunariaJS 的核心架构可以概括为以下层次:
┌─────────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ (lunaria init/build/preview) │
├─────────────────────────────────────────────────────────────────┤
│ API Layer │
│ (Public API for external use) │
├─────────────────────────────────────────────────────────────────┤
│ Core Modules │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Config │ │ File │ │ Status │ │
│ │ System │ │ Parsers │ │ Tracker │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Git │ │ Dashboard │ │ Reporter │ │
│ │ Client │ │ Generator │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Infrastructure │
│ (File System, Git, Logger, Utils) │
└─────────────────────────────────────────────────────────────────┘
核心模块职责
| 模块 | 职责 | 核心文件 |
|---|---|---|
| Config System | 配置加载、验证、默认值处理 | config.ts |
| File Parsers | 解析不同格式的本地化文件 | parsers/ |
| Status Tracker | 追踪翻译状态(done/outdated/missing) | status.ts |
| Git Client | Git 操作封装(获取提交历史、文件状态) | git.ts |
| Dashboard Generator | 生成静态 HTML 仪表板 | dashboard/ |
| Reporter | 生成状态报告和日志输出 | reporter.ts |
技术栈选择
LunariaJS 选择以下技术栈:
| 技术 | 用途 | 选择原因 |
|---|---|---|
| TypeScript | 主要语言 | 类型安全,更好的 IDE 支持 |
| Zod | Schema 验证 | 配置文件验证 |
| Vite | 构建工具 | 快速的 ES 模块支持 |
| simple-git | Git 操作 | Node.js Git 客户端 |
| handlebars | 模板引擎 | 仪表板 HTML 生成 |
目录结构
@lunariajs/core/
├── src/
│ ├── index.ts # 公开 API 入口
│ ├── config.ts # 配置系统
│ ├── status.ts # 状态追踪
│ ├── git.ts # Git 操作
│ ├── reporter.ts # 报告生成
│ ├── parsers/ # 文件解析器
│ │ ├── index.ts
│ │ ├── json.ts
│ │ ├── yaml.ts
│ │ └── markdown.ts
│ ├── dashboard/ # 仪表板生成
│ │ ├── index.ts
│ │ ├── templates/
│ │ └── assets/
│ ├── types/ # 类型定义
│ │ ├── config.ts
│ │ └── status.ts
│ └── utils/ # 工具函数
│ ├── file.ts
│ └── logger.ts
├── package.json
├── tsconfig.json
└── README.md
配置系统
配置类型定义
LunariaJS 使用 TypeScript 定义严格的配置类型:
// src/types/config.ts
/**
* 支持的文件格式
*/
export type FileFormat = 'json' | 'yaml' | 'md' | 'csv';
/**
* 语言配置
*/
export interface LocaleConfig {
/** 语言标签(如 'en', 'zh-cn') */
label: string;
/** 语言方向 */
dir?: 'ltr' | 'rtl';
/** 自定义数据 */
[key: string]: unknown;
}
/**
* 文件模式配置
*/
export interface FilePattern {
/** 源语言文件路径模式 */
sourcePath: string;
/** 翻译文件路径模式 */
localizationPath: string;
/** 包含的文件 glob 模式 */
include?: string[];
/** 排除的文件 glob 模式 */
exclude?: string[];
}
/**
* 仪表板配置
*/
export interface DashboardConfig {
/** 输出目录 */
outputDir: string;
/** 仪表板标题 */
title?: string;
/** 仪表板描述 */
description?: string;
/** 界面语言 */
uiLanguage?: string;
/** 自定义 CSS 路径 */
customCss?: string;
/** 站点 URL(用于 SEO) */
site?: string;
}
/**
* LunariaJS 完整配置
*/
export interface LunariaConfig {
/** 源语言 */
sourceLanguage: string;
/** 所有支持的语言 */
languages: string[];
/** 语言配置映射 */
locales?: Record<string, LocaleConfig>;
/** 文件模式列表 */
files: FilePattern[];
/** 仪表板配置 */
dashboard?: DashboardConfig;
/** 忽略的关键词(用于检测占位符翻译) */
ignoreKeywords?: string[];
}
配置加载机制
配置加载过程分为三个阶段:
// src/config.ts
import { promises as fs } from 'fs';
import { pathExists } from './utils/file';
import { LunariaConfig } from './types/config';
import { defaultConfig } from './config/defaults';
/**
* 配置加载器
*/
export async function loadConfig(
configPath: string = 'lunaria.config.json'
): Promise<LunariaConfig> {
// 阶段 1: 尝试加载用户配置
const userConfig = await loadUserConfig(configPath);
// 阶段 2: 合并默认配置
const mergedConfig = mergeConfig(defaultConfig, userConfig);
// 阶段 3: 验证配置
const validatedConfig = validateConfig(mergedConfig);
return validatedConfig;
}
/**
* 加载用户配置文件
*/
async function loadUserConfig(
configPath: string
): Promise<Partial<LunariaConfig>> {
// 支持 JSON 和 JS/TS 格式
if (configPath.endsWith('.json')) {
return loadJsonConfig(configPath);
} else if (configPath.endsWith('.js') || configPath.endsWith('.ts')) {
return loadJsConfig(configPath);
}
// 默认尝试 JSON
return loadJsonConfig(configPath);
}
/**
* 加载 JSON 配置
*/
async function loadJsonConfig(
configPath: string
): Promise<Partial<LunariaConfig>> {
if (!(await pathExists(configPath))) {
return {};
}
const content = await fs.readFile(configPath, 'utf-8');
return JSON.parse(content);
}
/**
* 加载 JavaScript/TypeScript 配置
*/
async function loadJsConfig(
configPath: string
): Promise<Partial<LunariaConfig>> {
// 动态导入 ES 模块
const module = await import(require.resolve(configPath, { paths: [process.cwd()] }));
return module.default || module;
}
/**
* 合并配置(深度合并)
*/
function mergeConfig(
defaultCfg: LunariaConfig,
userCfg: Partial<LunariaConfig>
): LunariaConfig {
return {
...defaultCfg,
...userCfg,
dashboard: {
...defaultCfg.dashboard,
...userCfg.dashboard,
},
};
}
配置验证
使用 Zod 进行严格的配置验证:
// src/config/validation.ts
import { z } from 'zod';
/**
* 配置 Schema 定义
*/
const LunariaConfigSchema = z.object({
sourceLanguage: z.string().min(1, 'sourceLanguage is required'),
languages: z.array(z.string()).min(1, 'At least one language is required'),
files: z.array(
z.object({
sourcePath: z.string(),
localizationPath: z.string(),
include: z.array(z.string()).optional(),
exclude: z.array(z.string()).optional(),
})
).min(1, 'At least one file pattern is required'),
dashboard: z.object({
outputDir: z.string().default('lunaria-dashboard'),
title: z.string().optional(),
description: z.string().optional(),
}).optional(),
});
/**
* 验证配置
*/
export function validateConfig(config: unknown): LunariaConfig {
const result = LunariaConfigSchema.safeParse(config);
if (!result.success) {
const errors = result.error.errors.map(
(e) => `${e.path.join('.')}: ${e.message}`
);
throw new Error(`Invalid configuration:\n${errors.join('\n')}`);
}
// 额外的语义验证
validateSemantics(result.data);
return result.data;
}
/**
* 语义验证
*/
function validateSemantics(config: LunariaConfig): void {
// 检查源语言是否在语言列表中
if (!config.languages.includes(config.sourceLanguage)) {
throw new Error(
`sourceLanguage "${config.sourceLanguage}" must be included in languages array`
);
}
// 检查路径模式中的占位符
for (const pattern of config.files) {
validatePathPattern(pattern.sourcePath, 'sourcePath');
validatePathPattern(pattern.localizationPath, 'localizationPath');
}
}
/**
* 验证路径模式
*/
function validatePathPattern(pattern: string, fieldName: string): void {
// 必须包含 {slug} 占位符
if (!pattern.includes('{slug}')) {
console.warn(
`Warning: ${fieldName} "${pattern}" does not contain {slug} placeholder. ` +
`This may result in incorrect file matching.`
);
}
// localizationPath 必须包含 {lang} 占位符
if (fieldName === 'localizationPath' && !pattern.includes('{lang}')) {
throw new Error(
`localizationPath "${pattern}" must contain {lang} placeholder`
);
}
}
文件解析器
解析器接口设计
LunariaJS 定义了统一的解析器接口:
// src/parsers/types.ts
/**
* 本地化文件内容
*/
export interface LocalizedContent {
/** 文件路径 */
path: string;
/** 语言代码 */
lang: string;
/** 键值对内容 */
entries: Record<string, string>;
/** 原始内容 */
raw: string;
/** 最后修改时间 */
lastModified?: Date;
}
/**
* 文件解析器接口
*/
export interface FileParser {
/** 支持的文件扩展名 */
extensions: string[];
/** 解析文件内容 */
parse(content: string, path: string): Promise<LocalizedContent>;
/** 序列化内容(用于生成文件) */
stringify(data: Record<string, string>): string;
}
JSON 解析器
// src/parsers/json.ts
import { FileParser, LocalizedContent } from './types';
export const JsonParser: FileParser = {
extensions: ['.json'],
async parse(content: string, path: string): Promise<LocalizedContent> {
try {
const data = JSON.parse(content);
// 扁平化嵌套对象
const entries = flattenObject(data);
return {
path,
lang: extractLangFromPath(path),
entries,
raw: content,
};
} catch (error) {
throw new Error(`Failed to parse JSON file ${path}: ${error}`);
}
},
stringify(data: Record<string, string>): string {
// 将扁平结构还原为嵌套对象
const nested = unflattenObject(data);
return JSON.stringify(nested, null, 2);
},
};
/**
* 扁平化嵌套对象
* 例如: { a: { b: 'value' } } => { 'a.b': 'value' }
*/
function flattenObject(obj: Record<string, any>, prefix = ''): Record<string, string> {
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(obj)) {
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'string') {
result[newKey] = value;
} else if (typeof value === 'object' && value !== null) {
Object.assign(result, flattenObject(value, newKey));
}
}
return result;
}
/**
* 从路径中提取语言代码
*/
function extractLangFromPath(path: string): string {
const match = path.match(/\/([a-z]{2}(-[a-z]{2})?)\//i);
return match ? match[1] : 'unknown';
}
YAML 解析器
// src/parsers/yaml.ts
import { FileParser, LocalizedContent } from './types';
import * as yaml from 'js-yaml';
export const YamlParser: FileParser = {
extensions: ['.yaml', '.yml'],
async parse(content: string, path: string): Promise<LocalizedContent> {
try {
const data = yaml.load(content);
if (typeof data !== 'object' || data === null) {
throw new Error('YAML content must be an object');
}
const entries = flattenObject(data as Record<string, any>);
return {
path,
lang: extractLangFromPath(path),
entries,
raw: content,
};
} catch (error) {
throw new Error(`Failed to parse YAML file ${path}: ${error}`);
}
},
stringify(data: Record<string, string>): string {
const nested = unflattenObject(data);
return yaml.dump(nested, { indent: 2, lineWidth: -1 });
},
};
Markdown 解析器
Markdown 文件的处理略有不同,因为它不是简单的键值对:
// src/parsers/markdown.ts
import { FileParser, LocalizedContent } from './types';
import { matter } from 'gray-matter';
export const MarkdownParser: FileParser = {
extensions: ['.md', '.mdx'],
async parse(content: string, path: string): Promise<LocalizedContent> {
try {
// 解析 frontmatter
const { data: frontmatter, content: body } = matter(content);
// 将 frontmatter 和 body 作为不同的条目
const entries: Record<string, string> = {
// frontmatter 条目
...flattenObject(frontmatter || {}),
// body 作为一个整体条目
'_body': body.trim(),
};
return {
path,
lang: extractLangFromPath(path),
entries,
raw: content,
};
} catch (error) {
throw new Error(`Failed to parse Markdown file ${path}: ${error}`);
}
},
stringify(data: Record<string, string>): string {
const { _body, ...frontmatter } = data;
const nested = unflattenObject(frontmatter);
const frontmatterStr = Object.keys(nested).length > 0
? matter.stringify(_body || '', nested)
: _body || '';
return frontmatterStr;
},
};
解析器注册和选择
// src/parsers/index.ts
import { FileParser } from './types';
import { JsonParser } from './json';
import { YamlParser } from './yaml';
import { MarkdownParser } from './markdown';
/**
* 解析器注册表
*/
const parsers: FileParser[] = [
JsonParser,
YamlParser,
MarkdownParser,
];
/**
* 根据文件扩展名获取解析器
*/
export function getParser(filePath: string): FileParser | null {
const ext = filePath.toLowerCase();
for (const parser of parsers) {
if (parser.extensions.some(e => ext.endsWith(e))) {
return parser;
}
}
return null;
}
/**
* 解析文件
*/
export async function parseFile(
filePath: string,
content: string
): Promise<LocalizedContent> {
const parser = getParser(filePath);
if (!parser) {
// 默认使用文本解析器
return {
path: filePath,
lang: extractLangFromPath(filePath),
entries: { '_raw': content },
raw: content,
};
}
return parser.parse(content, filePath);
}
状态追踪引擎
状态计算核心逻辑
状态追踪是 LunariaJS 的核心功能:
// src/status.ts
import { LunariaConfig } from './types/config';
import { FileStatus, TranslationStatus } from './types/status';
import { getFileLastModified } from './git';
/**
* 计算单个文件的翻译状态
*/
export async function calculateFileStatus(
sourcePath: string,
translationPath: string,
lang: string,
config: LunariaConfig
): Promise<FileStatus> {
// 检查翻译文件是否存在
const translationExists = await fileExists(translationPath);
if (!translationExists) {
return {
path: translationPath,
lang,
status: 'missing',
sourcePath,
};
}
// 获取最后修改时间
const sourceLastModified = await getFileLastModified(sourcePath);
const translationLastModified = await getFileLastModified(translationPath);
// 比较时间戳
if (translationLastModified >= sourceLastModified) {
return {
path: translationPath,
lang,
status: 'done',
sourcePath,
lastModified: translationLastModified,
};
}
// 翻译过时
const daysOutdated = Math.floor(
(sourceLastModified.getTime() - translationLastModified.getTime()) /
(1000 * 60 * 60 * 24)
);
return {
path: translationPath,
lang,
status: 'outdated',
sourcePath,
lastModified: translationLastModified,
sourceLastModified,
daysOutdated,
};
}
/**
* 计算所有文件的翻译状态
*/
export async function calculateAllStatus(
config: LunariaConfig
): Promise<TranslationStatus> {
const status: TranslationStatus = {
sourceLanguage: config.sourceLanguage,
languages: {},
totalFiles: 0,
generatedAt: new Date(),
};
// 初始化语言状态
for (const lang of config.languages) {
status.languages[lang] = {
total: 0,
done: 0,
outdated: 0,
missing: 0,
files: [],
};
}
// 遍历所有文件模式
for (const pattern of config.files) {
const sourceFiles = await globFiles(pattern.sourcePath, pattern.include, pattern.exclude);
for (const sourceFile of sourceFiles) {
status.totalFiles++;
const slug = extractSlug(sourceFile, pattern.sourcePath);
// 计算每种目标语言的状态
for (const lang of config.languages) {
if (lang === config.sourceLanguage) continue;
const translationPath = resolveTranslationPath(
pattern.localizationPath,
lang,
slug
);
const fileStatus = await calculateFileStatus(
sourceFile,
translationPath,
lang,
config
);
status.languages[lang].total++;
status.languages[lang].files.push(fileStatus);
switch (fileStatus.status) {
case 'done':
status.languages[lang].done++;
break;
case 'outdated':
status.languages[lang].outdated++;
break;
case 'missing':
status.languages[lang].missing++;
break;
}
}
}
}
return status;
}
/**
* 计算翻译完成度百分比
*/
export function calculateProgress(status: TranslationStatus, lang: string): number {
const langStatus = status.languages[lang];
if (!langStatus || langStatus.total === 0) return 0;
return Math.round((langStatus.done / langStatus.total) * 100);
}
Git 操作封装
// src/git.ts
import simpleGit, { SimpleGit } from 'simple-git';
let gitClient: SimpleGit | null = null;
/**
* 获取 Git 客户端实例
*/
function getGitClient(): SimpleGit {
if (!gitClient) {
gitClient = simpleGit();
}
return gitClient;
}
/**
* 获取文件的最后修改时间
*/
export async function getFileLastModified(filePath: string): Promise<Date> {
const git = getGitClient();
try {
// 获取文件最后修改的提交
const log = await git.log(['-1', '--format=%ct', '--', filePath]);
if (log.latest) {
const timestamp = parseInt(log.latest.hash, 10);
return new Date(timestamp * 1000);
}
// 如果没有 Git 历史,使用文件系统时间
const stats = await fs.stat(filePath);
return stats.mtime;
} catch (error) {
// Git 操作失败,使用文件系统时间
const stats = await fs.stat(filePath);
return stats.mtime;
}
}
/**
* 获取文件的完整提交历史
*/
export async function getFileHistory(
filePath: string,
limit: number = 10
): Promise<FileCommit[]> {
const git = getGitClient();
try {
const log = await git.log(['-' + limit, '--format=%H|%ct|%an|%s', '--', filePath]);
return log.all.map(commit => {
const [hash, timestamp, author, message] = commit.hash.split('|');
return {
hash,
date: new Date(parseInt(timestamp, 10) * 1000),
author,
message,
};
});
} catch (error) {
return [];
}
}
/**
* 检查是否是 Git 仓库
*/
export async function isGitRepository(): Promise<boolean> {
const git = getGitClient();
try {
await git.status();
return true;
} catch {
return false;
}
}
仪表板生成器
模板系统
LunariaJS 使用 Handlebars 模板引擎生成仪表板:
// src/dashboard/generator.ts
import Handlebars from 'handlebars';
import { TranslationStatus } from '../types/status';
import { readTemplate, copyAssets } from './utils';
// 注册 Handlebars 助手
Handlebars.registerHelper('progressBar', (progress: number) => {
const color = progress >= 90 ? 'green' : progress >= 70 ? 'yellow' : 'red';
return new Handlebars.SafeString(
`<div class="progress-bar">
<div class="progress-fill ${color}" style="width: ${progress}%"></div>
</div>`
);
});
Handlebars.registerHelper('statusIcon', (status: string) => {
const icons: Record<string, string> = {
done: '✅',
outdated: '⚠️',
missing: '❌',
};
return icons[status] || '❓';
});
Handlebars.registerHelper('formatDate', (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
}).format(new Date(date));
});
/**
* 生成仪表板 HTML
*/
export async function generateDashboard(
status: TranslationStatus,
outputDir: string
): Promise<void> {
// 读取模板
const indexTemplate = await readTemplate('index.html');
const compiledTemplate = Handlebars.compile(indexTemplate);
// 准备模板数据
const templateData = {
title: 'Localization Status',
generatedAt: new Date().toISOString(),
sourceLanguage: status.sourceLanguage,
languages: Object.entries(status.languages).map(([lang, data]) => ({
code: lang,
total: data.total,
done: data.done,
outdated: data.outdated,
missing: data.missing,
progress: Math.round((data.done / data.total) * 100),
files: data.files,
})),
totalFiles: status.totalFiles,
};
// 渲染 HTML
const html = compiledTemplate(templateData);
// 写入文件
await fs.writeFile(path.join(outputDir, 'index.html'), html);
// 复制静态资源
await copyAssets(outputDir);
// 写入 JSON 数据(供 API 使用)
await fs.writeFile(
path.join(outputDir, 'data', 'status.json'),
JSON.stringify(status, null, 2)
);
}
模板示例
<!-- src/dashboard/templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{title}}</title>
<link rel="stylesheet" href="assets/styles.css">
</head>
<body>
<header>
<h1>🌙 {{title}}</h1>
<p class="subtitle">Generated at {{formatDate generatedAt}}</p>
</header>
<main>
<section class="overview">
<h2>Translation Progress</h2>
<div class="language-grid">
{{#each languages}}
<div class="language-card">
<h3>{{code}}</h3>
{{progressBar progress}}
<p class="progress-text">{{progress}}% Complete</p>
<ul class="stats">
<li>✅ Done: {{done}}</li>
<li>⚠️ Outdated: {{outdated}}</li>
<li>❌ Missing: {{missing}}</li>
</ul>
</div>
{{/each}}
</div>
</section>
<section class="files">
<h2>File Status</h2>
{{#each languages}}
<details>
<summary>{{code}} ({{done}}/{{total}})</summary>
<table>
<thead>
<tr>
<th>File</th>
<th>Status</th>
<th>Last Modified</th>
</tr>
</thead>
<tbody>
{{#each files}}
<tr>
<td>{{path}}</td>
<td>{{statusIcon status}} {{status}}</td>
<td>{{formatDate lastModified}}</td>
</tr>
{{/each}}
</tbody>
</table>
</details>
{{/each}}
</section>
</main>
<footer>
<p>Powered by <a href="https://lunaria.libdoc.top/">LunariaJS</a></p>
</footer>
</body>
</html>
API 设计分析
公开 API
LunariaJS 导出以下公开 API:
// src/index.ts
export { loadConfig } from './config';
export { calculateAllStatus, calculateProgress } from './status';
export { generateDashboard } from './dashboard/generator';
export { getFileLastModified, getFileHistory } from './git';
export { parseFile, getParser } from './parsers';
// 类型导出
export type { LunariaConfig, FilePattern, DashboardConfig } from './types/config';
export type { TranslationStatus, FileStatus } from './types/status';
export type { LocalizedContent, FileParser } from './parsers/types';
使用示例
import {
loadConfig,
calculateAllStatus,
generateDashboard,
calculateProgress,
} from '@lunariajs/core';
async function main() {
// 1. 加载配置
const config = await loadConfig('lunaria.config.json');
// 2. 计算翻译状态
const status = await calculateAllStatus(config);
// 3. 生成仪表板
await generateDashboard(status, config.dashboard?.outputDir || 'lunaria-dashboard');
// 4. 输出进度报告
for (const lang of Object.keys(status.languages)) {
const progress = calculateProgress(status, lang);
console.log(`${lang}: ${progress}% complete`);
}
}
main().catch(console.error);
源码阅读指南
入口文件分析
从 src/index.ts 开始,这是包的入口点:
- 导入依赖:加载配置、状态计算、仪表板生成等模块
- 导出 API:公开供外部使用的函数和类型
- 版本信息:导出当前版本号
核心模块导航
推荐阅读顺序:
src/types/- 理解数据结构src/config.ts- 配置加载逻辑src/git.ts- Git 操作封装src/status.ts- 状态计算核心src/dashboard/- 仪表板生成
调试技巧
// 启用调试日志
process.env.LUNARIA_DEBUG = 'true';
// 使用 verbose 模式
npx lunaria build --verbose
扩展开发基础
自定义解析器
// 自定义 CSV 解析器
import { FileParser, LocalizedContent } from '@lunariajs/core/parsers/types';
export const CsvParser: FileParser = {
extensions: ['.csv'],
async parse(content: string, path: string): Promise<LocalizedContent> {
const lines = content.split('\n');
const entries: Record<string, string> = {};
for (const line of lines.slice(1)) { // 跳过表头
const [key, value] = line.split(',');
if (key && value) {
entries[key.trim()] = value.trim();
}
}
return {
path,
lang: extractLangFromPath(path),
entries,
raw: content,
};
},
stringify(data: Record<string, string>): string {
const lines = ['key,value'];
for (const [key, value] of Object.entries(data)) {
lines.push(`${key},${value}`);
}
return lines.join('\n');
},
};
自定义状态计算策略
// 自定义状态计算逻辑
export async function customStatusCalculator(
sourcePath: string,
translationPath: string,
options: { threshold: number }
): Promise<FileStatus> {
// 使用自定义逻辑判断状态
const sourceContent = await fs.readFile(sourcePath, 'utf-8');
const translationContent = await fs.readFile(translationPath, 'utf-8');
// 比较内容相似度
const similarity = calculateSimilarity(sourceContent, translationContent);
if (similarity < options.threshold) {
return { status: 'outdated', similarity };
}
return { status: 'done', similarity };
}
贡献代码指南
- Fork 仓库:
https://github.com/withastro/lunaria - 创建分支:
git checkout -b feature/your-feature - 编写代码:遵循 TypeScript 和 ESLint 规范
- 运行测试:
npm test - 提交 PR:描述清楚变更内容
总结
本章深入剖析了 @lunariajs/core 的核心架构:
| 模块 | 核心功能 | 关键技术 |
|---|---|---|
| 配置系统 | 加载、验证、合并配置 | Zod Schema 验证 |
| 文件解析器 | 解析 JSON/YAML/Markdown | 统一接口设计 |
| 状态追踪 | 计算翻译状态 | Git 时间戳对比 |
| Git 客户端 | 封装 Git 操作 | simple-git |
| 仪表板生成 | 生成静态 HTML | Handlebars 模板 |
关键要点:
- 使用 TypeScript 提供类型安全
- 模块化设计,职责清晰
- Zod 进行配置验证
- Git 历史追踪翻译状态
- 模板引擎生成仪表板
下一步
下一篇文章,我们将探讨 LunariaJS 的高级配置与自定义策略,学习如何:
- 处理企业级场景的复杂配置
- 实现多仓库本地化管理
- 设计自定义本地化策略
- 优化大型项目的性能
敬请期待!
💡 推荐阅读: