-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9c8a079
commit 786ffdc
Showing
8 changed files
with
463 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
'use strict'; | ||
|
||
var fs = require('fs'); | ||
var util = require('util'); | ||
var path = require('path'); | ||
|
||
let shim; | ||
class Y18N { | ||
constructor(opts) { | ||
// configurable options. | ||
opts = opts || {}; | ||
this.directory = opts.directory || './locales'; | ||
this.updateFiles = typeof opts.updateFiles === 'boolean' ? opts.updateFiles : true; | ||
this.locale = opts.locale || 'en'; | ||
this.fallbackToLanguage = typeof opts.fallbackToLanguage === 'boolean' ? opts.fallbackToLanguage : true; | ||
// internal stuff. | ||
this.cache = {}; | ||
this.writeQueue = []; | ||
} | ||
__(...args) { | ||
if (typeof arguments[0] !== 'string') { | ||
return this._taggedLiteral(arguments[0], ...arguments); | ||
} | ||
const str = args.shift(); | ||
let cb = function () { }; // start with noop. | ||
if (typeof args[args.length - 1] === 'function') | ||
cb = args.pop(); | ||
cb = cb || function () { }; // noop. | ||
if (!this.cache[this.locale]) | ||
this._readLocaleFile(); | ||
// we've observed a new string, update the language file. | ||
if (!this.cache[this.locale][str] && this.updateFiles) { | ||
this.cache[this.locale][str] = str; | ||
// include the current directory and locale, | ||
// since these values could change before the | ||
// write is performed. | ||
this._enqueueWrite({ | ||
directory: this.directory, | ||
locale: this.locale, | ||
cb | ||
}); | ||
} | ||
else { | ||
cb(); | ||
} | ||
return shim.format.apply(shim.format, [this.cache[this.locale][str] || str].concat(args)); | ||
} | ||
__n() { | ||
const args = Array.prototype.slice.call(arguments); | ||
const singular = args.shift(); | ||
const plural = args.shift(); | ||
const quantity = args.shift(); | ||
let cb = function () { }; // start with noop. | ||
if (typeof args[args.length - 1] === 'function') | ||
cb = args.pop(); | ||
if (!this.cache[this.locale]) | ||
this._readLocaleFile(); | ||
let str = quantity === 1 ? singular : plural; | ||
if (this.cache[this.locale][singular]) { | ||
const entry = this.cache[this.locale][singular]; | ||
str = entry[quantity === 1 ? 'one' : 'other']; | ||
} | ||
// we've observed a new string, update the language file. | ||
if (!this.cache[this.locale][singular] && this.updateFiles) { | ||
this.cache[this.locale][singular] = { | ||
one: singular, | ||
other: plural | ||
}; | ||
// include the current directory and locale, | ||
// since these values could change before the | ||
// write is performed. | ||
this._enqueueWrite({ | ||
directory: this.directory, | ||
locale: this.locale, | ||
cb | ||
}); | ||
} | ||
else { | ||
cb(); | ||
} | ||
// if a %d placeholder is provided, add quantity | ||
// to the arguments expanded by util.format. | ||
var values = [str]; | ||
if (~str.indexOf('%d')) | ||
values.push(quantity); | ||
return shim.format.apply(shim.format, values.concat(args)); | ||
} | ||
setLocale(locale) { | ||
this.locale = locale; | ||
} | ||
getLocale() { | ||
return this.locale; | ||
} | ||
updateLocale(obj) { | ||
if (!this.cache[this.locale]) | ||
this._readLocaleFile(); | ||
for (const key in obj) { | ||
if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
this.cache[this.locale][key] = obj[key]; | ||
} | ||
} | ||
} | ||
_taggedLiteral(parts, ...args) { | ||
let str = ''; | ||
parts.forEach(function (part, i) { | ||
var arg = args[i + 1]; | ||
str += part; | ||
if (typeof arg !== 'undefined') { | ||
str += '%s'; | ||
} | ||
}); | ||
return this.__.apply(this, [str].concat([].slice.call(args, 1))); | ||
} | ||
_enqueueWrite(work) { | ||
this.writeQueue.push(work); | ||
if (this.writeQueue.length === 1) | ||
this._processWriteQueue(); | ||
} | ||
_processWriteQueue() { | ||
var _this = this; | ||
var work = this.writeQueue[0]; | ||
// destructure the enqueued work. | ||
var directory = work.directory; | ||
var locale = work.locale; | ||
var cb = work.cb; | ||
var languageFile = this._resolveLocaleFile(directory, locale); | ||
var serializedLocale = JSON.stringify(this.cache[locale], null, 2); | ||
shim.fs.writeFile(languageFile, serializedLocale, 'utf-8', function (err) { | ||
_this.writeQueue.shift(); | ||
if (_this.writeQueue.length > 0) | ||
_this._processWriteQueue(); | ||
cb(err); | ||
}); | ||
} | ||
_readLocaleFile() { | ||
var localeLookup = {}; | ||
var languageFile = this._resolveLocaleFile(this.directory, this.locale); | ||
try { | ||
localeLookup = JSON.parse(shim.fs.readFileSync(languageFile, 'utf-8')); | ||
} | ||
catch (err) { | ||
if (err instanceof SyntaxError) { | ||
err.message = 'syntax error in ' + languageFile; | ||
} | ||
if (err.code === 'ENOENT') | ||
localeLookup = {}; | ||
else | ||
throw err; | ||
} | ||
this.cache[this.locale] = localeLookup; | ||
} | ||
_resolveLocaleFile(directory, locale) { | ||
var file = shim.resolve(directory, './', locale + '.json'); | ||
if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) { | ||
// attempt fallback to language only | ||
var languageFile = shim.resolve(directory, './', locale.split('_')[0] + '.json'); | ||
if (this._fileExistsSync(languageFile)) | ||
file = languageFile; | ||
} | ||
return file; | ||
} | ||
_fileExistsSync(file) { | ||
return shim.exists(file); | ||
} | ||
} | ||
function y18n(opts, _shim) { | ||
shim = _shim; | ||
const y18n = new Y18N(opts); | ||
return { | ||
__: y18n.__.bind(y18n), | ||
__n: y18n.__n.bind(y18n), | ||
setLocale: y18n.setLocale.bind(y18n), | ||
getLocale: y18n.getLocale.bind(y18n), | ||
updateLocale: y18n.updateLocale.bind(y18n), | ||
locale: y18n.locale | ||
}; | ||
} | ||
|
||
var nodePlatformShim = { | ||
fs: { | ||
readFileSync: fs.readFileSync, | ||
writeFile: fs.writeFile | ||
}, | ||
format: util.format, | ||
resolve: path.resolve, | ||
exists: (file) => { | ||
try { | ||
return fs.statSync(file).isFile(); | ||
} | ||
catch (err) { | ||
return false; | ||
} | ||
} | ||
}; | ||
|
||
const y18n$1 = (opts) => { | ||
return y18n(opts, nodePlatformShim); | ||
}; | ||
|
||
module.exports = y18n$1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
interface Y18NOpts { | ||
directory?: string; | ||
updateFiles?: boolean; | ||
locale?: string; | ||
fallbackToLanguage?: boolean; | ||
} | ||
interface Locale { | ||
[key: string]: string; | ||
} | ||
declare const y18n$0: (opts: Y18NOpts) => { | ||
__: (...args: (string | Function)[]) => string; | ||
__n: () => any; | ||
setLocale: (locale: string) => void; | ||
getLocale: () => string; | ||
updateLocale: (obj: Locale) => void; | ||
locale: string; | ||
}; | ||
export = y18n$0; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Y18NOpts } from './index.js'; | ||
declare const y18n: (opts: Y18NOpts) => { | ||
__: (...args: (string | Function)[]) => string; | ||
__n: () => any; | ||
setLocale: (locale: string) => void; | ||
getLocale: () => string; | ||
updateLocale: (obj: import("./index.js").Locale) => void; | ||
locale: string; | ||
}; | ||
export default y18n; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { y18n as _y18n } from './index.js'; | ||
import nodePlatformShim from './platform-shims/node.js'; | ||
const y18n = (opts) => { | ||
return _y18n(opts, nodePlatformShim); | ||
}; | ||
export default y18n; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
export interface Y18NOpts { | ||
directory?: string; | ||
updateFiles?: boolean; | ||
locale?: string; | ||
fallbackToLanguage?: boolean; | ||
} | ||
export interface Locale { | ||
[key: string]: string; | ||
} | ||
export interface PlatformShim { | ||
fs: { | ||
readFileSync: Function; | ||
writeFile: Function; | ||
}; | ||
exists: Function; | ||
format: Function; | ||
resolve: Function; | ||
} | ||
export declare function y18n(opts: Y18NOpts, _shim: PlatformShim): { | ||
__: (...args: (string | Function)[]) => string; | ||
__n: () => any; | ||
setLocale: (locale: string) => void; | ||
getLocale: () => string; | ||
updateLocale: (obj: Locale) => void; | ||
locale: string; | ||
}; |
Oops, something went wrong.