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)
......@@ -33,6 +33,10 @@ You may obtain a copy of the License at
return r
end
local function title(node)
return i18n(node.pkg).translate(node.title)
end
local function subtree(prefix, node, name, ...)
if not node then return end
......@@ -48,7 +52,7 @@ You may obtain a copy of the License at
local active = (v == name)
%>
<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>
<%
end
......@@ -71,7 +75,7 @@ You may obtain a copy of the License at
<head>
<meta charset="UTF-8" />
<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>
<body>
......@@ -88,7 +92,7 @@ You may obtain a copy of the License at
<% if #categories > 1 and not hidenav then %>
<ul id="topmenu">
<% 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 %>
</ul>
<% end %>
......@@ -110,9 +114,9 @@ You may obtain a copy of the License at
</noscript>
<%
ok, err = pcall(include, content)
ok, err = pcall(renderer.render, content, env, pkg)
if not ok then
renderer.render('error500', {message = err})
renderer.render('error500', {message = err}, 'gluon-web')
end
%>
......
package 'gluon-web-wifi-config'
entry({"admin", "wifi-config"}, model("admin/wifi-config"), _("WLAN"), 20)
-- Copyright 2008 Steven Barth <steven@midlink.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.
local fs = require "nixio.fs"
......@@ -77,7 +77,7 @@ local function set_language(renderer, accept)
end
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)
if lang == '*' then
......@@ -93,11 +93,7 @@ local function set_language(renderer, accept)
return (weights[a] or 0) > (weights[b] or 0)
end)
for _, lang in ipairs(langs) do
if renderer.setlanguage(lang) then
return
end
end
renderer.set_language(langs)
end
......@@ -147,7 +143,20 @@ function dispatch(http, request)
url = function(path) return build_url(http, path) end,
}, { __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({
package = function(name)
_pkg = name
end,
node = function(...)
return _node({...})
end,
......@@ -158,6 +167,7 @@ function dispatch(http, request)
c.target = target
c.title = title
c.order = order
c.pkg = _pkg
return c
end,
......@@ -177,17 +187,19 @@ function dispatch(http, request)
end,
template = function(view)
local pkg = _pkg
return function()
renderer.render("layout", {content = view})
renderer.render("layout", {content = view, pkg = pkg})
end
end,
model = function(name)
local pkg = _pkg
return function()
local hidenav = false
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
map:parse(http)
......@@ -199,7 +211,9 @@ function dispatch(http, request)
renderer.render("layout", {
content = "model/wrapper",
env = {
maps = maps,
},
hidenav = hidenav,
})
end
......@@ -210,12 +224,6 @@ function dispatch(http, request)
end,
}, { __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 })
setfenv(ctl, env)
......@@ -239,9 +247,14 @@ function dispatch(http, request)
if not node or not node.target then
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" ..
"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
end
......@@ -251,9 +264,14 @@ function dispatch(http, request)
local ok, err = pcall(node.target)
if not ok then
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" ..
"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
-- 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 @@
module("gluon.web.model", package.seeall)
local util = require("gluon.web.util")
local util = require "gluon.web.util"
local fs = require("nixio.fs")
local datatypes = require("gluon.web.model.datatypes")
local dispatcher = require("gluon.web.dispatcher")
local fs = require "nixio.fs"
local datatypes = require "gluon.web.model.datatypes"
local dispatcher = require "gluon.web.dispatcher"
local class = util.class
local instanceof = util.instanceof
......@@ -17,7 +17,7 @@ FORM_VALID = 1
FORM_INVALID = -1
-- 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/"
if not fs.access(modeldir..name..".lua") then
......@@ -26,14 +26,16 @@ function load(name, renderer)
local func = assert(loadfile(modeldir..name..".lua"))
local env = {
translate=renderer.translate,
translatef=renderer.translatef,
}
local i18n = setmetatable({
i18n = renderer.i18n
}, {
__index = renderer.i18n(pkg)
})
setfenv(func, setmetatable(env, {__index =
setfenv(func, setmetatable({}, {__index =
function(tbl, key)
return _M[key] or _G[key]
return _M[key] or i18n[key] or _G[key]
end
}))
......@@ -85,6 +87,7 @@ function Node:__init__(title, description, name)
self.name = name
self.index = nil
self.parent = nil
self.package = 'gluon-web'
end
function Node:append(obj)
......@@ -116,7 +119,7 @@ function Node:render(renderer, scope)
id = self:id(),
scope = scope,
}, {__index = scope})
renderer.render(self.template, env)
renderer.render(self.template, env, self.package)
end
end
......
-- 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.
local tparser = require "gluon.web.template.parser"
local i18n = require "gluon.web.i18n"
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"
local viewdir = util.libpath() .. "/view/"
local i18ndir = util.libpath() .. "/i18n/"
function renderer(env)
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 {}
local t = ctx.i18n(pkg)
local locals = {
renderer = ctx,
translate = ctx.translate,
translatef = ctx.translatef,
_translate = ctx._translate,
i18n = ctx.i18n,
translate = t.translate,
translatef = t.translatef,
_translate = t._translate,
include = function(name)
ctx.render(name, scope)
ctx.render(name, scope, pkg)
end,
}
setfenv(template, setmetatable({}, {
__index = function(tbl, key)
return scope[key] or env[key] or locals[key]
return scope[key] or locals[key] or env[key]
end
}))
......@@ -46,7 +66,7 @@ function renderer(env)
--- Render a certain template.
-- @param name Template name
-- @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 template, _, err = tparser.parse(sourcefile)
......@@ -54,45 +74,19 @@ function renderer(env)
"Error while parsing template '" .. sourcefile .. "':\n" ..
(err or "Unknown syntax error"))
render_template(name, template, scope)
render_template(name, template, scope, pkg)
end
--- Render a template from a string.
-- @param template Template string
-- @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)
assert(template, "Error while parsing template:\n" ..
(err or "Unknown syntax error"))
render_template('(local)', template, scope)
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(...)
render_template('(local)', template, scope, pkg)
end
return ctx
......
......@@ -23,15 +23,12 @@
#include <sys/mman.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
struct lmo_entry {
......@@ -41,28 +38,6 @@ struct lmo_entry {
uint32_t length;
} __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) {
const uint8_t *d = data;
......@@ -122,12 +97,13 @@ static uint32_t sfh_hash(const void *input, size_t len)
return hash;
}
static lmo_archive_t * lmo_open(const char *file)
bool lmo_load(lmo_catalog_t *cat, const char *file)
{
int fd = -1;
lmo_archive_t *ar = NULL;
struct stat s;
cat->data = MAP_FAILED;
fd = open(file, O_RDONLY|O_CLOEXEC);
if (fd < 0)
goto err;
......@@ -135,110 +111,42 @@ static lmo_archive_t * lmo_open(const char *file)
if (fstat(fd, &s))
goto err;
if ((ar = calloc(1, sizeof(*ar))) != NULL) {
ar->data = mmap(NULL, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
cat->data = mmap(NULL, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
close(fd);
fd = -1;
if (ar->data == MAP_FAILED)
if (cat->data == MAP_FAILED)
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));
ar->index = (const lmo_entry_t *)(ar->data + idx_offset);
uint32_t idx_offset = get_be32(cat->end - sizeof(uint32_t));
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;
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:
if (fd >= 0)
close(fd);
if (ar != NULL) {
if ((ar->data != NULL) && (ar->data != MAP_FAILED))
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;
}
}
if (cat->data != MAP_FAILED)
munmap(cat->data, cat->end - cat->data);
return false;
}
bool lmo_load_catalog(const char *lang, const char *dir)
void lmo_unload(lmo_catalog_t *cat)
{
DIR *dh = NULL;
char pattern[16];
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;
}
if (cat->data != MAP_FAILED)
munmap(cat->data, cat->end - cat->data);
}
}
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)
{
......@@ -253,34 +161,26 @@ static int lmo_compare_entry(const void *a, const void *b)
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;
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);
for (const lmo_archive_t *ar = lmo_active_catalog->archives; ar; ar = ar->next) {
const lmo_entry_t *e = lmo_find_entry(ar, hash);
const lmo_entry_t *e = lmo_find_entry(cat, hash);
if (!e)
continue;
return false;
*out = ar->data + ntohl(e->offset);
*out = cat->data + ntohl(e->offset);
*outlen = ntohl(e->length);
if (*out + *outlen > ar->end)
continue;
if (*out + *outlen > cat->end)
return false;
return true;
}
return false;
}
......@@ -24,7 +24,21 @@
#include <stddef.h>
bool lmo_load_catalog(const char *lang, const char *dir);
bool lmo_translate(const char *key, size_t keylen, char **out, size_t *outlen);
typedef struct lmo_entry lmo_entry_t;
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
......@@ -29,7 +29,7 @@
#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)
......@@ -87,40 +87,64 @@ static int template_L_pcdata(lua_State *L)
return 1;
}
static int template_L_load_catalog(lua_State *L) {
const char *lang = luaL_optstring(L, 1, "en");
const char *dir = luaL_checkstring(L, 2);
lua_pushboolean(L, lmo_load_catalog(lang, dir));
static int template_L_load_catalog(lua_State *L)
{
const char *file = luaL_checkstring(L, 1);
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;
}
static int template_L_translate(lua_State *L) {
size_t len;
char *tr;
size_t trlen;
const char *key = luaL_checklstring(L, 1, &len);
static int template_catalog_call(lua_State *L)
{
size_t inlen, outlen;
lmo_catalog_t *cat = luaL_checkudata(L, 1, TEMPLATE_CATALOG);
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, tr, trlen);
else
lua_pushnil(L);
lua_pushlstring(L, out, outlen);
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[] = {
{ "parse", template_L_parse },
{ "parse_string", template_L_parse_string },
{ "pcdata", template_L_pcdata },
{ "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")))
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;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment