Skip to content

XLIFF to/from JSON converter for Polymer i18n-behavior

License

Notifications You must be signed in to change notification settings

t2ym/xliff-conv

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status Coverage Status npm Bower

xliff-conv

XLIFF to/from JSON converter for Polymer i18n-behavior

Features

  • Update bundle.*.json values with those from XLIFF
  • Generate XLIFF from bundles
  • Map todo operations in bundles onto XLIFF states
  • Update todo operations in bundles with XLIFF states
  • Concise and flexible expressions to customize conversion
  • Handy migration from xliff2bundlejson
  • UMD support

Install

For Node.js

    npm install --save-dev xliff-conv

Quick Tour with polymer-starter-kit-i18n

For Browsers

	bower install --save xliff-conv

Import

On Node.js

	var XliffConv = require('xliff-conv');

On Browsers

	<script src="path/to/bower_components/xliff-conv/xliff-conv.js"></script>

Examples

Import XLIFF task on gulp

Note: This task has to be processed before Leverage task with unbundle to pick up outputs of this task.

Input:

  • Next XLIFF files in source
  • Current bundle JSON files in source (as output templates)

Output:

  • Overwritten bundle JSON files in source
    var gulp = require('gulp');
    var JSONstringify = require('json-stringify-safe');
    var stripBom = require('strip-bom');
    var through = require('through2');
    var XliffConv = require('xliff-conv');

    // Import bundles.{lang}.xlf
    gulp.task('import-xliff', function () {
      var xliffPath = path.join('app', 'xliff');
      var xliffConv = new XliffConv();
      return gulp.src([
          'app/**/xliff/bundle.*.xlf'
        ])
        .pipe(through.obj(function (file, enc, callback) {
          var bundle, bundlePath;
          var base = path.basename(file.path, '.xlf').match(/^(.*)[.]([^.]*)$/);
          var xliff = String(file.contents);
          if (base) {
            try {
              bundlePath = path.join(file.base, 'locales', 'bundle.' + base[2] + '.json');
              bundle = JSON.parse(stripBom(fs.readFileSync(bundlePath, 'utf8')));
              xliffConv.parseXliff(xliff, { bundle: bundle }, function (output) {
                file.contents = new Buffer(JSONstringify(output, null, 2));
                file.path = bundlePath;
                callback(null, file);
              });
            }
            catch (ex) {
              callback(null, file);
            }
          }
          else {
            callback(null, file);
          }
        }))
        .pipe(gulp.dest('app'))
        .pipe($.size({
          title: 'import-xliff'
        }));
    });

Export XLIFF task on gulp

Note: If the todo items in JSON files are removed, the corresponding trans-units are treated as approved="yes" and state="translated".

Input:

  • Next bundles object in gulpfile.js

Output:

  • bundle.{lang}.xlf XLIFF in DEST_DIR/xliff
    var gulp = require('gulp');
    var through = require('through2');
    var XliffConv = require('xliff-conv');

    var bundles; // bundles object generated by preprocess and leverage tasks

    // Generate bundles.{lang}.xlf
    gulp.task('export-xliff', function (callback) {
      var DEST_DIR = 'dist';
      var srcLanguage = 'en';
      var xliffPath = path.join(DEST_DIR, 'xliff');
      var xliffConv = new XliffConv();
      var promises = [];
      try {
        fs.mkdirSync(xliffPath);
      }
      catch (e) {
      }
      for (var lang in bundles) {
        if (lang) {
          (function (destLanguage) {
            promises.push(new Promise(function (resolve, reject) {
              xliffConv.parseJSON(bundles, {
                srcLanguage: srcLanguage,
                destLanguage: destLanguage
              }, function (output) {
                fs.writeFile(path.join(xliffPath, 'bundle.' + destLanguage + '.xlf'), output, resolve);
              });
            }));
          })(lang);
        }
      }
      Promise.all(promises).then(function (outputs) {
        callback();
      });
    });

API

Constructor

var xliffConv = new XliffConv(options)

options object

  • date: Date, default: new Date() - date attribute value for XLIFF
  • xliffStates: Object, default: XliffConv.xliffStates.default - todo.op to XLIFF state mapping table
  • patterns: Object, default: XliffConv.patterns - A set of named regular expressions for pattern matching
  • logger: Function, default: console.log - information logger
  • warnLogger: Function, default: console.warn - warning logger
  • errorLogger: Function, default: console.error - error logger

