Giter Club home page Giter Club logo

xliff-conv's Introduction

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

xliff-conv's People

Contributors

hrimhari avatar t2ym avatar vanvo1605 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

xliff-conv's Issues

Illegal invocation on loggers

Illegal invocation on loggers

this must be console for console.log(), console.warn(), and console.error() functions.

Handy migration from xliff2bundlejson

As xliff2bundlejson is unaware of todo items in JSON and state attributes in XLIFF, todo items are likely to be removed manually or by the finalize option in gulp-i18n-leverage.

For xliff-conv, handy migration from such xliff2bundlejson configurations has to be provided.

The finalize option should be enabled only for production builds (without feedback task) and todo items in JSON files have to be kept intact through development cycles.

Support for custom templates

Our translation workflow involves the use of XLIFF-supported tags not present in xliff-conv such as <note/>. We also don't care much about <header/> and usually prefer version 1.2 to 1.0.

All those differences can be addressed by supporting custom templates as parameters to parseJSON().

I'm preparing a pull request that adds this support.

Thank you,
Felipe

Successive 2 builds are required to reflect auto-translated-state annotations to JSON and XLIFF

Successive 2 build cycles are required to reflect auto-translated-state annotations to JSON and XLIFF.

Since pattern matching expressions as of 0.0.11 are applied through XLIFF import, JSON todo items for newly added strings are removed, that is, regarded as translated, only after 2 successive build cycles.

Required 2 Successive Build Cycles:

  1. HTML with new strings -> JSON with 'add' todo.op (through leverage) -> export XLIFF with 'new' state -> feedback to src XLIFF
  2. Import XLIFF with 'new' state -> JSON without todo (expression matching) -> XLIFF with 'translated' state for annotations

This behavior is by design for now. Better solution is under investigation.

Customize logger functions

Customize logger functions.

XliffConv(options):
options.logger - information logger; defaults to console.log
options.warnLogger - warning logger; defaults to console.warn
options.errorLogger - error logger; defaults to console.error

Add <tool> tag

Add <tool> tag with tool-id, tool-name, tool-version attributes.

[Enhancement] Support empty value "" to remove attribute

Support the special empty value "" to remove attribute

var xliffOptions =
{ xliffStates: {
  'add'    : [ 'new' ],
  'replace': [ 'needs-translation', 'needs-adaptation', 'needs-l10n' ],
  'approve' : [ '""', 'needs-review-translation', 'needs-review-adaptation', 'needs-review-l10n', 
        '[!approved&&state:=""&&approved:=no]' ],
  'default': [ '', 'translated', 'signed-off', 'final', '[approved]' ]
} };

Support ES Module

Support ES Module

Import

import XliffConv from 'xliff-conv/esm/xliff-conv.js'; // .js extension
import XliffConv from 'xliff-conv/xliff-conv.mjs'; // .mjs extension

Files

path type
xliff-conv/xliff-conv.js UMD (unchanged)
xliff-conv/xliff-conv.mjs ES Module
xliff-conv/esm/xliff-conv.js ES Module
xliff-conv/src/xliff-conv.js factory function source
xliff-conv/build.js build script

Build

# generate esm/xliff-conv.js, xliff-conv.mjs, xliff-conv.js from src/xliff-conv.js
npm run build # (= node build.js)

Don't force 'state' attribute if no state

Greetings,

In our translation system, we store XLIFF files in git and we don't use the 'state' attribute.

To avoid marking the entire file as changed after merging with a JSON, we'd like to be able to skip the 'state' attribute that is currently being forced.

I'm preparing a pull request with this change.

Thank you,
Felipe

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.