Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

i18n-json-po

Package Overview
Dependencies
Maintainers
8
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

i18n-json-po - npm Package Compare versions

Comparing version 1.0.6 to 1.0.7

129

convert.ts

@@ -1,7 +0,13 @@

import { I18NEntry, SingleI18NEntry, PluralI18NEntry } from 'i18n-proto';
import {
I18NEntry,
SingleI18NEntry,
PluralI18NEntry,
TranslationJson,
TranslationMeta
} from 'i18n-proto';
export type Metadata = {
copyrightSubject: string,
bugsEmail: string,
year: number
export type InitialMeta = {
copyrightSubject?: string,
bugsEmail?: string,
year?: number
};

@@ -30,7 +36,14 @@

export function makePoHeader(meta: Metadata, genDate: string): string {
return `# Translations template for PROJECT.
# Copyright (C) ${meta.year} ${meta.copyrightSubject}
export function makePoHeader({ meta, initialMeta, genDate, hasPluralForms }: {
meta?: TranslationMeta,
initialMeta: InitialMeta,
genDate: string,
hasPluralForms: boolean
}): string {
if (!meta) {
// make POT, use initial meta
return `# Translations template for PROJECT.
# Copyright (C) ${initialMeta.year} ${initialMeta.copyrightSubject}
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, ${meta.year}.
# FIRST AUTHOR <EMAIL@ADDRESS>, ${initialMeta.year}.
#

@@ -41,3 +54,3 @@ #, fuzzy

"Project-Id-Version: PROJECT VERSION\\n"
"Report-Msgid-Bugs-To: ${meta.bugsEmail}\\n"
"Report-Msgid-Bugs-To: ${initialMeta.bugsEmail}\\n"
"POT-Creation-Date: ${genDate}\\n"

@@ -53,15 +66,51 @@ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"

`;
} else {
// have meta - make po!
let headers = {
projectIdVersion: (v) => `Project-Id-Version: ${v}\n`,
reportMsgidBugsTo: (v) => `Report-Msgid-Bugs-To: ${v}\n`,
potCreationDate: (v) => `POT-Creation-Date: ${v}\n`,
poRevisionDate: (v) => `PO-Revision-Date: ${v}\n`,
lastTranslator: (v) => `Last-Translator: ${v.name} <${v.email}>\n`,
languageTeam: (v) => `Language-Team: ${v}\n`,
mimeVersion: (v) => `MIME-Version: ${v}\n`,
contentType: (v) => `Content-Type: ${v}\n`,
contentTransferEncoding: (v) => `Content-Transfer-Encoding: ${v}\n`,
generatedBy: (v) => `Generated-By: ${v}\n`,
language: (v) => `Language: ${v}\n`,
pluralForms: (v) => `Plural-Forms: ${v}\n`,
};
let items = [
'msgid ""',
'msgstr ""',
];
let pluralFormsHeaderFound = false;
for (let name in meta) {
if (name === 'pluralForms') {
pluralFormsHeaderFound = true;
}
items.push(JSON.stringify(headers[name](meta[name])));
}
if (hasPluralForms && !pluralFormsHeaderFound) {
throw new Error('Translation has some plural forms, but Plural-Forms header was not found');
}
return items.join("\n") + "\n\n"; // additional CRLFs to separated header
}
}
export function convert(json: string, meta: Metadata, printOccurences: boolean): string {
const document: I18NEntry[] = JSON.parse(json);
export function convert(json: string, initialMeta: InitialMeta | undefined, printOccurences: boolean): string {
const document: TranslationJson = JSON.parse(json);
let poEntries: PotEntry[] = [];
let hasPluralForms = false;
for (let item of document) {
for (let item of document.items) {
let potEntry = new PotEntry();
if (item.type === 'single') {
potEntry.parseSingleEntry(item, printOccurences);
potEntry.parseSingleEntry(item, printOccurences, !!document.meta);
}
if (item.type === 'plural') {
potEntry.parsePluralEntry(item, printOccurences);
potEntry.parsePluralEntry(item, printOccurences, !!document.meta);
hasPluralForms = true;
}

@@ -71,5 +120,8 @@ poEntries.push(potEntry);

return makePoHeader(meta, makeDate(new Date())) + poEntries
.map((entry) => entry.asString())
.join("\n\n");
return makePoHeader({
meta: document.meta,
initialMeta,
genDate: makeDate(new Date()),
hasPluralForms
}) + poEntries.map((entry) => entry.asString()).join("\n\n");
}

@@ -85,6 +137,9 @@

protected addMsgidPlural = (id: string) => this.items.push('msgid_plural ' + JSON.stringify(id));
protected addMsgstr = () => this.items.push('msgstr ""');
protected addMsgstrPlural = (count: number) => {
for (let i = 0; i < count; i++) {
this.items.push('msgstr[' + i + '] ""');
protected addMsgstr = (translation: string = '') => this.items.push('msgstr ' + JSON.stringify(translation));
protected addMsgstrPlural = (translations: string[]) => {
if (!translations.length) { // 2 empty translations by default
this.items.push('msgstr[0] ""');
this.items.push('msgstr[1] ""');
} else {
translations.forEach((val, index) => this.items.push('msgstr[' + index + '] ' + JSON.stringify(val)));
}

@@ -95,3 +150,7 @@ };

public parseSingleEntry({ entry, comments, occurences, context, type }: SingleI18NEntry, printOccurences: boolean): PotEntry {
public parseSingleEntry(
{ entry, comments, occurences, context, type, translation }: SingleI18NEntry,
printOccurences: boolean,
includeTranslations: boolean
): PotEntry {
this.items = [];

@@ -110,6 +169,4 @@ if (comments) {

if (type === 'single') {
this.addMsgid(entry);
this.addMsgstr();
}
this.addMsgid(entry);
this.addMsgstr(includeTranslations ? translation : '');

@@ -119,3 +176,7 @@ return this;

public parsePluralEntry({ entry, comments, occurences, context, type }: PluralI18NEntry, printOccurences: boolean): PotEntry {
public parsePluralEntry(
{ entry, comments, occurences, context, type, translations }: PluralI18NEntry,
printOccurences: boolean,
includeTranslations: boolean
): PotEntry {
this.items = [];

@@ -134,9 +195,7 @@ if (comments) {

if (type === 'plural') {
this.addMsgid(entry[0]);
// extracted original entries contain only first and
// last plurals forms, which identify the entry
this.addMsgidPlural(entry[1]);
this.addMsgstrPlural(entry.length);
}
this.addMsgid(entry[0]);
// extracted original entries contain only first and
// last plurals forms, which identify the entry
this.addMsgidPlural(entry[1]);
this.addMsgstrPlural(includeTranslations ? translations : []);

@@ -143,0 +202,0 @@ return this;

@@ -23,23 +23,64 @@ "use strict";

exports.makeDate = makeDate;
function makePoHeader(meta, genDate) {
return "# Translations template for PROJECT.\n# Copyright (C) " + meta.year + " " + meta.copyrightSubject + "\n# This file is distributed under the same license as the PROJECT project.\n# FIRST AUTHOR <EMAIL@ADDRESS>, " + meta.year + ".\n# \n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PROJECT VERSION\\n\"\n\"Report-Msgid-Bugs-To: " + meta.bugsEmail + "\\n\"\n\"POT-Creation-Date: " + genDate + "\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Generated-By: i18n-json2po\\n\"\n\n";
function makePoHeader(_a) {
var meta = _a.meta, initialMeta = _a.initialMeta, genDate = _a.genDate, hasPluralForms = _a.hasPluralForms;
if (!meta) {
// make POT, use initial meta
return "# Translations template for PROJECT.\n# Copyright (C) " + initialMeta.year + " " + initialMeta.copyrightSubject + "\n# This file is distributed under the same license as the PROJECT project.\n# FIRST AUTHOR <EMAIL@ADDRESS>, " + initialMeta.year + ".\n# \n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PROJECT VERSION\\n\"\n\"Report-Msgid-Bugs-To: " + initialMeta.bugsEmail + "\\n\"\n\"POT-Creation-Date: " + genDate + "\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Generated-By: i18n-json2po\\n\"\n\n";
}
else {
// have meta - make po!
var headers = {
projectIdVersion: function (v) { return "Project-Id-Version: " + v + "\n"; },
reportMsgidBugsTo: function (v) { return "Report-Msgid-Bugs-To: " + v + "\n"; },
potCreationDate: function (v) { return "POT-Creation-Date: " + v + "\n"; },
poRevisionDate: function (v) { return "PO-Revision-Date: " + v + "\n"; },
lastTranslator: function (v) { return "Last-Translator: " + v.name + " <" + v.email + ">\n"; },
languageTeam: function (v) { return "Language-Team: " + v + "\n"; },
mimeVersion: function (v) { return "MIME-Version: " + v + "\n"; },
contentType: function (v) { return "Content-Type: " + v + "\n"; },
contentTransferEncoding: function (v) { return "Content-Transfer-Encoding: " + v + "\n"; },
generatedBy: function (v) { return "Generated-By: " + v + "\n"; },
language: function (v) { return "Language: " + v + "\n"; },
pluralForms: function (v) { return "Plural-Forms: " + v + "\n"; }
};
var items = [
'msgid ""',
'msgstr ""',
];
var pluralFormsHeaderFound = false;
for (var name_1 in meta) {
if (name_1 === 'pluralForms') {
pluralFormsHeaderFound = true;
}
items.push(JSON.stringify(headers[name_1](meta[name_1])));
}
if (hasPluralForms && !pluralFormsHeaderFound) {
throw new Error('Translation has some plural forms, but Plural-Forms header was not found');
}
return items.join("\n") + "\n\n"; // additional CRLFs to separated header
}
}
exports.makePoHeader = makePoHeader;
function convert(json, meta, printOccurences) {
function convert(json, initialMeta, printOccurences) {
var document = JSON.parse(json);
var poEntries = [];
for (var _i = 0, document_1 = document; _i < document_1.length; _i++) {
var item = document_1[_i];
var hasPluralForms = false;
for (var _i = 0, _a = document.items; _i < _a.length; _i++) {
var item = _a[_i];
var potEntry = new PotEntry();
if (item.type === 'single') {
potEntry.parseSingleEntry(item, printOccurences);
potEntry.parseSingleEntry(item, printOccurences, !!document.meta);
}
if (item.type === 'plural') {
potEntry.parsePluralEntry(item, printOccurences);
potEntry.parsePluralEntry(item, printOccurences, !!document.meta);
hasPluralForms = true;
}
poEntries.push(potEntry);
}
return makePoHeader(meta, makeDate(new Date())) + poEntries
.map(function (entry) { return entry.asString(); })
.join("\n\n");
return makePoHeader({
meta: document.meta,
initialMeta: initialMeta,
genDate: makeDate(new Date()),
hasPluralForms: hasPluralForms
}) + poEntries.map(function (entry) { return entry.asString(); }).join("\n\n");
}

@@ -55,12 +96,19 @@ exports.convert = convert;

this.addMsgidPlural = function (id) { return _this.items.push('msgid_plural ' + JSON.stringify(id)); };
this.addMsgstr = function () { return _this.items.push('msgstr ""'); };
this.addMsgstrPlural = function (count) {
for (var i = 0; i < count; i++) {
_this.items.push('msgstr[' + i + '] ""');
this.addMsgstr = function (translation) {
if (translation === void 0) { translation = ''; }
return _this.items.push('msgstr ' + JSON.stringify(translation));
};
this.addMsgstrPlural = function (translations) {
if (!translations.length) {
_this.items.push('msgstr[0] ""');
_this.items.push('msgstr[1] ""');
}
else {
translations.forEach(function (val, index) { return _this.items.push('msgstr[' + index + '] ' + JSON.stringify(val)); });
}
};
this.asString = function () { return _this.items.join("\n"); };
}
PotEntry.prototype.parseSingleEntry = function (_a, printOccurences) {
var entry = _a.entry, comments = _a.comments, occurences = _a.occurences, context = _a.context, type = _a.type;
PotEntry.prototype.parseSingleEntry = function (_a, printOccurences, includeTranslations) {
var entry = _a.entry, comments = _a.comments, occurences = _a.occurences, context = _a.context, type = _a.type, translation = _a.translation;
this.items = [];

@@ -76,10 +124,8 @@ if (comments) {

}
if (type === 'single') {
this.addMsgid(entry);
this.addMsgstr();
}
this.addMsgid(entry);
this.addMsgstr(includeTranslations ? translation : '');
return this;
};
PotEntry.prototype.parsePluralEntry = function (_a, printOccurences) {
var entry = _a.entry, comments = _a.comments, occurences = _a.occurences, context = _a.context, type = _a.type;
PotEntry.prototype.parsePluralEntry = function (_a, printOccurences, includeTranslations) {
var entry = _a.entry, comments = _a.comments, occurences = _a.occurences, context = _a.context, type = _a.type, translations = _a.translations;
this.items = [];

@@ -95,9 +141,7 @@ if (comments) {

}
if (type === 'plural') {
this.addMsgid(entry[0]);
// extracted original entries contain only first and
// last plurals forms, which identify the entry
this.addMsgidPlural(entry[1]);
this.addMsgstrPlural(entry.length);
}
this.addMsgid(entry[0]);
// extracted original entries contain only first and
// last plurals forms, which identify the entry
this.addMsgidPlural(entry[1]);
this.addMsgstrPlural(includeTranslations ? translations : []);
return this;

@@ -104,0 +148,0 @@ };

import * as cli from 'cli';
import { readFile, writeFile } from 'fs';
import { convert } from './convert';
import { convert, InitialMeta } from './convert';

@@ -37,3 +37,3 @@ const options = cli.parse({

const meta = {
const meta: InitialMeta = {
copyrightSubject: options.copyrightSubject,

@@ -40,0 +40,0 @@ bugsEmail: options.bugsEmail,

{
"name": "i18n-json-po",
"version": "1.0.6",
"version": "1.0.7",
"description": "i18n .json to .pot file converter",

@@ -28,3 +28,3 @@ "main": "dist/index.js",

"eslint": "3.13.1",
"i18n-proto": "1.0.4",
"i18n-proto": "1.0.5",
"karma": "1.7.0",

@@ -31,0 +31,0 @@ "karma-browserify": "^5.1.1",

@@ -21,3 +21,3 @@ # i18n-json-po

to stdin.
-o / --output FILE Define output POT file name. If a file
-o / --output FILE Define output PO[T] file name. If a file
already exists, it's contents will be

@@ -32,3 +32,3 @@ overwritten. Defaults to stdout.

```
Be default jsonpo accepts input JSON file from stdin, so it's possible to combine it with [i18n-stex](https://github.com/2gis/stex) nicely:
By default `jsonpo` accepts input JSON file from stdin, so it's possible to combine it with [i18n-stex](https://github.com/2gis/stex) nicely:
```

@@ -39,2 +39,6 @@ $ stex -s 'src/**/*.ts' | jsonpo -p > strings.pot

### PO and POT
By default, if optional `meta` field is not present in input file, `jsonpo` creates POT (template) file **without any translations**. If you need to add translations into file, you MUST pass `meta` field according to its format. This will result in fully translated PO file. Note that if your translations include plural forms, you MUST provide `pluralForms` member in `meta` field: this field should contain formula describing how to choose proper plural form. See `Plural-Forms` header in gettext's PO file description.
## API usage

@@ -41,0 +45,0 @@

import * as assert from 'assert';
import { PluralI18NEntry, SingleI18NEntry } from 'i18n-proto';
import { Metadata, PotEntry, makeDate, getTzOffset, makePoHeader } from '../convert';
import { PluralI18NEntry, SingleI18NEntry, TranslationMeta } from 'i18n-proto';
import { InitialMeta, PotEntry, makeDate, getTzOffset, makePoHeader } from '../convert';
const xor = require('array-xor');

@@ -16,3 +16,3 @@

it('Makes valid POT header', () => {
let m: Metadata = {
let m: InitialMeta = {
copyrightSubject: 'cool team',

@@ -44,3 +44,7 @@ bugsEmail: 'bugs@team.com',

xor(
makePoHeader(m, 'SOMEDATE').split("\n"),
makePoHeader({
initialMeta: m,
genDate: 'SOMEDATE',
hasPluralForms: false
}).split("\n"),
expected.split("\n")

@@ -52,2 +56,76 @@ ),

it('Makes valid PO header from existing meta', () => {
let input: TranslationMeta = {
projectIdVersion: '2gis-online',
reportMsgidBugsTo: 'online4@2gis.ru',
potCreationDate: '2017-07-14 11:29+0700',
poRevisionDate: '2017-06-30 15:30+0700',
lastTranslator: {
name: '2GIS',
email: 'crowdin@2gis.ru'
},
language: 'cs_CZ',
languageTeam: 'Czech',
pluralForms: 'nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2',
mimeVersion: '1.0',
contentType: 'text/plain; charset=utf-8',
contentTransferEncoding: '8bit',
generatedBy: 'Babel 2.1.1'
};
let expected = `msgid ""
msgstr ""
"Project-Id-Version: 2gis-online\\n"
"Report-Msgid-Bugs-To: online4@2gis.ru\\n"
"POT-Creation-Date: 2017-07-14 11:29+0700\\n"
"PO-Revision-Date: 2017-06-30 15:30+0700\\n"
"Last-Translator: 2GIS <crowdin@2gis.ru>\\n"
"Language: cs_CZ\\n"
"Language-Team: Czech\\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=utf-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Generated-By: Babel 2.1.1\\n"
`;
assert.deepEqual(xor(
makePoHeader({ meta: input, initialMeta: {}, genDate: 'SOMEDATE', hasPluralForms: true }).split("\n"),
expected.split("\n")
), []);
});
it('Properly handles absence of Plural-Forms header', () => {
let input = {
projectIdVersion: '2gis-online',
reportMsgidBugsTo: 'online4@2gis.ru',
potCreationDate: '2017-07-14 11:29+0700',
poRevisionDate: '2017-06-30 15:30+0700',
lastTranslator: {
name: '2GIS',
email: 'crowdin@2gis.ru'
},
language: 'cs_CZ',
languageTeam: 'Czech',
mimeVersion: '1.0',
contentType: 'text/plain; charset=utf-8',
contentTransferEncoding: '8bit',
generatedBy: 'Babel 2.1.1'
} as TranslationMeta; // Explicit casting to emulate bad JSON
let catched = [];
try {
makePoHeader({ meta: input, initialMeta: {}, genDate: 'SOMEDATE', hasPluralForms: true })
} catch (e) {
catched.push(e);
}
assert.equal(catched.length, 1);
assert.equal(catched[0].message, 'Translation has some plural forms, but Plural-Forms header was not found');
// Should not throw exceptions without hasPluralForms
assert.notEqual(makePoHeader({ meta: input, initialMeta: {}, genDate: 'SOMEDATE', hasPluralForms: false }), undefined);
});
it('Parses single i18n entry', () => {

@@ -78,3 +156,3 @@ let entry = new PotEntry();

let xor1 = xor(
entry.parseSingleEntry(i18nEntry, true).asString().split("\n"),
entry.parseSingleEntry(i18nEntry, true, true).asString().split("\n"),
expected.split("\n")

@@ -85,3 +163,3 @@ );

let xor2 = xor(
entry.parseSingleEntry(i18nEntry, false).asString().split("\n"),
entry.parseSingleEntry(i18nEntry, false, true).asString().split("\n"),
expectedWithoutOccurences.split("\n")

@@ -122,3 +200,3 @@ );

let xor1 = xor(
entry.parsePluralEntry(i18nEntry, true).asString().split("\n"),
entry.parsePluralEntry(i18nEntry, true, true).asString().split("\n"),
expected.split("\n")

@@ -129,3 +207,3 @@ );

let xor2 = xor(
entry.parsePluralEntry(i18nEntry, false).asString().split("\n"),
entry.parsePluralEntry(i18nEntry, false, true).asString().split("\n"),
expectedWithoutOccurences.split("\n")

@@ -132,0 +210,0 @@ );

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc