有没有一种方法可以自动创建用于语言翻译的 .json 文件?

Is there a way I can automate the creation of .json files used for language translations?

我有这样的文件,其中包含翻译键和值:

locale-en.json
{
    "CHANGE_PASSWORD": "Change Password",
    "CONFIRM_PASSWORD":  "Confirm Password",
    "NEW_PASSWORD": "New Password"
}

locale-jp.json
{
    "CHANGE_PASSWORD": "パスワードを変更します",
    "CONFIRM_PASSWORD":  "パスワードを認証します",
    "NEW_PASSWORD": "新しいパスワード"
}

例如,当我向包含英文翻译的 JSON 文件添加新的翻译密钥时,我必须记住将该密钥和相关翻译添加到所有其他 JSON 文件。所有 JSON 文件也分别编辑。这个过程很费力而且容易出错。

有没有人找到减少错误和自动化流程的方法。

理想情况下,我希望能够 运行 来自 Windows PowerShell 的脚本,如果将附加密钥添加到 locale-en.json,该脚本会将文件更改为此:

locale-en.json
{
    "CHANGE_PASSWORD": "Change Password",
    "CONFIRM_PASSWORD":  "Confirm Password",
    "NEW_PASSWORD": "New Password",
    "NEW_KEY": "New Key"
}

locale-jp.json
{
    "CHANGE_PASSWORD": "パスワードを変更します",
    "CONFIRM_PASSWORD":  "パスワードを認証します",
    "NEW_PASSWORD": "新しいパスワード",
    >>>"NEW_KEY": "New Key"
}

Is there a way I can automate the creation of .json files used for language translations?

YES,执行自动任务正是 Grunt and Gulp 等自动化工具的设计目的。

如您所说,手动操作既费力又容易出错,所以 Grunt/Gulp 是正确的选择。

使用简单的Grunt/Gulp配置,可以同时观看所有相关的.json文件:添加到其中任何一个的任何密钥都会立即检测到,并命令执行您选择的自定义脚本。


如何 GRUNT/GULP 可以做到:

  1. Grunt/Gulp 会不断地 watch 所有相关的 JSON 文件;
  2. 当在监视的文件中检测到更改时,自定义脚本是 运行;
  3. 自定义脚本将读取更改的文件并检索新的键和值;
  4. 然后自定义脚本将写入到所有其他相关JSON文件。

配置 GUNT

要自动检测文件更改并执行 myCustomScript,只需像这样使用 grunt-contrib-watch

watch: {
  scripts: {
    files: ['**/*.locale.json'],
    tasks: ['myCustomScript'],
  },
}

