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
This commit is contained in:
1 parent
09be5d45d5
commit
10acfb6fff
22 files changed
+1325
-266
No files matched your search
@@ -0,0 +1,98 @@
|
||||
#!/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 };
|
||||
Reference in new issue
Block a user