XliffConv.xliffStates object - predefined mapping tables for options.xliffStates

  XliffConv.xliffStates = {
    // All state-less unapproved strings are regarded as needs-translation
    'default': {
      'add'    : [ 'new' ],
      'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n', '' ],
      'review' : [ 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n' ],
      'default': [ 'translated', 'signed-off', 'final', '[approved]' ]
    },
    // Aannotations {{name}} and tags <tag-name> are regarded as translated
    'annotationsAsTranslated': {
      'add'    : [ 'new' ],
      'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n', '' ],
      'review' : [ 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n' ],
      'default': [ 'translated', 'signed-off', 'final', '[approved]', '[source~=annotationsAndTags]' ]
    },
    // Newly added annotations {{name}} and tags <tag-name> are regarded as translated
    'newAnnotationsAsTranslated': {
      'add'    : [ 'new' ],
      'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n', '' ],
      'review' : [ 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n' ],
      'default': [ 'translated', 'signed-off', 'final', '[approved]', '[state==new&&source~=annotationsAndTags]' ]
    },
    // Newly added annotations {{name}} and tags <tag-name> are regarded as translated only at export
    'newAnnotationsAsTranslatedAtExport': {
      'add'    : [ 'new' ],
      'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n', '' ],
      'review' : [ 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n' ],
      'default': [ 'translated', 'signed-off', 'final', '[approved]', '[export&&state==new&&source~=annotationsAndTags]' ]
    },
    // Annotations {{name}} and tags <tag-name> are skipped in translation by translate=no
    'annotationsAsNoTranslate': {
      'add'    : [ 'new' ],
      'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n', '' ],
      'review' : [ 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n' ],
      'default': [ 'translated', 'signed-off', 'final', '[source~=annotationsAndTags&&translate:=no&&state:=final]', '[approved]' ],
    },
    /* === State Mapping Tables for migration from xliff2bundlejson === */
    // All state-less strings are regarded as approved=yes
    'approveAll': {
      'add'    : [ 'new' ],
      'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n' ],
      'review' : [ 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n' ],
      'default': [ 'translated', 'signed-off', 'final', '' ]
    },
    // State-less translated strings need review
    'reviewTranslated': {
      'add'    : [ 'new' ],
      'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n', '[!state&&!approved&&source==target]', '' ],
      'review' : [ 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n', '[!state&&!approved&&source!=target]' ],
      'default': [ 'translated', 'signed-off', 'final', '[approved]' ]
    },
    // State-less translated strings are regarded as approved=yes
    'approveTranslated': {
      'add'    : [ 'new' ],
      'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n', '[!state&&!approved&&source==target]', '' ],
      'review' : [ 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n' ],
      'default': [ 'translated', 'signed-off', 'final', '[!state&&!approved&&source!=target]', '[approved]' ]
    }
    /*
      Expression format:
        [condition1&&condition2&&...&&effect1&&effect2&&...]
          - expression is true when all the conditions are true
          - optional effects are processed if the expression is true

      Operators for conditions:
        parameter
          - true if parameter is non-null
        !parameter
          - true if parameter is undefined, null, or ''
        parameter1==parameter2
          - true if parameter1 is equal to parameter2
        parameter1!=parameter2
          - true if parameter1 is not equal to parameter2
        parameter~=pattern
          - true if parameter matches the regular expression options.patterns.pattern
          - if options.patterns.pattern is undefined, pattern is treated as the matching string
        tag.attribute~=pattern
          - true if attribute value of tag matched the regular expression options.patterns.pattern
          - if options.patterns.pattern is undefined, pattern is treated as the matching string

      Operators for effects:
        tag.attribute:=value
          - assign attribute of tag with the string value
        attribute:=value
          - assign predefined alias attribute with the string value
        tag:=value
          - assign textContent of tag with the string value

      Predefined parameters: Undefined parameters are treated as strings for matching
        state
          - state attribute of target
        id
          - id attribute of trans-unit
        component
          - component name in id
        restype
          - restype attribute of trans-unit. 'x-json-string' for strings
        source
          - text content of source tag
        target
          - text content of target tag
        approved
          - true if approved attribute of trans-unit is 'yes'
        import
          - true on XLIFF import (parseXliff); false on XLIFF export (parseJSON)
        export
          - true on XLIFF export (parseJSON); false on XLIFF import (parseXliff)

      Predefined tags:
        file
          - file tag
        trans-unit
          - trans-unit tag
        source
          - source tag
        target
          - target tag

      Predefined alias attributes:
        translate
          - alias for trans-unit.translate
        approved
          - alias for trans-unit.approved
        state
          - alias for target.state
     */
  };

XliffConv.patterns object - predefined named regular expressions for options.patterns

  XliffConv.patterns = {
    'annotationsAndTags': /^({{[^{} ]*}}|\[\[[^\[\] ]*\]\]|<[-a-zA-Z]{1,}>)$/,
    'annotations': /^({{[^{} ]*}}|\[\[[^\[\] ]*\]\])$/,
    'numbers': /^[0-9.]{1,}$/,
    'tags': /^<[-a-zA-Z]{1,}>$/
  };

