Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for arrays of strings to i18n. Arrays support pluralisa… #30

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions i18n/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ local function isPresent(str)
return type(str) == 'string' and #str > 0
end

local function isArray(t)
return type(t) == 'table' and (#t > 0 or next(t) == nil)
end

local function assertPresent(functionName, paramName, value)
if isPresent(value) then return end

Expand All @@ -43,7 +47,7 @@ local function assertPresent(functionName, paramName, value)
end

local function assertPresentOrPlural(functionName, paramName, value)
if isPresent(value) or isPluralTable(value) then return end
if isPresent(value) or isPluralTable(value) or isArray(value) then return end

local msg = "i18n.%s requires a non-empty string or plural-form table on its %s. Got %s (a %s value)."
error(msg:format(functionName, paramName, tostring(value), type(value)))
Expand All @@ -70,13 +74,26 @@ end
local function pluralize(t, data)
assertPresentOrPlural('interpolatePluralTable', 't', t)
data = data or {}
local count = data.count or 1
local key
for _, v in pairs(t) do
key = interpolate.getInterpolationKey(v, data)
if key then
break
end
end
local count = data[key or "count"] or 1
local plural_form = pluralizeFunction(count)
return t[plural_form]
end

local function treatNode(node, data)
if type(node) == 'string' then
if isArray(node) then
local iter = {ipairs(node)}
node = {}
for k,v in unpack(iter) do
node[k] = treatNode(v, data)
end
elseif type(node) == 'string' then
return interpolate(node, data)
elseif isPluralTable(node) then
return interpolate(pluralize(node, data), data)
Expand All @@ -90,7 +107,7 @@ local function recursiveLoad(currentContext, data)
composedKey = (currentContext and (currentContext .. '.') or "") .. tostring(k)
assertPresent('load', composedKey, k)
assertPresentOrTable('load', composedKey, v)
if type(v) == 'string' then
if type(v) == 'string' or isArray(v) then
i18n.set(composedKey, v)
else
recursiveLoad(composedKey, v)
Expand Down
26 changes: 23 additions & 3 deletions i18n/interpolate.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
local unpack = unpack or table.unpack -- lua 5.2 compat

local interpolate = {}

local FORMAT_CHARS = { c=1, d=1, E=1, e=1, f=1, g=1, G=1, i=1, o=1, u=1, X=1, x=1, s=1, q=1, ['%']=1 }

local fieldPattern = "(.?)%%{%s*(.-)%s*}"
local valuePattern = "(.?)%%<%s*(.-)%s*>%.([cdEefgGiouXxsq])"
function interpolate.getInterpolationKey(string, variables)
for previous, key in string:gmatch(fieldPattern) do
if previous ~= "%" and variables[key] then
return key
end
end

for previous, key in string:gmatch(valuePattern) do
if previous ~= "%" and variables[key] then
return key
end
end
end

-- matches a string of type %{age}
local function interpolateValue(string, variables)
return string:gsub("(.?)%%{%s*(.-)%s*}",
return string:gsub(fieldPattern,
function (previous, key)
if previous == "%" then
return
Expand All @@ -16,7 +34,7 @@ end

-- matches a string of type %<age>.d
local function interpolateField(string, variables)
return string:gsub("(.?)%%<%s*(.-)%s*>%.([cdEefgGiouXxsq])",
return string:gsub(valuePattern,
function (previous, key, format)
if previous == "%" then
return
Expand Down Expand Up @@ -46,7 +64,7 @@ local function unescapePercentages(string)
end)
end

local function interpolate(pattern, variables)
local function interpolateString(pattern, variables)
variables = variables or {}
local result = pattern
result = interpolateValue(result, variables)
Expand All @@ -57,4 +75,6 @@ local function interpolate(pattern, variables)
return result
end

setmetatable(interpolate, {__call = function(_, ...) return interpolateString(...) end})

return interpolate
3 changes: 2 additions & 1 deletion spec/en.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
return {
en = {
hello = 'Hello!',
balance = 'Your account balance is %{value}.'
balance = 'Your account balance is %{value}.',
array = {"one", "two", "three"}
}
}
2 changes: 1 addition & 1 deletion spec/i18n_interpolate_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ local interpolate = require 'i18n.interpolate'

describe('i18n.interpolate', function()
it("exists", function()
assert.equal('function', type(interpolate))
assert.equal('table', type(interpolate))
end)

it("performs standard interpolation via string.format", function()
Expand Down
83 changes: 83 additions & 0 deletions spec/i18n_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,89 @@ describe('i18n', function()
assert.equal('Hello!', i18n('hello'))
local balance = i18n('balance', {value = 0})
assert.equal('Your account balance is 0.', balance)
assert.same({"one", "two", "three"}, i18n('array'))
end)
end)

describe('arrays', function()
it("Load supports arrays of strings", function()
i18n.set('en.array', {"one", "two", "three"})
assert.same({"one", "two", "three"},i18n('array'))
end)

it("Arrays respect languages", function()
i18n.set('en.array', {"one", "two", "three"})
i18n.set('fr.array', {"un", "deux", "trois"})
i18n.setLocale('fr')
assert.same({"un", "deux", "trois"},i18n('array'))
end)

it("Variables in array elements can be interpolated", function()
i18n.set('en.suede', {"%{count} for the count", "%{show} for the show", "%{ready} to make ready", "go!"})
assert.same({
"one for the count",
"two for the show",
"three to make ready",
"go!"
},i18n('suede',{count = "one", show = "two", ready = "three"}))
end)

it("Interpolation produces different results when called with different values", function()
i18n.set('en.suede', {"%{count} for the count", "%{show} for the show", "%{ready} to make ready", "go!"})
assert.same({
"one for the count",
"two for the show",
"three to make ready",
"go!"
},i18n('suede',{count = "one", show = "two", ready = "three"}))

assert.same({
"a for the count",
"b for the show",
"c to make ready",
"go!"
},i18n('suede',{count = "a", show = "b", ready = "c"}))
end)

it("Variables in array elements can be pluralized", function()
i18n.set('en.safe', {"Welcome to Apature!", {
one = "%{count} unfortunate retirement today!",
other = "%{count} unfortunate retirements today!"}
})
assert.same({
"Welcome to Apature!",
"1 unfortunate retirement today!",
},i18n('safe',{count = 1}))
end)

it("Variables in array elements can be pluralized independently", function()
i18n.set('en.safe', {
"Welcome to Apature!", {
one = "%{count} unfortunate retirement today!",
other = "%{count} unfortunate retirements today!"
}, {
one = "only %{test} test subject active!",
other = "%{test} test subjects active"
}
})
assert.same({
"Welcome to Apature!",
"10 unfortunate retirements today!",
"only 1 test subject active!"
},i18n('safe',{count = 10, test = 1}))
end)

it("Without field or value names in string. Pluralisation assumes count as default key", function()
i18n.set('en.safe', {
"Welcome to Apature!", {
one = "It's great!",
other = "It's mostly safe!"
}
})
assert.same({
"Welcome to Apature!",
"It's mostly safe!",
},i18n('safe',{count = 2}))
end)
end)

Expand Down