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
98 lines
2.8 KiB
JavaScript
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 }; |