xliffConv.parseXliff(xliff, options, callback) method

  • xliff: String, XLIFF as a string
  • options: Object, options.bundle as target bundle JSON object
  • callback: Function, callback(output) with output JSON object

xliffConv.parseJSON(bundles, options, callback) method

  • bundles: Object, bundles object
  • options.srcLanguage: String, default: 'en' - <file source-language> attribute
  • options.destLanguage: String, default: 'fr' - <file target-language> attribute
  • options.xmlSpace: String, default: 'default' - <file xml:space> attribute
  • options.dataType: String, default: 'plaintext' - <file datatype> attribute
  • options.original: String, default: 'messages' - <file original> attribute
  • options.productName: String, default: 'messages' - <file product-name> attribute
  • options.xmlHeader: String, default:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd">
  • options.xliffTemplate: String, default:
<xliff version="1.0">
  <file xml:space="[options.xmlSpace]"
      source-language="[options.srcLanguage]"
      target-language="[options.destLanguage]"
      datatype="[options.dataType]"
      original="[options.original]"
      date="[this.date.toISOString().replace(/[.][0-9]*Z$/, 'Z')]"
      product-name="[options.productName]">
    <header>
      <tool tool-id="xliff-conv" tool-name="xliff-conv" tool-version="[toolVersion]"/>
    </header>
    <body>
    </body>
  </file>
</xliff>
  • options.transUnitTemplate: String, default:
      <trans-unit>
        <source></source>
        <target></target>
      </trans-unit>
  • options.addNewAttr: Object, default: undefined
    • Customize id and add a new attribute to <trans-unit> with the original id value
    • labelArrayWithUniqueId is an Object mapping a new attribute value for each id
      xliffConv.parseJSON(bundles, {
        srcLanguage: srcLanguage,
        destLanguage: destLanguage,
        addNewAttr: {
          newAttrName: labelMapWithUniqueId
        }
      }, function (output) {
        fs.writeFile(path.join(xliffPath, 'bundle.' + destLanguage + '.xlf'), output, resolve);
      });
      // example labelMapWithUniqueId Object
      labelMapWithUniqueId =
        {
          // id: attribute value
          "Factory_audit_address": "ckv7ymf07ahqog4lur12bwobg1z3dsxzkqkdwxan",
          "alert_info_when_update_config_preferences": "ybsqyempsolypcf4poq1wdxxl8c04oam03ei27bc",
          "application_title": "rj7rtcdbefchcbrq9itw6sewjifd2v3c5dn99969",
          "back": "48gtruuew3ndd7pnj26lttt0kbgnlv2iyhtti99v",
          "barcode_section": "i2d0t2y11b5zlrlhbn5it8qkbxbp7ub0bdgxy7tr",
          "cancel_title": "bbzgu18z7wl6thj0eh9p83nlcrz4znyfox4khjuq",
          "client_initial_2_letter": "ilttwryn5jccb4wnhfu3nq9z72ds21m2ho7fnsgs"
        }
      <!-- example trans-unit -->
      <!-- without options.addNewAttr -->
      <trans-unit id="Factory_audit_address" approved="yes">
        <source>Address</source>
        <target state="translated">Adresse</target>
      </trans-unit>
      <!-- with options.addNewAttr = { resname: labelMapWithUniqueId } above -->
      <trans-unit id="ckv7ymf07ahqog4lur12bwobg1z3dsxzkqkdwxan" resname="Factory_audit_address" approved="yes">
        <source>Address</source>
        <target state="translated">Adresse</target>
      </trans-unit>
  • callback: Function, callback(output) with output XLIFF as a string

Notes:

  • With options.xliffTemplate, all the attribute values within the template are NOT replaced. It is the caller's responsibility to set appropriate values to the attributes.
  • With options.transUnitTemplate, XliffConv does NOT recognize <note> tags in the template in importing XLIFF and discards the contents.

Custom XLIFF restype attributes

restype JSON type Note
x-json-string string Omitted
x-json-boolean boolean "true" or "false" in value
x-json-number number
x-json-object object Unused for now
x-json-undefined undefined Empty string in value

Default Mapping of todo.op and XLIFF states

JSON -> XLIFF

todo.op XLIFF state
add new
replace needs-translation
review needs-review-translation
N/A translated

XLIFF -> JSON

XLIFF state approved todo.op Note
new no or N/A add
needs-translation no or N/A replace
needs-adaptation no or N/A replace
needs-l10n no or N/A replace
N/A no or N/A replace
needs-review-translation no or N/A review
needs-review-adaptation no or N/A review
needs-review-l10n no or N/A review
translated yes N/A Remove todo
signed-off yes N/A Remove todo
final yes N/A Remove todo
N/A yes N/A Remove todo

License

BSD-2-Clause

About

XLIFF to/from JSON converter for Polymer i18n-behavior

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •