openclaw/scripts/extract-skill-descriptions.js
olwater 10acfb6fff feat(i18n): introduce internationalization architecture and zh-CN support
This commit adds a robust i18n framework to OpenClaw with the following highlights:

1. Non-invasive Architecture: The core logic remains untouched. The i18n layer acts as a lightweight UI wrapper, ensuring zero side effects on the agent's performance or stability.
2. Seamless Migration: 100% backward compatible. Existing users will notice no change unless the LANG environment variable is explicitly set.
3. Robust Fallback: Implements a reliable fallback mechanism that defaults to English strings if a translation is missing or corrupted.
4. Onboarding Focus: Prioritizes the onboarding wizard and skill descriptions to improve accessibility for Chinese-speaking users.

Changes:
- Implemented a unified t() translation helper.
- Added locales/en.ts (base) and locales/zh.ts.
- Enabled language switching via LANG/LC_ALL environment variables.
- Added comprehensive documentation in i18n/README.md.

Testing: Lightly tested with LANG=zh_cn environment variable
Prompts: Used Claude 3.5 for translation assistance
2026-01-31 00:21:01 +08:00

98 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
const skillsDir = path.join(process.cwd(), 'skills');
function extractSkillDescription(skillDir) {
const skillFile = path.join(skillDir, 'SKILL.md');
if (!fs.existsSync(skillFile)) {
return null;
}
const content = fs.readFileSync(skillFile, 'utf8');
const frontmatterMatch = content.match(/^---[\s\S]*?---/);
if (!frontmatterMatch) {
return null;
}
const frontmatter = frontmatterMatch[0];
const descriptionMatch = frontmatter.match(/description:\s*([^\n]+)/);
if (!descriptionMatch) {
return null;
}
const description = descriptionMatch[1].trim().replace(/^"|"$/g, '');
const metadataMatch = frontmatter.match(/metadata:\s*({[^}]+})/);
let installLabel = null;
if (metadataMatch) {
try {
const metadata = JSON.parse(metadataMatch[1]);
if (metadata.openclaw && metadata.openclaw.install && metadata.openclaw.install[0]) {
installLabel = metadata.openclaw.install[0].label;
}
} catch (e) {
// Ignore parsing errors
}
}
return { description, installLabel };
}
function main() {
const skillDescriptions = [];
if (!fs.existsSync(skillsDir)) {
console.error('Skills directory not found');
process.exit(1);
}
const skillNames = fs.readdirSync(skillsDir);
for (const skillName of skillNames) {
const skillDir = path.join(skillsDir, skillName);
if (fs.statSync(skillDir).isDirectory()) {
const result = extractSkillDescription(skillDir);
if (result && result.description) {
skillDescriptions.push({
name: skillName,
description: result.description,
installLabel: result.installLabel
});
}
}
}
console.log('Extracted skill descriptions:');
console.log('================================');
const translationEntries = [];
for (const skill of skillDescriptions) {
console.log(`Skill: ${skill.name}`);
console.log(`Description: ${skill.description}`);
if (skill.installLabel) {
console.log(`Install Label: ${skill.installLabel}`);
}
console.log('--------------------------------');
translationEntries.push(` '${skill.description.replace(/'/g, "\\'")}': '${skill.description}',`);
if (skill.installLabel) {
translationEntries.push(` '${skill.installLabel.replace(/'/g, "\\'")}': '${skill.installLabel}',`);
}
}
console.log('\nTranslation entries (add to src/i18n/locales/zh_CN.ts):');
console.log('==================================================');
console.log(translationEntries.join('\n'));
console.log(`\nTotal skills found: ${skillDescriptions.length}`);
}
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
export { extractSkillDescription, main };