diff --git a/convert.ts b/convert.ts new file mode 100644 index 0000000..26f028e --- /dev/null +++ b/convert.ts @@ -0,0 +1,125 @@ +import { I18NEntry, SingleI18NEntry, PluralI18NEntry } from 'i18n-proto'; + +export type Metadata = { + copyrightSubject: string, + bugsEmail: string, + year: number +}; + +export function makeDate(date: Date) { + const timezoneShift = date.getTimezoneOffset() / -60; + let tz = 'Z'; + if (timezoneShift !== 0) { + tz = (timezoneShift > 0 ? '+' : '-') + + (timezoneShift > 9 ? '' : '0') + + timezoneShift + '00'; + } + + return date.getFullYear() + '-' + + (date.getMonth() > 9 ? '' : '0') + date.getMonth() + '-' + + (date.getDay() > 9 ? '' : '0') + date.getDay() + ' ' + + (date.getHours() > 9 ? '' : '0') + date.getHours() + ':' + + (date.getMinutes() > 9 ? '' : '0') + date.getMinutes() + + tz; +} + +export function convert(json: string, meta: Metadata): string { + const document: I18NEntry[] = JSON.parse(json); + let poEntries: PotEntry[] = []; + + // 1) Make POT header + let poHeader = + `# Translations template for PROJECT. +# Copyright (C) ${meta.year} ${meta.copyrightSubject} +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , ${meta.year}. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\\n" +"Report-Msgid-Bugs-To: ${meta.bugsEmail}\\n" +"POT-Creation-Date: ${makeDate(new Date())}\\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" +"Last-Translator: FULL NAME \\n" +"Language-Team: LANGUAGE \\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=utf-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Generated-By: i18n-json2po\\n" + +`; + + // 2) Make POT entries + for (let item of document) { + let potEntry = new PotEntry(); + if (item.type === 'single') { + potEntry.parseSingleEntry(item); + } + if (item.type === 'plural') { + potEntry.parsePluralEntry(item); + } + poEntries.push(potEntry); + } + + return poHeader + poEntries + .map((entry) => entry.asString()) + .join("\n\n"); +} + +export class PotEntry { + private items: string[] = []; + + protected addComment = (comment: string) => this.items.push('#. ' + comment); + protected addOccurence = (occ: string) => this.items.push('#: ' + occ); + protected addContext = (context: string) => this.items.push('msgctxt ' + JSON.stringify(context)); + protected addMsgid = (id: string) => this.items.push('msgid ' + JSON.stringify(id)); + 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 + '] ""'); + } + }; + + public asString = () => this.items.join("\n"); + + public parseSingleEntry({ entry, comments, occurences, context, type }: SingleI18NEntry) { + if (comments) { + comments.forEach(this.addComment); + } + + if (occurences) { + occurences.forEach(this.addOccurence); + } + + if (context) { + this.addContext(context); + } + + if (type === 'single') { + this.addMsgid(entry); + this.addMsgstr(); + } + } + + public parsePluralEntry({ entry, comments, occurences, context, type }: PluralI18NEntry) { + if (comments) { + comments.forEach(this.addComment); + } + + if (occurences) { + occurences.forEach(this.addOccurence); + } + + if (context) { + this.addContext(context); + } + + if (type === 'plural') { + this.addMsgid(entry[0]); + this.addMsgidPlural(entry[entry.length - 1]); + this.addMsgstrPlural(entry.length); + } + } +} diff --git a/index.ts b/index.ts index e69de29..2c2703e 100644 --- a/index.ts +++ b/index.ts @@ -0,0 +1,76 @@ +import * as cli from 'cli'; +import { readFile, writeFile } from 'fs'; +import { convert } from './convert'; + +const options = cli.parse({ + src: ['s', 'A source JSON file to process', 'string', '__stdin'], + output: ['o', 'Output JSON file', 'string', '__stdout'], + copyrightSubject: ['c', 'Copyright for generated POT file', 'string', ''], + bugsEmail: ['b', 'Email for bugs', 'string', ''], + year: ['y', 'Copyright year', 'number', (new Date()).getFullYear()], + help: ['h', 'Show some help', 'bool', false] +}); + +if (options.help) { + console.log(`i18n JSON -> POT converter + +Options: + -h / --help Show this help + -s / --src FILE Define input JSON file name. Defaults + to stdin. + -o / --output FILE Define output POT file name. If a file + already exists, it's contents will be + overwritten. Defaults to stdout. + + -c / --copyrightSubject Team name or author name. + -b / --bugsEmail Email for sending bugs + -y / --year Copyright year, defaults to current year. +`); + process.exit(0); +} + +console.warn('Running conversion for file: ', options.src); + +const meta = { + copyrightSubject: options.copyrightSubject, + bugsEmail: options.bugsEmail, + year: options.year, +}; + +if (options.src === '__stdin') { + cli.withStdin((data) => { + try { + makeOutput(convert(data, meta), options.output); + } catch (e) { + console.error(e); + process.exit(1); + } + }); +} else { + readFile(options.src, { encoding: 'utf-8' }, (err, data) => { + if (err) { + console.error(err); + process.exit(1); + } + try { + makeOutput(convert(data, meta), options.output); + } catch (e) { + console.error(e); + process.exit(1); + } + }); +} + +function makeOutput(data: string, output: string) { + if (output === '__stdout') { + console.log(data); + } else { + writeFile(output, data, (e) => { + if (e) { + console.error(e); + process.exit(1); + } + process.exit(0); // success + }); + } +} diff --git a/package.json b/package.json index c0f7644..8c1cfa5 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@types/cli": "^0.11.19", "cli": "^1.0.1", + "i18n-proto": "^1.0.1", "ts-node": "^3.3.0", "typescript": "^2.4.2" } diff --git a/yarn.lock b/yarn.lock index 54c0535..2af68a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -93,6 +93,10 @@ has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +i18n-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/i18n-proto/-/i18n-proto-1.0.1.tgz#0a564b8cc893eee1f46800931561df0b917ccff0" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"