Skip to content
Snippets Groups Projects
Unverified Commit 557565e1 authored by Matthias Schiffer's avatar Matthias Schiffer
Browse files

gluon-web: add i18n package namespaces

parent 1a426c3b
No related branches found
No related tags found
No related merge requests found
Showing with 298 additions and 283 deletions
package 'gluon-web-private-wifi'
entry({"admin", "privatewifi"}, model("admin/privatewifi"), _("Private WLAN"), 30) entry({"admin", "privatewifi"}, model("admin/privatewifi"), _("Private WLAN"), 30)
...@@ -33,6 +33,10 @@ You may obtain a copy of the License at ...@@ -33,6 +33,10 @@ You may obtain a copy of the License at
return r return r
end end
local function title(node)
return i18n(node.pkg).translate(node.title)
end
local function subtree(prefix, node, name, ...) local function subtree(prefix, node, name, ...)
if not node then return end if not node then return end
...@@ -48,7 +52,7 @@ You may obtain a copy of the License at ...@@ -48,7 +52,7 @@ You may obtain a copy of the License at
local active = (v == name) local active = (v == name)
%> %>
<li class="tabmenu-item-<%=v%><% if active then %> active<% end %>"> <li class="tabmenu-item-<%=v%><% if active then %> active<% end %>">
<a href="<%=url(append(prefix, v))%>"><%=pcdata(translate(child.title))%></a> <a href="<%=url(append(prefix, v))%>"><%=pcdata(title(child))%></a>
</li> </li>
<% <%
end end
...@@ -71,7 +75,7 @@ You may obtain a copy of the License at ...@@ -71,7 +75,7 @@ You may obtain a copy of the License at
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" /> <link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
<title><%=pcdata( hostname .. ( (rnode and rnode.title) and ' - ' .. translate(rnode.title) or '')) %></title> <title><%=pcdata( hostname .. ((rnode and rnode.title) and ' - ' .. title(rnode) or '')) %></title>
</head> </head>
<body> <body>
...@@ -88,7 +92,7 @@ You may obtain a copy of the License at ...@@ -88,7 +92,7 @@ You may obtain a copy of the License at
<% if #categories > 1 and not hidenav then %> <% if #categories > 1 and not hidenav then %>
<ul id="topmenu"> <ul id="topmenu">
<% for i, r in ipairs(categories) do %> <% for i, r in ipairs(categories) do %>
<li><a class="topcat<% if request[1] == r then %> active<%end%>" href="<%=url({r})%>"><%=pcdata(translate(root.nodes[r].title))%></a></li> <li><a class="topcat<% if request[1] == r then %> active<%end%>" href="<%=url({r})%>"><%=pcdata(title(root.nodes[r]))%></a></li>
<% end %> <% end %>
</ul> </ul>
<% end %> <% end %>
...@@ -110,9 +114,9 @@ You may obtain a copy of the License at ...@@ -110,9 +114,9 @@ You may obtain a copy of the License at
</noscript> </noscript>
<% <%
ok, err = pcall(include, content) ok, err = pcall(renderer.render, content, env, pkg)
if not ok then if not ok then
renderer.render('error500', {message = err}) renderer.render('error500', {message = err}, 'gluon-web')
end end
%> %>
......
package 'gluon-web-wifi-config'
entry({"admin", "wifi-config"}, model("admin/wifi-config"), _("WLAN"), 20) entry({"admin", "wifi-config"}, model("admin/wifi-config"), _("WLAN"), 20)
-- Copyright 2008 Steven Barth <steven@midlink.org> -- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org> -- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net> -- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0. -- Licensed to the public under the Apache License 2.0.
local fs = require "nixio.fs" local fs = require "nixio.fs"
...@@ -77,7 +77,7 @@ local function set_language(renderer, accept) ...@@ -77,7 +77,7 @@ local function set_language(renderer, accept)
end end
for match in accept:gmatch("[^,]+") do for match in accept:gmatch("[^,]+") do
local lang = match:match('^%s*([^%s;-_]+)') local lang = match:match('^%s*([^%s;_-]+)')
local q = tonumber(match:match(';q=(%S+)%s*$') or 1) local q = tonumber(match:match(';q=(%S+)%s*$') or 1)
if lang == '*' then if lang == '*' then
...@@ -93,11 +93,7 @@ local function set_language(renderer, accept) ...@@ -93,11 +93,7 @@ local function set_language(renderer, accept)
return (weights[a] or 0) > (weights[b] or 0) return (weights[a] or 0) > (weights[b] or 0)
end) end)
for _, lang in ipairs(langs) do renderer.set_language(langs)
if renderer.setlanguage(lang) then
return
end
end
end end
...@@ -147,7 +143,20 @@ function dispatch(http, request) ...@@ -147,7 +143,20 @@ function dispatch(http, request)
url = function(path) return build_url(http, path) end, url = function(path) return build_url(http, path) end,
}, { __index = _G })) }, { __index = _G }))
local function createtree()
local base = util.libpath() .. "/controller/"
local function load_ctl(path)
local ctl = assert(loadfile(path))
local _pkg
local subdisp = setmetatable({ local subdisp = setmetatable({
package = function(name)
_pkg = name
end,
node = function(...) node = function(...)
return _node({...}) return _node({...})
end, end,
...@@ -158,6 +167,7 @@ function dispatch(http, request) ...@@ -158,6 +167,7 @@ function dispatch(http, request)
c.target = target c.target = target
c.title = title c.title = title
c.order = order c.order = order
c.pkg = _pkg
return c return c
end, end,
...@@ -177,17 +187,19 @@ function dispatch(http, request) ...@@ -177,17 +187,19 @@ function dispatch(http, request)
end, end,
template = function(view) template = function(view)
local pkg = _pkg
return function() return function()
renderer.render("layout", {content = view}) renderer.render("layout", {content = view, pkg = pkg})
end end
end, end,
model = function(name) model = function(name)
local pkg = _pkg
return function() return function()
local hidenav = false local hidenav = false
local model = require "gluon.web.model" local model = require "gluon.web.model"
local maps = model.load(name, renderer) local maps = model.load(name, renderer, pkg)
for _, map in ipairs(maps) do for _, map in ipairs(maps) do
map:parse(http) map:parse(http)
...@@ -199,7 +211,9 @@ function dispatch(http, request) ...@@ -199,7 +211,9 @@ function dispatch(http, request)
renderer.render("layout", { renderer.render("layout", {
content = "model/wrapper", content = "model/wrapper",
env = {
maps = maps, maps = maps,
},
hidenav = hidenav, hidenav = hidenav,
}) })
end end
...@@ -210,12 +224,6 @@ function dispatch(http, request) ...@@ -210,12 +224,6 @@ function dispatch(http, request)
end, end,
}, { __index = _G }) }, { __index = _G })
local function createtree()
local base = util.libpath() .. "/controller/"
local function load_ctl(path)
local ctl = assert(loadfile(path))
local env = setmetatable({}, { __index = subdisp }) local env = setmetatable({}, { __index = subdisp })
setfenv(ctl, env) setfenv(ctl, env)
...@@ -239,9 +247,14 @@ function dispatch(http, request) ...@@ -239,9 +247,14 @@ function dispatch(http, request)
if not node or not node.target then if not node or not node.target then
http:status(404, "Not Found") http:status(404, "Not Found")
renderer.render("layout", { content = "error404", message = renderer.render("layout", {
content = "error404",
env = {
message =
"No page is registered at '/" .. table.concat(request, "/") .. "'.\n" .. "No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
"If this URL belongs to an extension, make sure it is properly installed.\n" "If this URL belongs to an extension, make sure it is properly installed.\n",
},
pkg = 'gluon-web',
}) })
return return
end end
...@@ -251,9 +264,14 @@ function dispatch(http, request) ...@@ -251,9 +264,14 @@ function dispatch(http, request)
local ok, err = pcall(node.target) local ok, err = pcall(node.target)
if not ok then if not ok then
http:status(500, "Internal Server Error") http:status(500, "Internal Server Error")
renderer.render("layout", { content = "error500", message = renderer.render("layout", {
content = "error500",
env = {
message =
"Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" .. "Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)") "The called action terminated with an exception:\n" .. tostring(err or "(unknown)"),
},
pkg = 'gluon-web',
}) })
end end
end end
-- Copyright 2018 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0.
local tparser = require "gluon.web.template.parser"
local util = require "gluon.web.util"
local fs = require "nixio.fs"
local i18ndir = util.libpath() .. "/i18n"
local function i18n_file(lang, pkg)
return string.format('%s/%s.%s.lmo', i18ndir, pkg, lang)
end
local function no_translation(key)
return nil
end
local function load_catalog(lang, pkg)
if pkg then
local file = i18n_file(lang, pkg)
local cat = fs.access(file) and tparser.load_catalog(file)
if cat then return cat end
end
return no_translation
end
module "gluon.web.i18n"
function supported(lang)
return lang == 'en' or fs.access(i18n_file(lang, 'gluon-web'))
end
function load(lang, pkg)
local _translate = load_catalog(lang, pkg)
local function translate(key)
return _translate(key) or key
end
local function translatef(key, ...)
return translate(key):format(...)
end
return {
_translate = _translate,
translate = translate,
translatef = translatef,
}
end
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
module("gluon.web.model", package.seeall) module("gluon.web.model", package.seeall)
local util = require("gluon.web.util") local util = require "gluon.web.util"
local fs = require("nixio.fs") local fs = require "nixio.fs"
local datatypes = require("gluon.web.model.datatypes") local datatypes = require "gluon.web.model.datatypes"
local dispatcher = require("gluon.web.dispatcher") local dispatcher = require "gluon.web.dispatcher"
local class = util.class local class = util.class
local instanceof = util.instanceof local instanceof = util.instanceof
...@@ -17,7 +17,7 @@ FORM_VALID = 1 ...@@ -17,7 +17,7 @@ FORM_VALID = 1
FORM_INVALID = -1 FORM_INVALID = -1
-- Loads a model from given file, creating an environment and returns it -- Loads a model from given file, creating an environment and returns it
function load(name, renderer) function load(name, renderer, pkg)
local modeldir = util.libpath() .. "/model/" local modeldir = util.libpath() .. "/model/"
if not fs.access(modeldir..name..".lua") then if not fs.access(modeldir..name..".lua") then
...@@ -26,14 +26,16 @@ function load(name, renderer) ...@@ -26,14 +26,16 @@ function load(name, renderer)
local func = assert(loadfile(modeldir..name..".lua")) local func = assert(loadfile(modeldir..name..".lua"))
local env = { local i18n = setmetatable({
translate=renderer.translate, i18n = renderer.i18n
translatef=renderer.translatef, }, {
} __index = renderer.i18n(pkg)
})
setfenv(func, setmetatable(env, {__index = setfenv(func, setmetatable({}, {__index =
function(tbl, key) function(tbl, key)
return _M[key] or _G[key] return _M[key] or i18n[key] or _G[key]
end end
})) }))
...@@ -85,6 +87,7 @@ function Node:__init__(title, description, name) ...@@ -85,6 +87,7 @@ function Node:__init__(title, description, name)
self.name = name self.name = name
self.index = nil self.index = nil
self.parent = nil self.parent = nil
self.package = 'gluon-web'
end end
function Node:append(obj) function Node:append(obj)
...@@ -116,7 +119,7 @@ function Node:render(renderer, scope) ...@@ -116,7 +119,7 @@ function Node:render(renderer, scope)
id = self:id(), id = self:id(),
scope = scope, scope = scope,
}, {__index = scope}) }, {__index = scope})
renderer.render(self.template, env) renderer.render(self.template, env, self.package)
end end
end end
......
-- Copyright 2008 Steven Barth <steven@midlink.org> -- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net> -- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0. -- Licensed to the public under the Apache License 2.0.
local tparser = require "gluon.web.template.parser" local tparser = require "gluon.web.template.parser"
local i18n = require "gluon.web.i18n"
local util = require "gluon.web.util" local util = require "gluon.web.util"
local fs = require "nixio.fs"
local tostring, setmetatable, setfenv, pcall, assert = tostring, setmetatable, setfenv, pcall, assert local tostring, ipairs, setmetatable, setfenv = tostring, ipairs, setmetatable, setfenv
local pcall, assert = pcall, assert
module "gluon.web.template" module "gluon.web.template"
local viewdir = util.libpath() .. "/view/" local viewdir = util.libpath() .. "/view/"
local i18ndir = util.libpath() .. "/i18n/"
function renderer(env) function renderer(env)
local ctx = {} local ctx = {}
local language = 'en'
local catalogs = {}
local function render_template(name, template, scope) function ctx.set_language(langs)
for _, lang in ipairs(langs) do
if i18n.supported(lang) then
language = lang
catalogs = {}
return
end
end
end
function ctx.i18n(pkg)
local cat = catalogs[pkg] or i18n.load(language, pkg)
if pkg then catalogs[pkg] = cat end
return cat
end
local function render_template(name, template, scope, pkg)
scope = scope or {} scope = scope or {}
local t = ctx.i18n(pkg)
local locals = { local locals = {
renderer = ctx, renderer = ctx,
translate = ctx.translate, i18n = ctx.i18n,
translatef = ctx.translatef, translate = t.translate,
_translate = ctx._translate, translatef = t.translatef,
_translate = t._translate,
include = function(name) include = function(name)
ctx.render(name, scope) ctx.render(name, scope, pkg)
end, end,
} }
setfenv(template, setmetatable({}, { setfenv(template, setmetatable({}, {
__index = function(tbl, key) __index = function(tbl, key)
return scope[key] or env[key] or locals[key] return scope[key] or locals[key] or env[key]
end end
})) }))
...@@ -46,7 +66,7 @@ function renderer(env) ...@@ -46,7 +66,7 @@ function renderer(env)
--- Render a certain template. --- Render a certain template.
-- @param name Template name -- @param name Template name
-- @param scope Scope to assign to template (optional) -- @param scope Scope to assign to template (optional)
function ctx.render(name, scope) function ctx.render(name, scope, pkg)
local sourcefile = viewdir .. name .. ".html" local sourcefile = viewdir .. name .. ".html"
local template, _, err = tparser.parse(sourcefile) local template, _, err = tparser.parse(sourcefile)
...@@ -54,45 +74,19 @@ function renderer(env) ...@@ -54,45 +74,19 @@ function renderer(env)
"Error while parsing template '" .. sourcefile .. "':\n" .. "Error while parsing template '" .. sourcefile .. "':\n" ..
(err or "Unknown syntax error")) (err or "Unknown syntax error"))
render_template(name, template, scope) render_template(name, template, scope, pkg)
end end
--- Render a template from a string. --- Render a template from a string.
-- @param template Template string -- @param template Template string
-- @param scope Scope to assign to template (optional) -- @param scope Scope to assign to template (optional)
function ctx.render_string(str, scope) function ctx.render_string(str, scope, pkg)
local template, _, err = tparser.parse_string(str) local template, _, err = tparser.parse_string(str)
assert(template, "Error while parsing template:\n" .. assert(template, "Error while parsing template:\n" ..
(err or "Unknown syntax error")) (err or "Unknown syntax error"))
render_template('(local)', template, scope) render_template('(local)', template, scope, pkg)
end
function ctx.setlanguage(lang)
lang = lang:gsub("_", "-")
if not lang then return false end
if lang ~= 'en' and not fs.access(i18ndir .. "gluon-web." .. lang .. ".lmo") then
return false
end
return tparser.load_catalog(lang, i18ndir)
end
-- Returns a translated string, or nil if none is found
function ctx._translate(key)
return (tparser.translate(key))
end
-- Returns a translated string, or the original string if none is found
function ctx.translate(key)
return tparser.translate(key) or key
end
function ctx.translatef(key, ...)
local t = ctx.translate(key)
return t:format(...)
end end
return ctx return ctx
......
...@@ -23,15 +23,12 @@ ...@@ -23,15 +23,12 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <dirent.h>
#include <fcntl.h> #include <fcntl.h>
#include <fnmatch.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <limits.h>
struct lmo_entry { struct lmo_entry {
...@@ -41,28 +38,6 @@ struct lmo_entry { ...@@ -41,28 +38,6 @@ struct lmo_entry {
uint32_t length; uint32_t length;
} __attribute__((packed)); } __attribute__((packed));
typedef struct lmo_entry lmo_entry_t;
struct lmo_archive {
size_t length;
const lmo_entry_t *index;
char *data;
const char *end;
struct lmo_archive *next;
};
typedef struct lmo_archive lmo_archive_t;
struct lmo_catalog {
char lang[6];
struct lmo_archive *archives;
struct lmo_catalog *next;
};
typedef struct lmo_catalog lmo_catalog_t;
static inline uint16_t get_le16(const void *data) { static inline uint16_t get_le16(const void *data) {
const uint8_t *d = data; const uint8_t *d = data;
...@@ -122,12 +97,13 @@ static uint32_t sfh_hash(const void *input, size_t len) ...@@ -122,12 +97,13 @@ static uint32_t sfh_hash(const void *input, size_t len)
return hash; return hash;
} }
static lmo_archive_t * lmo_open(const char *file) bool lmo_load(lmo_catalog_t *cat, const char *file)
{ {
int fd = -1; int fd = -1;
lmo_archive_t *ar = NULL;
struct stat s; struct stat s;
cat->data = MAP_FAILED;
fd = open(file, O_RDONLY|O_CLOEXEC); fd = open(file, O_RDONLY|O_CLOEXEC);
if (fd < 0) if (fd < 0)
goto err; goto err;
...@@ -135,110 +111,42 @@ static lmo_archive_t * lmo_open(const char *file) ...@@ -135,110 +111,42 @@ static lmo_archive_t * lmo_open(const char *file)
if (fstat(fd, &s)) if (fstat(fd, &s))
goto err; goto err;
if ((ar = calloc(1, sizeof(*ar))) != NULL) { cat->data = mmap(NULL, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
ar->data = mmap(NULL, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
close(fd); close(fd);
fd = -1; fd = -1;
if (ar->data == MAP_FAILED) if (cat->data == MAP_FAILED)
goto err; goto err;
ar->end = ar->data + s.st_size; cat->end = cat->data + s.st_size;
uint32_t idx_offset = get_be32(ar->end - sizeof(uint32_t)); uint32_t idx_offset = get_be32(cat->end - sizeof(uint32_t));
ar->index = (const lmo_entry_t *)(ar->data + idx_offset); cat->index = (const lmo_entry_t *)(cat->data + idx_offset);
if ((const char *)ar->index > (ar->end - sizeof(uint32_t))) if ((const char *)cat->index > (cat->end - sizeof(uint32_t)))
goto err; goto err;
ar->length = (ar->end - sizeof(uint32_t) - (const char *)ar->index) / sizeof(lmo_entry_t); cat->length = (cat->end - sizeof(uint32_t) - (const char *)cat->index) / sizeof(lmo_entry_t);
return ar; return true;
}
err: err:
if (fd >= 0) if (fd >= 0)
close(fd); close(fd);
if (ar != NULL) { if (cat->data != MAP_FAILED)
if ((ar->data != NULL) && (ar->data != MAP_FAILED)) munmap(cat->data, cat->end - cat->data);
munmap(ar->data, ar->end - ar->data);
free(ar);
}
return NULL;
}
static lmo_catalog_t *lmo_catalogs;
static lmo_catalog_t *lmo_active_catalog;
bool lmo_change_catalog(const char *lang)
{
lmo_catalog_t *cat;
for (cat = lmo_catalogs; cat; cat = cat->next) {
if (!strncmp(cat->lang, lang, sizeof(cat->lang))) {
lmo_active_catalog = cat;
return true;
}
}
return false; return false;
} }
bool lmo_load_catalog(const char *lang, const char *dir) void lmo_unload(lmo_catalog_t *cat)
{ {
DIR *dh = NULL; if (cat->data != MAP_FAILED)
char pattern[16]; munmap(cat->data, cat->end - cat->data);
char path[PATH_MAX];
struct dirent *de = NULL;
lmo_archive_t *ar = NULL;
lmo_catalog_t *cat = NULL;
if (lmo_change_catalog(lang))
return true;
if (!(dh = opendir(dir)))
goto err;
if (!(cat = calloc(1, sizeof(*cat))))
goto err;
snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
while ((de = readdir(dh)) != NULL) {
if (!fnmatch(pattern, de->d_name, 0)) {
snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
ar = lmo_open(path);
if (ar) {
ar->next = cat->archives;
cat->archives = ar;
}
} }
}
closedir(dh);
cat->next = lmo_catalogs;
lmo_catalogs = cat;
lmo_active_catalog = cat;
return true;
err:
if (dh)
closedir(dh);
free(cat);
return false;
}
static int lmo_compare_entry(const void *a, const void *b) static int lmo_compare_entry(const void *a, const void *b)
{ {
...@@ -253,34 +161,26 @@ static int lmo_compare_entry(const void *a, const void *b) ...@@ -253,34 +161,26 @@ static int lmo_compare_entry(const void *a, const void *b)
return 0; return 0;
} }
static const lmo_entry_t * lmo_find_entry(const lmo_archive_t *ar, uint32_t hash) static const lmo_entry_t * lmo_find_entry(const lmo_catalog_t *cat, uint32_t hash)
{ {
lmo_entry_t key; lmo_entry_t key;
key.key_id = htonl(hash); key.key_id = htonl(hash);
return bsearch(&key, ar->index, ar->length, sizeof(lmo_entry_t), lmo_compare_entry); return bsearch(&key, cat->index, cat->length, sizeof(lmo_entry_t), lmo_compare_entry);
} }
bool lmo_translate(const char *key, size_t keylen, char **out, size_t *outlen) bool lmo_translate(const lmo_catalog_t *cat, const char *key, size_t keylen, const char **out, size_t *outlen)
{ {
if (!lmo_active_catalog)
return false;
uint32_t hash = sfh_hash(key, keylen); uint32_t hash = sfh_hash(key, keylen);
const lmo_entry_t *e = lmo_find_entry(cat, hash);
for (const lmo_archive_t *ar = lmo_active_catalog->archives; ar; ar = ar->next) {
const lmo_entry_t *e = lmo_find_entry(ar, hash);
if (!e) if (!e)
continue; return false;
*out = ar->data + ntohl(e->offset); *out = cat->data + ntohl(e->offset);
*outlen = ntohl(e->length); *outlen = ntohl(e->length);
if (*out + *outlen > ar->end) if (*out + *outlen > cat->end)
continue; return false;
return true; return true;
} }
return false;
}
...@@ -24,7 +24,21 @@ ...@@ -24,7 +24,21 @@
#include <stddef.h> #include <stddef.h>
bool lmo_load_catalog(const char *lang, const char *dir); typedef struct lmo_entry lmo_entry_t;
bool lmo_translate(const char *key, size_t keylen, char **out, size_t *outlen);
struct lmo_catalog {
size_t length;
const lmo_entry_t *index;
char *data;
const char *end;
};
typedef struct lmo_catalog lmo_catalog_t;
bool lmo_load(lmo_catalog_t *cat, const char *file);
void lmo_unload(lmo_catalog_t *cat);
bool lmo_translate(const lmo_catalog_t *cat, const char *key, size_t keylen, const char **out, size_t *outlen);
#endif #endif
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
#include <string.h> #include <string.h>
#define TEMPLATE_LUALIB_META "gluon.web.template.parser" #define TEMPLATE_CATALOG "gluon.web.template.parser.catalog"
static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname) static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname)
...@@ -87,40 +87,64 @@ static int template_L_pcdata(lua_State *L) ...@@ -87,40 +87,64 @@ static int template_L_pcdata(lua_State *L)
return 1; return 1;
} }
static int template_L_load_catalog(lua_State *L) { static int template_L_load_catalog(lua_State *L)
const char *lang = luaL_optstring(L, 1, "en"); {
const char *dir = luaL_checkstring(L, 2); const char *file = luaL_checkstring(L, 1);
lua_pushboolean(L, lmo_load_catalog(lang, dir));
lmo_catalog_t *cat = lua_newuserdata(L, sizeof(*cat));
if (!lmo_load(cat, file)) {
lua_pop(L, 1);
return 0;
}
luaL_getmetatable(L, TEMPLATE_CATALOG);
lua_setmetatable(L, -2);
return 1; return 1;
} }
static int template_L_translate(lua_State *L) { static int template_catalog_call(lua_State *L)
size_t len; {
char *tr; size_t inlen, outlen;
size_t trlen; lmo_catalog_t *cat = luaL_checkudata(L, 1, TEMPLATE_CATALOG);
const char *key = luaL_checklstring(L, 1, &len); const char *in = luaL_checklstring(L, 2, &inlen), *out;
if (!lmo_translate(cat, in, inlen, &out, &outlen))
return 0;
if (lmo_translate(key, len, &tr, &trlen)) lua_pushlstring(L, out, outlen);
lua_pushlstring(L, tr, trlen);
else
lua_pushnil(L);
return 1; return 1;
} }
static int template_catalog_gc(lua_State *L)
{
lmo_catalog_t *cat = luaL_checkudata(L, 1, TEMPLATE_CATALOG);
lmo_unload(cat);
return 0;
}
/* module table */
static const luaL_reg R[] = { static const luaL_reg R[] = {
{ "parse", template_L_parse }, { "parse", template_L_parse },
{ "parse_string", template_L_parse_string }, { "parse_string", template_L_parse_string },
{ "pcdata", template_L_pcdata }, { "pcdata", template_L_pcdata },
{ "load_catalog", template_L_load_catalog }, { "load_catalog", template_L_load_catalog },
{ "translate", template_L_translate }, {}
};
static const luaL_reg template_catalog_methods[] = {
{ "__call", template_catalog_call },
{ "__gc", template_catalog_gc },
{} {}
}; };
__attribute__ ((visibility("default"))) __attribute__ ((visibility("default")))
LUALIB_API int luaopen_gluon_web_template_parser(lua_State *L) { LUALIB_API int luaopen_gluon_web_template_parser(lua_State *L) {
luaL_register(L, TEMPLATE_LUALIB_META, R); luaL_register(L, "gluon.web.template.parser", R);
luaL_newmetatable(L, TEMPLATE_CATALOG);
luaL_register(L, NULL, template_catalog_methods);
lua_pop(L, 1);
return 1; return 1;
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment