xliff-conv

    1.0.12 • Public • Published

    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

    Install

    npm i xliff-conv

    DownloadsWeekly Downloads

    70

    Version

    1.0.12

    License

    BSD-2-Clause

    Unpacked Size

    247 kB

    Total Files

    17

    Last publish

    Collaborators

    • t2ym