将新密钥添加到相关 .JSON 文件的自定义脚本:

  grunt.event.on('watch', function(action, filepath) {
    // filepath is the path to the file where change is detected
    grunt.config.set('filepath', grunt.config.escape(filepath));
   });

  var myCustomScript=function(changedFile,keyFile){

     var project = grunt.file.readJSON(changedFile);
     //will store the file where changes were detected as a json object

     var keys=grunt.file.readJSON(keyFile);
     //will store keyFile as a json object

     //walk changedFile keys, and check is keys are in keyFile
     for (var key in project) {
       if (project.hasOwnProperty(key)) {
         if(!keys.hasOwnProperty(key)){
           //a new key was detected
           newKeyArray.push(key);
         }
       }
     }

  //should update all the other relevant JSON files with `grunt.file.write`, and add all the keys in newKeyArray:

  var filesToChangeArray=grunt.file.match('**/*.locale.json');
  //returns an array that contains all filepaths where change is desired
  filesToChangeArray.forEach(function(path){
    //walk newKeyArray to set addedContent string
    newKeyArray.forEach(function(key){
    addedContent+='"'+key+'":"to be set",';
    //this will write all the new keys, with a value of "to be set", to the addedContent string
    }
    grunt.file.write(path,addedContent);
    });
  }

Ideally I would like to be able to run a script from Windows PowerShell

尽管 Grunt/Gulp 经常用于执行用 javaScript/nodejs 编写的自定义文件,但它们能够很好地命令执行用其他语言编写的脚本。

要执行 PowerShell 脚本,您可以使用名为 grunt-shell 的 G运行t 插件,如下所示:

grunt.initConfig({
shell: {
    ps: {
        options: {
            stdout: true
        },
        command: 'powershell myScript.ps1'
    }
}
});

作为detailed in this SO post.

因此,如果您喜欢 PowerShell,您可以两全其美:

  • 使用 Grunt/Gulp 手表轻松检测;
  • 检测到更改时执行 PowerShell 脚本。

However, you might as easily use Grunt/Gulp only for this: as Grunt/Gulp is already taking care of the detection in the background, all you need to do is have it run a custom script that reads your new keys (grunt.file.readJSON) and copies them (grunt.file.write) to the relevant files.

你可以在 powershell 中写这样的东西:

$masterFile = "locale-en.json"

function Get-LocaleMap($file){

    $map = @{}

    $localeJson = ConvertFrom-Json (gc $file -Raw)
    $localeJson | gm -MemberType NoteProperty | % {
        $map.Add($_.Name, ($localeJson | select -ExpandProperty $_.Name))
    }

    return $map
}

$masterLocale = Get-LocaleMap $masterFile

ls | ? { $_.Name -like "locale-*.json" -and $_.Name -ne $masterFile } | % {
    $locale = Get-LocaleMap $_.FullName
    $masterLocale.GetEnumerator() | % {
        if(!$locale.ContainsKey($_.Key)){
            $locale.Add($_.Key, $_.Value)
        }
    }

    ConvertTo-Json $locale | Out-File -FilePath $_.FullName -Force -Encoding utf8
}

它根据您的英语 json 文件创建了一个字典。然后它查找所有其他语言环境文件并检查它们是否存在英语文件中存在但从中丢失的键。然后它添加缺少的键和值并以 Unicode 格式保存语言环境文件。

让我向您展示如何使用老派 Windows 脚本,因为您似乎更喜欢 JavaScript:

var masterFile = "locale-en.json"
var fso = new ActiveXObject("Scripting.FileSystemObject");
var scriptPath = fso.GetParentFolderName(WScript.ScriptFullName);
var charSet = 'utf-8';
var f = fso.GetFolder(scriptPath);
var fc = new Enumerator(f.files);

function getLocaleMap(fileName){
    var path = scriptPath + '\' + fileName;
    var stream = new ActiveXObject("ADODB.Stream"); // you cannot use fso for utf-8

    try{
        stream.CharSet = charSet;
        stream.Open();
        stream.LoadFromFile(path);
        var text = stream.ReadText();
        var json = {};
        eval('json = ' + text); // JSON.parse is not available in all versions
        return json;
    }
    finally{
        stream.Close();
    }
}

function saveAsUtf8(fileName, text){
    var path = scriptPath + '\' + fileName;
    var stream = new ActiveXObject("ADODB.Stream"); 

    try{
        stream.CharSet = charSet;
        stream.Open();
        stream.Position = 0;
        stream.WriteText(text);
        stream.SaveToFile(path, 2); // overwrite
    }
    finally{
        stream.Close();
    }
}

var locales = [];
var masterMap = getLocaleMap(masterFile);

for (; !fc.atEnd(); fc.moveNext())
{
    var file = fc.item();
    var extension = file.Name.split('.').pop();
    if(extension != "json" || file.Name == masterFile){
       continue;
    }

    var map = getLocaleMap(file.Name);
    var newLocaleText = '{\r\n';
    var i = 0;

    for(var name in masterMap){
        var value = '';

        if(map[name]){
            value = map[name];
        }
        else{
            value = masterMap[name];
        }

        if(i > 0){
            newLocaleText += ",\r\n";
        }

        newLocaleText += "\t'" + name + "': '" + value + "'";
        i++;
    }

    newLocaleText += '\r\n}'

    saveAsUtf8(file.Name, newLocaleText);
}

您可以像这样从命令行 运行 javascript:

Cscript.exe "C:\yourscript.js"

希望对您有所帮助。

通过命令行使用带有 nodejs 的 javascript 解决方案使流程自动化。

$ node localeUpdater.js

这将监视您的默认语言环境 (locale-en.json) 以及所做的任何修改,并根据需要更新您的整个语言环境文件列表。

  1. 如果不存在则创建必要的语言环境文件列表,然后使用默认语言环境数据对其进行初始化
  2. 根据默认语言环境添加新密钥
  3. 根据默认区域设置删除丢失的键

localeUpdater.js

var fs = require("fs");

var localeFileDefault = "locale-en.json";
var localeFileList = ["locale-jp.json", "locale-ph.json"];

fs.watchFile(localeFileDefault, function() {

  var localeDefault = readFile(localeFileDefault);
  var localeCurrent = null;
  var fileNameCurrent = null;

  for (var i in localeFileList) {
    fileNameCurrent = localeFileList[i];

    console.log("Adding new keys from default locale to file " + fileNameCurrent);
    localeCurrent = readFile(fileNameCurrent);
    for (var key in localeDefault) {
      if (!localeCurrent[key]) {
        console.log(key + " key added.");
        localeCurrent[key] = localeDefault[key];
      }
    }

    console.log("Removing keys not on default locale to file " + fileNameCurrent);
    for (var key in localeCurrent) {
      if (!localeDefault[key]) {
        console.log(key + " key removed.");
        delete localeCurrent[key];
      }
    }

    writeFile(fileNameCurrent, JSON.stringify(localeCurrent));
    console.log("File " + fileNameCurrent + " updated.");
  }

});

function readFile(fileName) {
  var result = null;
  if (fs.existsSync(fileName)) {
    result = fs.readFileSync(fileName, "utf8");
    result = result ? JSON.parse(result) : {};
  } else {
    writeFile(fileName, "{}");
    result = {};
  }
  return result;
}

function writeFile(fileName, content) {
  fs.writeFileSync(fileName, content, "utf8");
}

您应该采取多种保护措施。

首先你的翻译功能应该有一些保障措施。类似于:

function gettext(text) {
    if (manifest[text]) {
        return text;
    }

    return text;
}

我不确定您是如何注册新字符串的,但是我们对 gettext('...') 之类的代码库进行了正则表达式,然后我们以这种方式编译了一个翻译列表。我们每天几次将其推送给第 3 方翻译公司,该公司会注意到新的字符串。他们填充新事物,我们拉回内容。 "pull" 涉及对不同语言文件的编译。翻译文件编译总是回落到英文。换句话说,我们从 3rd 方下载文件并执行类似的操作:

_.map(strings, function(string) {
    return localeManifest[locale][text] || localeManifest['en_US'][text];
}

这可确保即使区域设置的清单不包含翻译,我们仍会使用美国英语版本填充它。