Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
zakkudo committed Feb 26, 2019
1 parent 03c6777 commit 56f0248
Show file tree
Hide file tree
Showing 22 changed files with 1,411 additions and 223 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
"deep-equal": "^1.0.1",
"fs-extra": "^7.0.1",
"glob": "^7.1.2",
"json5": "^2.1.0",
"safe-eval": "^0.4.1"
"jju": "^1.4.0",
"json5": "^2.1.0"
},
"scripts": {
"build": "scripts/build.sh",
Expand Down
61 changes: 61 additions & 0 deletions src/LocaleDirectory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const fs = require('fs');
const path = require('path');
const PO = require('./formats/PO');
const JSON = require('./formats/JSON');
const JSON5 = require('./formats/JSON5');
const CSV = require('./formats/CSV');
const UnsupportedFormatError = require('./errors/UnsupportedFormatError');

const formatHandler = new Map([
['po', PO],
['json', JSON],
['json5', JSON5],
['csv', CSV],
]);

class LocaleDirectory {
constructor(directoryPath, defaultFormat) {
this.directoryPath = directoryPath;
}

buildFilename(locale, format) {
const basename = [locale, format].filter(p => p).join('.');

return path.resolve(this.directoryPath, `${basename}`);
}

writeLocalization(locale, localization, format) {
const filename = this.buildFilename(locale, format);
if (!handler) {
throw new UnsupportedFormatError(format);
}
const handler = formatHandler.get(format);
const data = handler.stringify(localization);

fs.writeSync(filename, data);
}

ensureDirectory() {
fs.ensureDirSync(this.templateDirectory);
}

readLocalization(locale, format) {
const filename = this.buildFilename(locale, format);
const handler = formatHandler.get(format);
if (!handler) {
throw new UnsupportedFormatError(format);
}
const text = String(fs.readSync(filename));

return handler.parse(text);
}

normalizeTo(format) {
formatHandler.entries().forEach(([extension, handler]) => {
if (extension !== format) {
}
});
}
}

module.exports = LocaleDirectory;
7 changes: 7 additions & 0 deletions src/errors/UnsupportedFormatError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class UnsupportedFormatError extends Error {
constructor(format) {
super(`"${format}" is not a supported format`);
}
}

module.exports = UnsupportedFormatError;
19 changes: 19 additions & 0 deletions src/formats/CSV.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string
*/

class CSV {
static parse() {
}

static stringify() {
}
}

module.exports = CSV;
21 changes: 21 additions & 0 deletions src/formats/JSON.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string
*/

class _JSON {
static parse(text) {
return JSON.parse(text);
}

static stringify(data) {
return JSON.stringify(data);
}
}

module.exports = _JSON;
160 changes: 160 additions & 0 deletions src/formats/JSON5.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string
*/

const path = require('path');
const JSON5 = require('json5');
const jju = require('jju');

function isWhitespace(token) {
return token.type === 'whitespace' || token.type === 'newline';
}

function removeCommentMarkers(text) {
if (text.startsWith('//')) {
return text.slice(2);
} else if (text.startsWith('/*')) {
return text.slice(2, -2);
}

return text
}

function parseComments(text) {
const tokens = jju.tokenize(text).filter((t) => !isWhitespace(t));
let buffer = [];
const comments = {};

tokens.forEach((t) => {
if (t.type === 'comment') {
buffer.push(removeCommentMarkers(t.raw));
} else if (t.type === 'key') {
if (buffer.length) {
const leaf = t.stack.reduce((node, key) => {
const subNode = node [key] = node[key] || {};
return subNode;
}, comments);

leaf[t.value] = buffer;
buffer = [];
}
} else {
buffer = [];
}
});

return comments;
}

/*
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string
*/

/**
* @private
*/
function serializeLocalizationWithMetaData(localizationWithMetadata) {
const templateDirectory = path.resolve(this.templateDirectory, '..');
const translations = Object.values(localizationWithMetadata).map((t) => {
const usages = t.usages.map((u) => {
return `${path.relative(templateDirectory, u.filename)}:${u.lineNumber}`;
}).sort();

return Object.assign({}, t, {usages});
}).sort((a, b) => {
return a.key.localeCompare(b.key) || a.context.localeCompare(b.context);
});
const indent = ' ';
const lines = ['{'];
let previousKey = translations[0].key;

/*
* EXAMPLE
* {
* "English": {
* // filename:number
* "default": "French"
* }
* }
*
*/
return translations.reduce((lines, t, i) => {
const {key, context, data, usages, note} = t;
const newLines = [];

if (i === 0) {
newLines.push(`${indent}"${key}": {`);
}

if (previousKey !== key) {
newLines.push(`${indent}},`);
newLines.push(`${indent}"${key}": {`);
} else if (i > 0) {
const length = lines.length;
const last = length - 1;

lines[last] = lines[last] + ',';
}

if (note) {
newLines.push(`${indent}${indent}// ${note.toUpperCase()}`);
}

usages.forEach((u) => {
newLines.push(`${indent}${indent}// ${u}`);
});

newLines.push(`${indent}${indent}"${context}": ${JSON.stringify(data)}`);

previousKey = key;

return lines.concat(newLines);
}, lines).concat([`${indent}}`, '}']).join('\n');
}


class _JSON5 {
static parse(text) {
const comments = parseComments(text);
console.log(JSON.stringify(comments, null, 4));
const localizations = JSON5.parse(text);

Object.entries(localizations).forEach(([key, contexts]) => {
Object.entries(contexts).forEach(([context, value]) => {
if (comments[key] && comments[key][context]) {
comments[key][context].forEach((c) => {
const leaf = localizations[key][context];

if (c.startsWith(' ')) {
leaf.notes = leaf.notes || '';
leaf.notes += c.slice(1);
} else if (c.startsWith('. ')) {
leaf.comments = leaf.comments || '';
leaf.comments += c.slice(2);
}
});
}
});
});

return localizations;
}

static stringify(data) {
return serializeLocalizationWithMetaData.call(this, localization);
}
}

module.exports = _JSON5;
13 changes: 13 additions & 0 deletions src/formats/JSON5.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const JSON5 = require('./JSON5');

describe('formats/JSON5', () => {
it('test', () => {
const tokens = JSON5.parse(`{
"About": {
// Notes
//, src/Application/index.js:40
"default": "について",
}
}`);
});
});

0 comments on commit 56f0248

Please sign in to comment.