From 634d4596538911c555d41c7cb03ed24228260e61 Mon Sep 17 00:00:00 2001 From: bcoe Date: Tue, 4 Aug 2020 22:59:18 -0700 Subject: [PATCH 1/9] refactor!: typescript/ESM conversion --- .eslintrc | 21 +++ .gitignore | 1 + .nvmrc | 1 - index.js | 188 ---------------------- lib/index.ts | 226 +++++++++++++++++++++++++++ package.json | 41 ++++- rollup.config.js | 17 ++ scripts/replace-legacy-export.cjs | 11 ++ test/{y18n-test.js => y18n-test.cjs} | 10 +- tsconfig.json | 14 ++ tsconfig.test.json | 6 + 11 files changed, 334 insertions(+), 202 deletions(-) create mode 100644 .eslintrc delete mode 100644 .nvmrc delete mode 100644 index.js create mode 100644 lib/index.ts create mode 100644 rollup.config.js create mode 100644 scripts/replace-legacy-export.cjs rename test/{y18n-test.js => y18n-test.cjs} (98%) create mode 100644 tsconfig.json create mode 100644 tsconfig.test.json diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..5ce8c64 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,21 @@ +{ + "overrides": [ + { + "files": "*.ts", + "parser": "@typescript-eslint/parser", + "rules": { + "no-unused-vars": "off", + "no-useless-constructor": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-useless-constructor": "error" + } + } + ], + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint/eslint-plugin" + ] +} diff --git a/.gitignore b/.gitignore index 20bf8fc..12e513c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store node_modules .nyc_output +build coverage package-lock.json diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index b009dfb..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -lts/* diff --git a/index.js b/index.js deleted file mode 100644 index d720681..0000000 --- a/index.js +++ /dev/null @@ -1,188 +0,0 @@ -var fs = require('fs') -var path = require('path') -var util = require('util') - -function Y18N (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 = [] -} - -Y18N.prototype.__ = function () { - if (typeof arguments[0] !== 'string') { - return this._taggedLiteral.apply(this, arguments) - } - var args = Array.prototype.slice.call(arguments) - var str = args.shift() - var 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([this.directory, this.locale, cb]) - } else { - cb() - } - - return util.format.apply(util, [this.cache[this.locale][str] || str].concat(args)) -} - -Y18N.prototype._taggedLiteral = function (parts) { - var args = arguments - var str = '' - parts.forEach(function (part, i) { - var arg = args[i + 1] - str += part - if (typeof arg !== 'undefined') { - str += '%s' - } - }) - return this.__.apply(null, [str].concat([].slice.call(arguments, 1))) -} - -Y18N.prototype._enqueueWrite = function (work) { - this.writeQueue.push(work) - if (this.writeQueue.length === 1) this._processWriteQueue() -} - -Y18N.prototype._processWriteQueue = function () { - var _this = this - var work = this.writeQueue[0] - - // destructure the enqueued work. - var directory = work[0] - var locale = work[1] - var cb = work[2] - - var languageFile = this._resolveLocaleFile(directory, locale) - var serializedLocale = JSON.stringify(this.cache[locale], null, 2) - - fs.writeFile(languageFile, serializedLocale, 'utf-8', function (err) { - _this.writeQueue.shift() - if (_this.writeQueue.length > 0) _this._processWriteQueue() - cb(err) - }) -} - -Y18N.prototype._readLocaleFile = function () { - var localeLookup = {} - var languageFile = this._resolveLocaleFile(this.directory, this.locale) - - try { - localeLookup = JSON.parse(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 -} - -Y18N.prototype._resolveLocaleFile = function (directory, locale) { - var file = path.resolve(directory, './', locale + '.json') - if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) { - // attempt fallback to language only - var languageFile = path.resolve(directory, './', locale.split('_')[0] + '.json') - if (this._fileExistsSync(languageFile)) file = languageFile - } - return file -} - -// this only exists because fs.existsSync() "will be deprecated" -// see https://nodejs.org/api/fs.html#fs_fs_existssync_path -Y18N.prototype._fileExistsSync = function (file) { - try { - return fs.statSync(file).isFile() - } catch (err) { - return false - } -} - -Y18N.prototype.__n = function () { - var args = Array.prototype.slice.call(arguments) - var singular = args.shift() - var plural = args.shift() - var quantity = args.shift() - - var cb = function () {} // start with noop. - if (typeof args[args.length - 1] === 'function') cb = args.pop() - - if (!this.cache[this.locale]) this._readLocaleFile() - - var str = quantity === 1 ? singular : plural - if (this.cache[this.locale][singular]) { - str = this.cache[this.locale][singular][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([this.directory, 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 util.format.apply(util, values.concat(args)) -} - -Y18N.prototype.setLocale = function (locale) { - this.locale = locale -} - -Y18N.prototype.getLocale = function () { - return this.locale -} - -Y18N.prototype.updateLocale = function (obj) { - if (!this.cache[this.locale]) this._readLocaleFile() - - for (var key in obj) { - this.cache[this.locale][key] = obj[key] - } -} - -module.exports = function (opts) { - var y18n = new Y18N(opts) - - // bind all functions to y18n, so that - // they can be used in isolation. - for (var key in y18n) { - if (typeof y18n[key] === 'function') { - y18n[key] = y18n[key].bind(y18n) - } - } - - return y18n -} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..01c6af6 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,226 @@ +import { readFileSync, statSync, writeFile } from 'fs' +import { resolve } from 'path' +// TODO: implement minimal version of util.format. +const util = require('util') + +interface Y18NOpts { + directory?: string; + updateFiles?: boolean; + locale?: string; + fallbackToLanguage?: boolean; +} + +interface Work { + directory: string; + locale: string; + cb: Function +} + +interface Locale { + [key: string]: string +} + +interface CacheEntry { + [key: string]: string; +} + +class Y18N { + directory: string; + updateFiles: boolean; + locale: string; + fallbackToLanguage: boolean; + writeQueue: Work[]; + cache: {[key: string]: {[key: string]: CacheEntry|string}}; + + constructor (opts: Y18NOpts) { + // 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: (string|Function)[]): string { + // const args = Array.prototype.slice.call(arguments) + if (typeof arguments[0] !== 'string') { + return this._taggedLiteral(arguments[0] as string[], ...arguments) + } + const str: string = args.shift() as string + + let cb: Function = function () {} // start with noop. + if (typeof args[args.length - 1] === 'function') cb = (args.pop() as Function) + 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 util.format.apply(util, [this.cache[this.locale][str] || str].concat(args as string[])) + } + + __n () { + const args = Array.prototype.slice.call(arguments) + const singular: string = args.shift() + const plural: string = args.shift() + const quantity: number = 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] as CacheEntry + 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: (string|number)[] = [str] + if (~str.indexOf('%d')) values.push(quantity) + + return util.format.apply(util, values.concat(args)) + } + + setLocale (locale: string) { + this.locale = locale + } + + getLocale () { + return this.locale + } + + updateLocale (obj: Locale) { + 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: string[], ...args: string[]) { + let str = '' + parts.forEach(function (part, i) { + var arg = args[i + 1] + str += part + if (typeof arg !== 'undefined') { + str += '%s' + } + }) + return this.__.apply(null, [str].concat([].slice.call(arguments, 1))) + } + + _enqueueWrite (work: 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) + + 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(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: string, locale: string) { + var file = resolve(directory, './', locale + '.json') + if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) { + // attempt fallback to language only + var languageFile = resolve(directory, './', locale.split('_')[0] + '.json') + if (this._fileExistsSync(languageFile)) file = languageFile + } + return file + } + + _fileExistsSync (file: string) { + try { + return statSync(file).isFile() + } catch (err) { + return false + } + } +} + +function y18n (opts: Y18NOpts) { + 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) + } +} + +export default y18n diff --git a/package.json b/package.json index ad989b6..c6ae1a3 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,13 @@ "name": "y18n", "version": "4.0.0", "description": "the bare-bones internationalization library used by yargs", + "exports": { + "import": "./build/lib/index.js", + "require": "./build/index.cjs" + }, + "type": "module", + "module": "./build/lib/index.js", + "types": "./build/index.cjs.d.ts", "keywords": [ "i18n", "internationalization", @@ -18,23 +25,41 @@ "license": "ISC", "author": "Ben Coe ", "main": "index.js", - "files": [ - "index.js" - ], "scripts": { - "fix": "standard --fix", - "coverage": "c8 --check-coverage report", - "pretest": "standard", - "test": "c8 mocha" + "check": "standardx '**/*.ts'", + "fix": "standardx --fix '**/*.ts'", + "pretest": "rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs", + "test": "c8 --reporter=text --reporter=html mocha test/*.cjs", + "posttest": "npm run check", + "coverage": "c8 report --check-coverage", + "precompile": "rimraf build", + "compile": "tsc", + "postcompile": "npm run build:cjs", + "build:cjs": "rollup -c", + "postbuild:cjs": "node scripts/replace-legacy-export.cjs", + "prepare": "npm run compile" }, "devDependencies": { + "@wessberg/rollup-plugin-ts": "^1.3.1", "c8": "^7.3.0", "chai": "^4.0.1", + "cross-env": "^7.0.2", + "gts": "^2.0.2", "mocha": "^8.0.0", "rimraf": "^3.0.2", - "standard": "^14.3.4" + "standardx": "^5.0.0", + "typescript": "^3.9.7" }, + "files": [ + "browser.js", + "build" + ], "engines": { "node": ">=10" + }, + "standardx": { + "ignore": [ + "build" + ] } } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..935ebda --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,17 @@ +import ts from '@wessberg/rollup-plugin-ts' + +const output = { + format: 'cjs', + file: './build/index.cjs', + exports: 'default' +} + +if (process.env.NODE_ENV === 'test') output.sourcemap = true + +export default { + input: './lib/index.ts', + output, + plugins: [ + ts({ /* options */ }) + ] +} diff --git a/scripts/replace-legacy-export.cjs b/scripts/replace-legacy-export.cjs new file mode 100644 index 0000000..ad13f3c --- /dev/null +++ b/scripts/replace-legacy-export.cjs @@ -0,0 +1,11 @@ +#!/usr/bin/env node +'use strict' + +const { readFileSync, writeFileSync } = require('fs') + +// Cleanup the export statement in CJS typings file generated by rollup: +const legacyTypings = require('path').resolve(__dirname, '../build/index.cjs.d.ts') +const contents = readFileSync(legacyTypings, 'utf8').replace( + 'export { y18n as default };', 'export = y18n;' +) +writeFileSync(legacyTypings, contents, 'utf8') diff --git a/test/y18n-test.js b/test/y18n-test.cjs similarity index 98% rename from test/y18n-test.js rename to test/y18n-test.cjs index e96e8e6..ab4cef8 100644 --- a/test/y18n-test.js +++ b/test/y18n-test.cjs @@ -1,10 +1,10 @@ /* global describe, it, after, beforeEach */ -var expect = require('chai').expect -var fs = require('fs') -var rimraf = require('rimraf') -var y18n = require('../') -var path = require('path') +const expect = require('chai').expect +const fs = require('fs') +const rimraf = require('rimraf') +const y18n = require('../build/index.cjs') +const path = require('path') require('chai').should() diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9cf3eed --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "outDir": "build", + "rootDir": ".", + "sourceMap": false, + "target": "es2017", + "moduleResolution": "node", + "module": "es2015" + }, + "include": [ + "lib/**/*.ts" + ] +} \ No newline at end of file diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..5635d6c --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": true + } +} From dd6f197ebb8c0bc58393beadcdd8f6d089e7a2cb Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 4 Sep 2020 15:41:34 -0700 Subject: [PATCH 2/9] test: get tests passing --- lib/index.ts | 7 ++++--- test/y18n-test.cjs | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 01c6af6..c775e18 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -46,7 +46,6 @@ class Y18N { } __ (...args: (string|Function)[]): string { - // const args = Array.prototype.slice.call(arguments) if (typeof arguments[0] !== 'string') { return this._taggedLiteral(arguments[0] as string[], ...arguments) } @@ -148,7 +147,8 @@ class Y18N { str += '%s' } }) - return this.__.apply(null, [str].concat([].slice.call(arguments, 1))) + console.info([str].concat([].slice.call(args, 1))) + return this.__.apply(this, [str].concat([].slice.call(args, 1))) } _enqueueWrite (work: Work) { @@ -219,7 +219,8 @@ function y18n (opts: Y18NOpts) { __n: y18n.__n.bind(y18n), setLocale: y18n.setLocale.bind(y18n), getLocale: y18n.getLocale.bind(y18n), - updateLocale: y18n.updateLocale.bind(y18n) + updateLocale: y18n.updateLocale.bind(y18n), + locale: y18n.locale } } diff --git a/test/y18n-test.cjs b/test/y18n-test.cjs index ab4cef8..d65b14b 100644 --- a/test/y18n-test.cjs +++ b/test/y18n-test.cjs @@ -11,7 +11,7 @@ require('chai').should() describe('y18n', function () { describe('configure', function () { it('allows you to override the default y18n configuration', function () { - var y = y18n({ locale: 'fr' }) + const y = y18n({ locale: 'fr' }) y.locale.should.equal('fr') }) }) @@ -43,7 +43,6 @@ describe('y18n', function () { locale: 'pirate', directory: path.join(__dirname, 'locales') }).__ - __`Hi, ${'Ben'} ${''}!`.should.equal('Yarr! Shiver me timbers, why \'tis Ben !') }) it('uses replacements from the default locale if none is configured', function () { From 678f660dfd97c65a26c0e3b6f937654dc18417b1 Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 4 Sep 2020 16:00:27 -0700 Subject: [PATCH 3/9] chore: split out platform specifics --- lib/cjs.ts | 8 ++++++++ lib/index.ts | 40 +++++++++++++++++++++----------------- lib/platform-shims/node.ts | 13 +++++++++++++ rollup.config.js | 2 +- 4 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 lib/cjs.ts create mode 100644 lib/platform-shims/node.ts diff --git a/lib/cjs.ts b/lib/cjs.ts new file mode 100644 index 0000000..1ba6a58 --- /dev/null +++ b/lib/cjs.ts @@ -0,0 +1,8 @@ +import { y18n as _y18n, Y18NOpts } from './index.js' +import nodePlatformShim from './platform-shims/node.js' + +const y18n = (opts: Y18NOpts) => { + return _y18n(opts, nodePlatformShim) +} + +export default y18n diff --git a/lib/index.ts b/lib/index.ts index c775e18..065037c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,9 +1,4 @@ -import { readFileSync, statSync, writeFile } from 'fs' -import { resolve } from 'path' -// TODO: implement minimal version of util.format. -const util = require('util') - -interface Y18NOpts { +export interface Y18NOpts { directory?: string; updateFiles?: boolean; locale?: string; @@ -16,7 +11,7 @@ interface Work { cb: Function } -interface Locale { +export interface Locale { [key: string]: string } @@ -24,6 +19,17 @@ interface CacheEntry { [key: string]: string; } +export interface PlatformShim { + fs: { + readFileSync: Function, + statSync: Function, + writeFile: Function + }, + format: Function, + resolve: Function +} + +let shim: PlatformShim class Y18N { directory: string; updateFiles: boolean; @@ -73,7 +79,7 @@ class Y18N { cb() } - return util.format.apply(util, [this.cache[this.locale][str] || str].concat(args as string[])) + return shim.format.apply(shim.format, [this.cache[this.locale][str] || str].concat(args as string[])) } __n () { @@ -117,7 +123,7 @@ class Y18N { var values: (string|number)[] = [str] if (~str.indexOf('%d')) values.push(quantity) - return util.format.apply(util, values.concat(args)) + return shim.format.apply(shim.format, values.concat(args)) } setLocale (locale: string) { @@ -147,7 +153,6 @@ class Y18N { str += '%s' } }) - console.info([str].concat([].slice.call(args, 1))) return this.__.apply(this, [str].concat([].slice.call(args, 1))) } @@ -168,7 +173,7 @@ class Y18N { var languageFile = this._resolveLocaleFile(directory, locale) var serializedLocale = JSON.stringify(this.cache[locale], null, 2) - writeFile(languageFile, serializedLocale, 'utf-8', function (err) { + shim.fs.writeFile(languageFile, serializedLocale, 'utf-8', function (err: Error) { _this.writeQueue.shift() if (_this.writeQueue.length > 0) _this._processWriteQueue() cb(err) @@ -180,7 +185,7 @@ class Y18N { var languageFile = this._resolveLocaleFile(this.directory, this.locale) try { - localeLookup = JSON.parse(readFileSync(languageFile, 'utf-8')) + localeLookup = JSON.parse(shim.fs.readFileSync(languageFile, 'utf-8')) } catch (err) { if (err instanceof SyntaxError) { err.message = 'syntax error in ' + languageFile @@ -194,10 +199,10 @@ class Y18N { } _resolveLocaleFile (directory: string, locale: string) { - var file = resolve(directory, './', locale + '.json') + var file = shim.resolve(directory, './', locale + '.json') if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) { // attempt fallback to language only - var languageFile = resolve(directory, './', locale.split('_')[0] + '.json') + var languageFile = shim.resolve(directory, './', locale.split('_')[0] + '.json') if (this._fileExistsSync(languageFile)) file = languageFile } return file @@ -205,14 +210,15 @@ class Y18N { _fileExistsSync (file: string) { try { - return statSync(file).isFile() + return shim.fs.statSync(file).isFile() } catch (err) { return false } } } -function y18n (opts: Y18NOpts) { +export function y18n (opts: Y18NOpts, _shim: PlatformShim) { + shim = _shim const y18n = new Y18N(opts) return { __: y18n.__.bind(y18n), @@ -223,5 +229,3 @@ function y18n (opts: Y18NOpts) { locale: y18n.locale } } - -export default y18n diff --git a/lib/platform-shims/node.ts b/lib/platform-shims/node.ts new file mode 100644 index 0000000..ab5bb22 --- /dev/null +++ b/lib/platform-shims/node.ts @@ -0,0 +1,13 @@ +import { readFileSync, statSync, writeFile } from 'fs' +import { format } from 'util' +import { resolve } from 'path' + +export default { + fs: { + readFileSync, + statSync, + writeFile + }, + format, + resolve +} diff --git a/rollup.config.js b/rollup.config.js index 935ebda..ec8a2ac 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -9,7 +9,7 @@ const output = { if (process.env.NODE_ENV === 'test') output.sourcemap = true export default { - input: './lib/index.ts', + input: './lib/cjs.ts', output, plugins: [ ts({ /* options */ }) From 4d99c0d7502d9d33e4330300d502edb95f395728 Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 4 Sep 2020 16:16:05 -0700 Subject: [PATCH 4/9] test: smoke tests for ESM --- .github/workflows/ci.yaml | 9 +++++++++ index.mjs | 8 ++++++++ package.json | 13 +++++++------ rollup.config.js | 7 ++++++- scripts/replace-legacy-export.cjs | 11 ----------- test/esm/y18n-test.mjs | 18 ++++++++++++++++++ 6 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 index.mjs delete mode 100644 scripts/replace-legacy-export.cjs create mode 100644 test/esm/y18n-test.mjs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 18714f3..2ce5fe8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,3 +39,12 @@ jobs: - run: npm install - run: npm test - run: npm run coverage + esm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: '14.x' + - run: npm install + - run: npm run test:esm diff --git a/index.mjs b/index.mjs new file mode 100644 index 0000000..46c8213 --- /dev/null +++ b/index.mjs @@ -0,0 +1,8 @@ +import shim from './build/lib/platform-shims/node.js' +import { y18n as _y18n } from './build/lib/index.js' + +const y18n = (opts) => { + return _y18n(opts, shim) +} + +export default y18n diff --git a/package.json b/package.json index c6ae1a3..a9ff69d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "4.0.0", "description": "the bare-bones internationalization library used by yargs", "exports": { - "import": "./build/lib/index.js", + "import": "./index.mjs", "require": "./build/index.cjs" }, "type": "module", @@ -26,17 +26,17 @@ "author": "Ben Coe ", "main": "index.js", "scripts": { - "check": "standardx '**/*.ts'", - "fix": "standardx --fix '**/*.ts'", + "check": "standardx '**/*.ts' '**/*.cjs' '**/*.mjs'", + "fix": "standardx --fix '**/*.ts' '**/*.cjs' '**/*.mjs'", "pretest": "rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs", "test": "c8 --reporter=text --reporter=html mocha test/*.cjs", + "test:esm": "c8 --reporter=text --reporter=html mocha test/esm/*.mjs", "posttest": "npm run check", "coverage": "c8 report --check-coverage", "precompile": "rimraf build", "compile": "tsc", "postcompile": "npm run build:cjs", "build:cjs": "rollup -c", - "postbuild:cjs": "node scripts/replace-legacy-export.cjs", "prepare": "npm run compile" }, "devDependencies": { @@ -48,11 +48,12 @@ "mocha": "^8.0.0", "rimraf": "^3.0.2", "standardx": "^5.0.0", + "ts-transform-default-export": "^1.0.2", "typescript": "^3.9.7" }, "files": [ - "browser.js", - "build" + "build", + "index.mjs" ], "engines": { "node": ">=10" diff --git a/rollup.config.js b/rollup.config.js index ec8a2ac..537ce1e 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,5 @@ import ts from '@wessberg/rollup-plugin-ts' +import transformDefaultExport from 'ts-transform-default-export' const output = { format: 'cjs', @@ -12,6 +13,10 @@ export default { input: './lib/cjs.ts', output, plugins: [ - ts({ /* options */ }) + ts({ + transformers: ({ program }) => ({ + afterDeclarations: transformDefaultExport(program) + }) + }), ] } diff --git a/scripts/replace-legacy-export.cjs b/scripts/replace-legacy-export.cjs deleted file mode 100644 index ad13f3c..0000000 --- a/scripts/replace-legacy-export.cjs +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node -'use strict' - -const { readFileSync, writeFileSync } = require('fs') - -// Cleanup the export statement in CJS typings file generated by rollup: -const legacyTypings = require('path').resolve(__dirname, '../build/index.cjs.d.ts') -const contents = readFileSync(legacyTypings, 'utf8').replace( - 'export { y18n as default };', 'export = y18n;' -) -writeFileSync(legacyTypings, contents, 'utf8') diff --git a/test/esm/y18n-test.mjs b/test/esm/y18n-test.mjs new file mode 100644 index 0000000..4a4a178 --- /dev/null +++ b/test/esm/y18n-test.mjs @@ -0,0 +1,18 @@ +/* global describe, it */ + +import * as assert from 'assert' +import y18n from '../../index.mjs' + +describe('y18n', function () { + it('__ smoke test', function () { + var __ = y18n({ + locale: 'pirate', + directory: './test/locales' + }).__ + + assert.strictEqual( + __`Hi, ${'Ben'} ${'Coe'}!`, + 'Yarr! Shiver me timbers, why \'tis Ben Coe!' + ) + }) +}) From a74fc136d43f287fe4ff2a527b1e4b0880750eef Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 4 Sep 2020 16:18:00 -0700 Subject: [PATCH 5/9] chore: add node types --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a9ff69d..af1bc08 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "prepare": "npm run compile" }, "devDependencies": { + "@types/node": "^14.6.4", "@wessberg/rollup-plugin-ts": "^1.3.1", "c8": "^7.3.0", "chai": "^4.0.1", From 35bd7f851111911aa705e9a8ba5a81874a6b65a1 Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 4 Sep 2020 16:20:03 -0700 Subject: [PATCH 6/9] deps: rollup was missing --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index af1bc08..285e186 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "gts": "^2.0.2", "mocha": "^8.0.0", "rimraf": "^3.0.2", + "rollup": "^2.26.10", "standardx": "^5.0.0", "ts-transform-default-export": "^1.0.2", "typescript": "^3.9.7" From 3bee35d495088eff9efd290d091e3888b67208c0 Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 4 Sep 2020 16:29:32 -0700 Subject: [PATCH 7/9] build: update publish configuration --- .github/workflows/release-please.yml | 32 +++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 38ace4d..2fdb821 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -7,8 +7,38 @@ jobs: release-please: runs-on: ubuntu-latest steps: - - uses: GoogleCloudPlatform/release-please-action@v1.6.3 + - uses: bcoe/release-please-action@v2.0.0 + id: release with: token: ${{ secrets.GITHUB_TOKEN }} release-type: node package-name: y18n + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - run: npm install + - run: npm run compile + - name: push Deno release + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git remote add gh-token "https://${{ secrets.GITHUB_TOKEN}}@github.com/yargs/y18n.git" + git checkout -b deno + git add -f build + git commit -a -m 'chore: ${{ steps.release.outputs.tag_name }} release' + git push origin +deno + git tag -a ${{ steps.release.outputs.tag_name }}-deno -m 'chore: ${{ steps.release.outputs.tag_name }} release' + git push origin ${{ steps.release.outputs.tag_name }}-deno + if: ${{ steps.release.outputs.release_created }} + - uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: 'https://external-dot-oss-automation.appspot.com/' + if: ${{ steps.release.outputs.release_created }} + - run: npm install + if: ${{ steps.release.outputs.release_created }} + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + if: ${{ steps.release.outputs.release_created }} From 252f9948124d3eff052f108eaf53429f33f36450 Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 4 Sep 2020 17:08:23 -0700 Subject: [PATCH 8/9] chore: getting deno configured --- .github/workflows/ci.yaml | 13 +++++++++++++ .gitignore | 1 + deno.ts | 9 +++++++++ lib/index.ts | 8 ++------ lib/platform-shims/deno.ts | 34 ++++++++++++++++++++++++++++++++++ lib/platform-shims/node.ts | 10 ++++++++-- test/deno/y18n-test.ts | 19 +++++++++++++++++++ test/esm/y18n-test.mjs | 2 +- tsconfig.json | 3 +++ tsconfig.test.json | 5 ++++- 10 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 deno.ts create mode 100644 lib/platform-shims/deno.ts create mode 100644 test/deno/y18n-test.ts diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2ce5fe8..a423c85 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,3 +48,16 @@ jobs: node-version: '14.x' - run: npm install - run: npm run test:esm + deno: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - run: npm install + - run: npm run compile + - uses: denolib/setup-deno@v2 + - run: | + deno --version + deno test --allow-read test/deno/y18n-test.ts diff --git a/.gitignore b/.gitignore index 12e513c..6430fa7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules build coverage package-lock.json +example.* diff --git a/deno.ts b/deno.ts new file mode 100644 index 0000000..b743c4f --- /dev/null +++ b/deno.ts @@ -0,0 +1,9 @@ +import { y18n as _y18n } from './build/lib/index.js' +import { Y18NOpts } from './build/lib/index.d.ts' +import shim from './lib/platform-shims/deno.ts' + +const y18n = (opts: Y18NOpts) => { + return _y18n(opts, shim) +} + +export default y18n diff --git a/lib/index.ts b/lib/index.ts index 065037c..864cbac 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -22,9 +22,9 @@ interface CacheEntry { export interface PlatformShim { fs: { readFileSync: Function, - statSync: Function, writeFile: Function }, + exists: Function, format: Function, resolve: Function } @@ -209,11 +209,7 @@ class Y18N { } _fileExistsSync (file: string) { - try { - return shim.fs.statSync(file).isFile() - } catch (err) { - return false - } + return shim.exists(file) } } diff --git a/lib/platform-shims/deno.ts b/lib/platform-shims/deno.ts new file mode 100644 index 0000000..3cd243e --- /dev/null +++ b/lib/platform-shims/deno.ts @@ -0,0 +1,34 @@ +/* global Deno */ + +import { posix } from 'https://deno.land/std/path/mod.ts' +import { sprintf } from 'https://deno.land/std/fmt/printf.ts' + +export default { + fs: { + readFileSync: (path: string) => { + try { + return Deno.readTextFileSync(path) + } catch (err) { + // Fake the same error as Node.js, so that it does not bubble. + err.code = 'ENOENT' + throw err + } + }, + writeFile: Deno.writeFile + }, + format: sprintf, + resolve: (base: string, p1: string, p2: string) => { + try { + return posix.resolve(base, p1, p2) + } catch (err) { + // Most likely we simply don't have --allow-read set. + } + }, + exists: (file: string) => { + try { + return Deno.statSync(file).isFile + } catch (err) { + return false + } + } +} diff --git a/lib/platform-shims/node.ts b/lib/platform-shims/node.ts index ab5bb22..2d40e7b 100644 --- a/lib/platform-shims/node.ts +++ b/lib/platform-shims/node.ts @@ -5,9 +5,15 @@ import { resolve } from 'path' export default { fs: { readFileSync, - statSync, writeFile }, format, - resolve + resolve, + exists: (file: string) => { + try { + return statSync(file).isFile() + } catch (err) { + return false + } + } } diff --git a/test/deno/y18n-test.ts b/test/deno/y18n-test.ts new file mode 100644 index 0000000..9b1b497 --- /dev/null +++ b/test/deno/y18n-test.ts @@ -0,0 +1,19 @@ +/* global Deno */ + +import { + assertEquals +} from 'https://deno.land/std/testing/asserts.ts' +import y18n from '../../deno.ts' + +// Parser: +Deno.test('smoke test', () => { + const __ = y18n({ + locale: 'pirate', + directory: './test/locales' + }).__ + + assertEquals( + __`Hi, ${'Ben'} ${'Coe'}!`, + 'Yarr! Shiver me timbers, why \'tis Ben Coe!' + ) +}) diff --git a/test/esm/y18n-test.mjs b/test/esm/y18n-test.mjs index 4a4a178..c8675ae 100644 --- a/test/esm/y18n-test.mjs +++ b/test/esm/y18n-test.mjs @@ -5,7 +5,7 @@ import y18n from '../../index.mjs' describe('y18n', function () { it('__ smoke test', function () { - var __ = y18n({ + const __ = y18n({ locale: 'pirate', directory: './test/locales' }).__ diff --git a/tsconfig.json b/tsconfig.json index 9cf3eed..9318745 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,5 +10,8 @@ }, "include": [ "lib/**/*.ts" + ], + "exclude": [ + "lib/platform-shims/deno.ts" ] } \ No newline at end of file diff --git a/tsconfig.test.json b/tsconfig.test.json index 5635d6c..b2175ed 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -2,5 +2,8 @@ "extends": "./tsconfig.json", "compilerOptions": { "sourceMap": true - } + }, + "exclude": [ + "lib/platform-shims/deno.ts" + ] } From 40b823070935e81d8b9376e104c3d772b76e7e4a Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 4 Sep 2020 17:17:02 -0700 Subject: [PATCH 9/9] build: do not publish types --- README.md | 17 +++++++++++++++++ package.json | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b80abac..56a18f6 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,23 @@ output: `2 fishes foo` +## Deno Example + +As of `v5` `y18n` supports [Deno](https://github.com/denoland/deno): + +```typescript +import y18n from "https://deno.land/x/y18n/deno.ts"; + +const __ = y18n({ + locale: 'pirate', + directory: './test/locales' +}).__ + +console.info(__`Hi, ${'Ben'} ${'Coe'}!`) +``` + +You will need to run with `--allow-read` to load alternative locales. + ## JSON Language Files The JSON language files should be stored in a `./locales` folder. diff --git a/package.json b/package.json index 285e186..df42208 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ }, "files": [ "build", - "index.mjs" + "index.mjs", + "!*.d.ts" ], "engines": { "node": ">=10"