From d4f5319e02b1dd868db7812c67dcbeb232ffeb50 Mon Sep 17 00:00:00 2001 From: Er2 Date: Mon, 22 Nov 2021 21:05:55 +0300 Subject: [PATCH] init --- erl/LINGUAS | 2 ++ erl/en.els | 12 +++++++ erl/ru.els | 13 ++++++++ license | 20 ++++++++++++ locale.lua | 38 ++++++++++++++++++++++ readme.org | 36 +++++++++++++++++++++ syntax/els.yaml | 13 ++++++++ syntax/example.els | 20 ++++++++++++ tools/compile.lua | 79 ++++++++++++++++++++++++++++++++++++++++++++++ tools/po2els.lua | 60 +++++++++++++++++++++++++++++++++++ 10 files changed, 293 insertions(+) create mode 100644 erl/LINGUAS create mode 100644 erl/en.els create mode 100644 erl/ru.els create mode 100644 license create mode 100644 locale.lua create mode 100644 readme.org create mode 100644 syntax/els.yaml create mode 100644 syntax/example.els create mode 100644 tools/compile.lua create mode 100644 tools/po2els.lua diff --git a/erl/LINGUAS b/erl/LINGUAS new file mode 100644 index 0000000..b90ab21 --- /dev/null +++ b/erl/LINGUAS @@ -0,0 +1,2 @@ +en +ru diff --git a/erl/en.els b/erl/en.els new file mode 100644 index 0000000..e613d0e --- /dev/null +++ b/erl/en.els @@ -0,0 +1,12 @@ +Er2 Locale source file + +TRANSLATORS: Plural forms! +TRANSLATORS: $plurals are needed +"My string" + > "\$hehe $plural form" + +"New string" + = "New translate" + > "New translates" + +"Basic string" diff --git a/erl/ru.els b/erl/ru.els new file mode 100644 index 0000000..0472a7e --- /dev/null +++ b/erl/ru.els @@ -0,0 +1,13 @@ +Er2 Locale source file + +TRANSLATORS: Plural forms! +TRANSLATORS: $plurals are needed +"My string" + = "Моя строка" + > "\$hehe $plural форма" + +"New string" + = "Новый перевод" + > "Новые переводы" + +"Basic string" specially not translated diff --git a/license b/license new file mode 100644 index 0000000..afdeb62 --- /dev/null +++ b/license @@ -0,0 +1,20 @@ +Zlib License + +Copyright (c) 2021 Er2 + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/locale.lua b/locale.lua new file mode 100644 index 0000000..2f2517b --- /dev/null +++ b/locale.lua @@ -0,0 +1,38 @@ +-- Improved gettext +--- Er2 Locale +--- (c) Er2 2021 +--- Zlib License +local gtext = { + dloc = 'en_US', sdloc = 'en', + pref = '$' +} +function gtext:__call(str, t) + t = type(t) == 'table' and t or {} + t.plural = t.plural or 0 + local plf = t.plural + 1 + local tr = self.trans[self.locale or self.floc] + if tr and tr[str] and tr[str][plf] then str = tr[str][plf] + else + tr = self.trans[self.floc] + if tr and tr[str] and tr[str][plf] then str = tr[str][plf] end + end + + -- Interpolation + str = str:gsub('(.?)%'..gtext.pref..'([%w_]+)', function(p, v) + if p == '\\' then return gtext.pref..v end + return p.. tostring(t[v] or '') + end) + return str +end + +return function(path) + local self = setmetatable({ + floc = gtext.dloc, + trans = require(path..'.loc') + }, gtext) + + if not self.trans[self.floc] then self.floc = gtext.sdloc end + if not self.trans[self.floc] then self.floc = l:match '([^\n]+)' end + self.locale = self.floc + _, gettext = self, self +end diff --git a/readme.org b/readme.org new file mode 100644 index 0000000..781f963 --- /dev/null +++ b/readme.org @@ -0,0 +1,36 @@ +* Er2 Locale + +Originally it was a part of ErTK (under development), +but I decide to open source to other repository. + +Currently there is only Lua implementation, +but you can port to other language. + +* Syntax + +Localization files is using it's own format (see ~syntax/example.els~). +Also you can see at the ~erl~ directory. + +** Editor support + +Syntax highlighting available only in ~micro~ editor +(~syntax~ directory) + +* Tools + +Except runtime we have 2 tools: + + - ~compile.lua~ - Compiles els to one (prog. language specific!) file + + - ~po2els.lua~ - If you want to migrate from gettext + or if translation program/service cannot export to els yet + +* Example + +#+begin_src lua + require('locale') 'erl' -- init and load erl folder + _.locale = 'ru' -- set locale will be used, maybe create $LANG detect + _("My string") -- basic usage + _ "My string" -- without (), lua feature + _("My string", {plural = 1}) -- print plural form, maybe create other function for this +#+end_src diff --git a/syntax/els.yaml b/syntax/els.yaml new file mode 100644 index 0000000..bd95120 --- /dev/null +++ b/syntax/els.yaml @@ -0,0 +1,13 @@ +filetype: els + +detect: + filename: "\\.els$" + header: "Er2 Locale" + +rules: + - comment: ".+" # Comment by default + - constant.string: "\"([^\"]+)\"$" # Non-critical bug + - todo: "(TODO|XXX|FIXME|TRANSLATORS):.+$" + - statement: "^[[:space:]]*[=>]" + - identifier: "[^\\\\]\\$([A-Za-z0-9)]+)" + - special: "\\\\.?" diff --git a/syntax/example.els b/syntax/example.els new file mode 100644 index 0000000..895d801 --- /dev/null +++ b/syntax/example.els @@ -0,0 +1,20 @@ +Er2 Locale source + +Let's create a new string +"Just string" +Currently it haven't got translation +but we can fix it + = "No, it's localized string" +Spaces is optional +Also we can replace this translation + = "Power string!" + +This is basic, how about plural forms? + > "Power strings!" +Spaces is also optional + +Oh, I forgot! We can use interpolation + > "\$escaped $plural not escaped variable" +To use it, type _("string", {key = value}) + +Now you know this localization format! diff --git a/tools/compile.lua b/tools/compile.lua new file mode 100644 index 0000000..ba1c48f --- /dev/null +++ b/tools/compile.lua @@ -0,0 +1,79 @@ +#!/usr/bin/lua +-- els to lua translator +--- (c) Er2 2021 +--- Zlib license + +print '[1/4] Enter translation directory (without / at end)' +local dir = io.read() + +local loc = {} + +if not io.open(dir..'/LINGUAS') then + print '[2/4] Enter languages divided by space' + local l = io.read() .. ' ' + for v in l:gmatch '([%w_]+)%s+' do table.insert(loc, v) end +else + print '[2/4] Reading LINGUAS' + local f = io.open(dir..'/LINGUAS') + for v in f:read('*a'):gmatch '([^\n]+)' do table.insert(loc, v) end + f:close() +end + +print '[3/4] Generating locales' +local t = os.time() +local lf = io.open(dir..'/loc.lua', 'w') +local k, v = 1 +lf:write 'return{' +repeat v = loc[k] + local f = io.open(dir..'/'..v..'.els') + if f then print(' Generating', v) + local tr = f:read '*a' + f:close() + lf:write(('%s={'):format(v)) + local trl, s = {} + tr = tr..'\n\b' + for l in tr:gmatch '([^\n]+)' do + local str = l:match '^%s*"(.+)"%s*$' + if l == '\b' or (str and str ~= s) then + for _, v in pairs(trl) do + v = v:gsub('\\(%$[%w_]+)', '\\\\%1') + lf:write(('"%s",'):format(v)) + end + if s then lf:write '},' end + trl, s = {str}, str + if s then + print(' |> String', s) + lf:write(('["%s"]={'):format(s)) + end + end + + str = l:match '^%s*=%s*"(.+)"%s*$' + if str and s then + print(' =>', str) + trl[1] = str + end + str = l:match '^%s*>%s*"(.+)"%s*$' + if str and s then + print(' ||>', str) + table.insert(trl, str) + end + end + + lf:write '},' + else + print(' 404 Not found', v) + table.remove(loc, k) + k = k - 1 + end + k = k + 1 + v = loc[k] +until not v +lf:write '}' +lf:flush() +lf:close() +print(('%d seconds'):format(os.time() - t)) + +print '[4/4] Locale linting' +require(dir..'.loc') + +print 'Done!' diff --git a/tools/po2els.lua b/tools/po2els.lua new file mode 100644 index 0000000..3dd7f26 --- /dev/null +++ b/tools/po2els.lua @@ -0,0 +1,60 @@ +#!/usr/bin/lua +-- po to els translator +--- (c) Er2 2021 +--- Zlib license + +print '[1/3] Enter translation directory (without / at end)' +local dir = io.read() + +local loc = {} + +if not io.open(dir..'/LINGUAS') then + print '[2/3] Enter languages divided by space' + local l = io.read() .. ' ' + for v in l:gmatch '([%w_]+)%s+' do table.insert(loc, v) end +else + print '[2/3] Reading LINGUAS' + local f = io.open(dir..'/LINGUAS') + for v in f:read('*a'):gmatch '([^\n]+)' do table.insert(loc, v) end + f:close() +end + +print '[3/3] Generating els' +local t = os.time() +local w = 0 +local k, v = 1 +repeat v = loc[k] + local f = io.open(dir..'/'..v..'.po') + if f then print(' Generating', v) + local tr = f:read '*a' + f:close() + f = io.open(dir..'/'..v..'.els', 'w') + f:write 'Er2 Locale source file\n' + for l in tr:gmatch '[^\n]+' do + if l:match '%%%a' then w = w + 1 + print(' WARN', 'Found printf string, not will be replaced') + print(' INFO', l) + end + l = l + :gsub('^%s*".*"%s*$', '') + :gsub('^msgid_plural.+', '') + :gsub('^msgid%s+"(.*)"', '"%1"') + :gsub('^msgstr%s+"(.*)"', ' = "%1"') + :gsub('^msgstr%s*%[%s*(%d+)%s*%]%s+(".*")', function(id, str) + return (id == '0' and ' = ' or ' > ') .. str end) + :gsub('^%s*[=>]?%s*""', '') + if #l > 0 then f:write(l, '\n') end + end + f:flush() + f:close() + else + print(' 404 Not found', v) + table.remove(loc, k) + k = k - 1 + end + k = k + 1 + v = loc[k] +until not v +print(('%d seconds, %d warnings'):format(os.time() - t, w)) + +print 'Done!'