diff --git a/index.js b/index.js index e5a5a79c..f639903a 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,11 @@ -var fs = require("fs"), - path = require("path"); +"use strict"; +const fs = require("fs"), + path = require("path"); -fs.readdirSync(path.join(__dirname, "lib/lang")).forEach(function(pathname) { - var match = /^([^\.].*)\.js$/.exec(pathname); +fs.readdirSync(path.join(__dirname, "lib/lang")).forEach(pathname => { + const match = /^([^\.].*)\.js$/.exec(pathname); - if(match) + if(match) { exports[match[1]] = require("./lib/lang/" + pathname); + } }); diff --git a/lib/lang/az.js b/lib/lang/az.js index 6be7858b..a8ac3090 100644 --- a/lib/lang/az.js +++ b/lib/lang/az.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = require("../template")({ "clear": "buludsuz", "no-precipitation": "yağmursuz", @@ -92,21 +94,22 @@ module.exports = require("../template")({ /* Capitalize the first letter of every word, except if that word is * "and". (This is a very crude bastardization of proper English titling * rules, but it is adequate for the purposes of this module.) */ - "title": function(str) { - return str.replace(/([a-zA-ZÇ窺IıƏəÖöĞğÜü.]+)/g, - function(txt) { - if(txt === "və" | txt === "düym"| txt === "sm.") return txt; - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - }); - }, + "title": str => str.replace( + /([a-zA-ZÇ窺IıƏəÖöĞğÜü.]+)/g, + txt => (txt === "və" || txt === "düym" || txt === "sm.")? + txt: + txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() + ), /* Capitalize the first word of the sentence and end with a period. */ - "sentence": function(str) { + "sentence": str => { /* Capitalize. */ str = str.charAt(0).toUpperCase() + str.slice(1); /* Add a period if there isn't already one. */ - if(str.charAt(str.length - 1) !== ".") str += "."; + if(str.charAt(str.length - 1) !== ".") { + str += "."; + } return str; - } + }, }); diff --git a/lib/lang/zh-tw.js b/lib/lang/zh-tw.js index 8c056862..84463c55 100644 --- a/lib/lang/zh-tw.js +++ b/lib/lang/zh-tw.js @@ -1,17 +1,4 @@ -function join_with_shared_prefix(a, b, joiner) { - var i = 0; - - while(i !== a.length && - i !== b.length && - a.charCodeAt(i) === a.charCodeAt(i)) - ++i; - - while(i && a.charCodeAt(i - 1) !== 32) - --i; - - return a + joiner + b.slice(i); -} - +"use strict"; module.exports = require("../template")({ "clear": "晴朗", "no-precipitation": "無降水", @@ -80,17 +67,11 @@ module.exports = require("../template")({ "inches": "$1英寸", "centimeters": "$1釐米", "less-than": "低於$1", - "and": function(a, b) { - return join_with_shared_prefix(a, b, a.indexOf("。") === -1 ? "," : ""); - }, - "through": function(a, b) { - return join_with_shared_prefix(a, b, "直至"); - }, + "and": (a, b) => a.endsWith("。")? a + b: a + "," + b, + "through": "$1直至$2", "with": "$1,且$2", "range": "$1\u2013$2", - "parenthetical": function(a, b) { - return a + "(" + b + (a === "mixed precipitation" ? "的雪)" : ")"); - }, + "parenthetical": "$1($2)", "for-hour": "在接下來一個小時內$1。", "starting-in": "$1將於$2後開始。", "stopping-in": "$1將於$2後結束。", @@ -98,15 +79,9 @@ module.exports = require("../template")({ "stopping-then-starting-later": "$1將於$2後結束,而在之後的$3又將繼續。", "for-day": "$1將持續一整天。", "starting": "$1開始於$2。", - "until": function(condition, period) { - return condition + "將持續至" + period; - }, - "until-starting-again": function(condition, a, b) { - return condition + "直到" + a + ",將於" + b + "再次出現"; - }, - "starting-continuing-until": function(condition, a, b) { - return condition + "開始於" + a + ",將持續至" + b; - }, + "until": "$1將持續至$2", + "until-starting-again": "$1直到$2,將於$3再次出現", + "starting-continuing-until": "$1開始於$2,將持續至$3", "during": "$1持續至$2", "for-week": "$1持續一整周", "over-weekend": "$1持續一整周", @@ -114,12 +89,6 @@ module.exports = require("../template")({ "temperatures-rising": "$2升溫到$1", "temperatures-valleying": "$2溫度驟降到$1", "temperatures-falling": "$2溫度下降到$1", - "title": function(str) { - return str; - }, - "sentence": function(str) { - if(str.charAt(str.length - 1) !== "。") - str += "。"; - return str; - } + "title": "$1", + "sentence": a => a.endsWith("。")? a: a + "。", }); diff --git a/lib/lang/zh.js b/lib/lang/zh.js index 70b8fcf9..bb9ebb14 100644 --- a/lib/lang/zh.js +++ b/lib/lang/zh.js @@ -1,17 +1,4 @@ -function join_with_shared_prefix(a, b, joiner) { - var i = 0; - - while(i !== a.length && - i !== b.length && - a.charCodeAt(i) === a.charCodeAt(i)) - ++i; - - while(i && a.charCodeAt(i - 1) !== 32) - --i; - - return a + joiner + b.slice(i); -} - +"use strict"; module.exports = require("../template")({ "clear": "晴朗", "no-precipitation": "无降水", @@ -80,17 +67,11 @@ module.exports = require("../template")({ "inches": "$1英寸", "centimeters": "$1厘米", "less-than": "低于$1", - "and": function(a, b) { - return join_with_shared_prefix(a, b, a.indexOf("。") === -1 ? "," : ""); - }, - "through": function(a, b) { - return join_with_shared_prefix(a, b, "直至"); - }, + "and": (a, b) => a.endsWith("。")? a + b: a + "," + b, + "through": "$1直至$2", "with": "$1,且$2", "range": "$1\u2013$2", - "parenthetical": function(a, b) { - return a + "(" + b + (a === "mixed precipitation" ? "的雪)" : ")"); - }, + "parenthetical": "$1($2)", "for-hour": "在接下来一个小时内$1。", "starting-in": "$1将于$2后开始。", "stopping-in": "$1将于$2后结束。", @@ -98,15 +79,9 @@ module.exports = require("../template")({ "stopping-then-starting-later": "$1将于$2后结束,而在之后的$3又将继续。", "for-day": "$1将持续一整天。", "starting": "$1开始于$2。", - "until": function(condition, period) { - return condition + "将持续至" + period; - }, - "until-starting-again": function(condition, a, b) { - return condition + "直到" + a + ",将于" + b + "再次出现"; - }, - "starting-continuing-until": function(condition, a, b) { - return condition + "开始于" + a + ",将持续至" + b; - }, + "until": "$1将持续至$2", + "until-starting-again": "$1直到$2,将于$3再次出现", + "starting-continuing-until": "$1开始于$2,将持续至$3", "during": "$1持续至$2", "for-week": "$1持续一整周", "over-weekend": "$1持续一整周", @@ -114,12 +89,6 @@ module.exports = require("../template")({ "temperatures-rising": "$2升温到$1", "temperatures-valleying": "$2温度骤降到$1", "temperatures-falling": "$2温度下降到$1", - "title": function(str) { - return str; - }, - "sentence": function(str) { - if(str.charAt(str.length - 1) !== "。") - str += "。"; - return str; - } + "title": "$1", + "sentence": a => a.endsWith("。")? a: a + "。", }); diff --git a/lib/template.js b/lib/template.js index 856a1b85..4b5c817e 100644 --- a/lib/template.js +++ b/lib/template.js @@ -1,80 +1,98 @@ -module.exports = function(template) { - function parse(expr, stack) { - var result; +"use strict"; - if(typeof expr === "number") { - stack.push(expr); +function parse(template, expr, stack) { + if(typeof expr === "number") { + return expr.toString(); + } + + let result; - result = expr.toString(); + if(typeof expr === "string") { + if(!template.hasOwnProperty(expr)) { + throw new Error("\"" + expr + "\" not found in language template."); } - else if(typeof expr === "string") { - stack.push(expr); + stack.push(expr); - if(!template.hasOwnProperty(expr)) - throw new Error("\"" + expr + "\" not found in language template."); + if(typeof template[expr] === "string") { + if(/\$\d+/.test(template[expr])) { + throw new Error( + "\"" + expr + "\" was used in a value context, " + + "but is expected in a template context." + ); + } - else if(typeof template[expr] === "string") { - if(/\$\d+/.test(template[expr])) - throw new Error("\"" + expr + "\" was used in a value context, but is expected in a template context."); + result = template[expr]; + } - else - result = template[expr]; + else if(typeof template[expr] === "function") { + if(template[expr].length !== 0) { + throw new Error( + "\"" + expr + "\" was used in a value context, " + + "but is expected in a template context." + ); } - else if(typeof template[expr] === "function") { - if(template[expr].length !== 0) - throw new Error("\"" + expr + "\" was used in a value context, but is expected in a template context."); + result = template[expr].call(stack); + } - else - result = template[expr].call(stack); - } + else { + throw new Error( + "\"" + expr + "\" is not a valid language template pattern." + ); + } + } - else - throw new Error("\"" + expr + "\" is not a valid language template pattern."); + else if(Array.isArray(expr) && + expr.length && + typeof expr[0] === "string") { + if(!template.hasOwnProperty(expr[0])) { + throw new Error("\"" + expr[0] + "\" not found in language template."); } - else if(Array.isArray(expr) && - expr.length && - typeof expr[0] === "string") { - stack.push(expr[0]); - - if(!template.hasOwnProperty(expr[0])) - throw new Error("\"" + expr[0] + "\" not found in language template."); - - else if(typeof template[expr[0]] === "string") - result = template[expr[0]].replace(/\$\d+/g, function(n) { - return parse(expr[n.slice(1)|0], stack); - }); - - else if(typeof template[expr[0]] === "function") { - if(template[expr[0]].length === 0) - throw new Error("\"" + expr[0] + "\" was used in a template context, but is expected in a value context."); - - else if(template[expr[0]].length !== expr.length - 1) - throw new Error("Template \"" + expr[0] + "\" did not expect " + (expr.length - 1) + " arguments."); - - else - result = template[expr[0]].apply( - stack, - expr.slice(1).map(function(arg) { - return parse(arg, stack); - }) - ); - } + stack.push(expr[0]); - else - throw new Error("\"" + expr[0] + "\" is not a valid language template pattern."); + if(typeof template[expr[0]] === "string") { + result = template[expr[0]].replace( + /\$\d+/g, + n => parse(template, expr[n.slice(1)|0], stack) + ); } - else - throw new Error("Invalid expression."); + else if(typeof template[expr[0]] === "function") { + if(template[expr[0]].length === 0) { + throw new Error( + "\"" + expr[0] + "\" was used in a template context, " + + "but is expected in a value context." + ); + } - stack.pop(); - return result; - }; + if(template[expr[0]].length !== expr.length - 1) { + throw new Error( + "Template \"" + expr[0] + "\" did not expect " + + (expr.length - 1) + " arguments." + ); + } - return function(expr) { - return parse(expr, []); + result = template[expr[0]].apply( + stack, + expr.slice(1).map(arg => parse(template, arg, stack)) + ); + } + + else { + throw new Error( + "\"" + expr[0] + "\" is not a valid language template pattern." + ); + } } -}; + + else { + throw new Error("Invalid expression."); + } + + stack.pop(); + return result; +} + +module.exports = template => expr => parse(template, expr, []); diff --git a/test.js b/test.js index 4460016c..a21e9a13 100644 --- a/test.js +++ b/test.js @@ -1,136 +1,144 @@ -var assert = require("assert"), - fs = require("fs"), - path = require("path"), - template = require("./lib/template"), - translation = require("./"), - util = require("util"); - -describe("translation", function() { - describe("template", function() { - var convert = template({ - "foo": "bar", - "bar": "meeple $2", - "baz": function(a, b) { return "meeple " + b; }, - "quux": function() { return "glorple"; } - }); +"use strict"; +const assert = require("assert"), + fs = require("fs"), + path = require("path"), + template = require("./lib/template"), + translation = require("./"), + util = require("util"); + +describe("translation", () => { + describe("template", () => { + const convert = template({ + "foo": "bar", + "bar": "meeple $2", + "baz": (a, b) => "meeple " + b, + "quux": () => "glorple", + }); - it("should return a number in string form", function() { + it("should return a number in string form", () => { assert.strictEqual(convert(42), "42"); }); - it("should throw an error given an unrecognized string", function() { - assert.throws(function() { convert("42"); }); + it("should throw an error given an unrecognized string", () => { + assert.throws(() => { convert("42"); }); }); - it("should apply an expected value conversion", function() { + it("should apply an expected value conversion", () => { assert.strictEqual(convert("foo"), "bar"); }); - it("should throw an error given a value that's expected to be a string template", function() { - assert.throws(function() { convert("bar"); }); + it("should throw an error given a value expected to be a string", () => { + assert.throws(() => { convert("bar"); }); }); - it("should throw an error given a value that's expected to be a function template", function() { - assert.throws(function() { convert("baz"); }); + it("should throw an error given a value expected to be a function", () => { + assert.throws(() => { convert("baz"); }); }); - it("should throw an error given an empty array", function() { - assert.throws(function() { convert([]); }); + it("should throw an error given an empty array", () => { + assert.throws(() => { convert([]); }); }); - it("should apply a string template", function() { + it("should apply a string template", () => { assert.strictEqual(convert(["bar", 10, 20]), "meeple 20"); }); - it("should fail to apply a function template given the wrong number of arguments", function() { - assert.throws(function() { convert(["baz", 10, 20, 30]); }); + it("should fail to apply a function with the wrong arity", () => { + assert.throws(() => { convert(["baz", 10, 20, 30]); }); }); - it("should apply a function template", function() { + it("should apply a function template", () => { assert.strictEqual(convert(["baz", 10, 20]), "meeple 20"); }); - it("should recursively apply function templates", function() { + it("should recursively apply function templates", () => { /* Actually, a "meeple meeple bar" sounds like it'd be a pretty tasty * candy treat. */ - assert.strictEqual(convert(["bar", 10, ["baz", 20, "foo"]]), "meeple meeple bar"); + assert.strictEqual( + convert(["bar", 10, ["baz", 20, "foo"]]), + "meeple meeple bar" + ); }); - it("should throw an error given undefined", function() { - assert.throws(function() { convert(undefined); }); + it("should throw an error given undefined", () => { + assert.throws(() => { convert(undefined); }); }); - it("should throw an error given null", function() { - assert.throws(function() { convert(null); }); + it("should throw an error given null", () => { + assert.throws(() => { convert(null); }); }); - it("should throw an error given an object", function() { - assert.throws(function() { convert({}); }); + it("should throw an error given an object", () => { + assert.throws(() => { convert({}); }); }); - it("should apply an expected value conversion given a zero-argument function", function() { + it("should apply a zero-argument function", () => { assert.strictEqual(convert("quux"), "glorple"); }); - it("should fail to apply a zero-argument function given arguments", function() { - assert.throws(function() { convert(["quux"]); }); - }); - - it("should fail to apply a function template given a value", function() { - assert.throws(function() { convert("baz"); }); - }); - - it("should provide context to functions", function() { - var convert = template({ - "foo": function(a, b, c) { - assert.deepEqual(this, ["foo"]); - return "Moop."; - }, - "bar": function() { - assert.deepEqual(this, ["foo", "bar"]); - return "Boop."; - }, - "baz": function(a) { - assert.deepEqual(this, ["foo", "baz"]); - return "Soup."; - }, - "quux": function() { - assert.deepEqual(this, ["foo", "baz", "quux"]); - return "Floop."; - }, - "neem": function(a) { - assert.deepEqual(this, ["foo", "neem"]); - return "Bloop."; - }, - "glorp": function(a) { - assert.deepEqual(this, ["foo", "neem", "glorp"]); - return "Rope?"; - } - }); + it("should fail to apply a zero-argument function given arguments", () => { + assert.throws(() => { convert(["quux"]); }); + }); + + it("should fail to apply a function template given a value", () => { + assert.throws(() => { convert("baz"); }); + }); + + it("should provide context to functions", () => { + const convert = template({ + "foo": function(a, b, c) { + assert.deepEqual(this, ["foo"]); + return "Moop."; + }, + "bar": function() { + assert.deepEqual(this, ["foo", "bar"]); + return "Boop."; + }, + "baz": function(a) { + assert.deepEqual(this, ["foo", "baz"]); + return "Soup."; + }, + "quux": function() { + assert.deepEqual(this, ["foo", "baz", "quux"]); + return "Floop."; + }, + "neem": function(a) { + assert.deepEqual(this, ["foo", "neem"]); + return "Bloop."; + }, + "glorp": function(a) { + assert.deepEqual(this, ["foo", "neem", "glorp"]); + return "Rope?"; + } + }); convert(["foo", "bar", ["baz", "quux"], ["neem", ["glorp", 42]]]); }); }); - describe("language", function() { - fs.readdirSync(path.join(__dirname, "test_cases")).forEach(function(lang) { - if(lang.charAt(0) === ".") + describe("language", () => { + fs.readdirSync(path.join(__dirname, "test_cases")).forEach(lang => { + if(lang.charAt(0) === ".") { return; + } - var name = path.basename(lang, ".json"), - translate = translation[name]; + const name = path.basename(lang, ".json"), + translate = translation[name]; - describe(name, function() { - var cases = JSON.parse( - fs.readFileSync(path.join(__dirname, "test_cases", lang), "utf8") + describe(name, () => { + const cases = JSON.parse( + fs.readFileSync( + path.join(__dirname, "test_cases", lang), + "utf8" + ) ); - Object.keys(cases).forEach(function(summary) { - var source = cases[summary]; + Object.keys(cases).forEach(summary => { + const source = cases[summary]; it( util.format("should translate %j to \"%s\"", source, summary), - function() { + () => { assert.strictEqual(translate(source), summary); } );