gluon-web: add i18n package namespaces

This commit is contained in:
Matthias Schiffer 2018-02-23 06:26:10 +01:00
parent 1a426c3bb9
commit 557565e189
No known key found for this signature in database
GPG Key ID: 16EF3F64CB201D9C
30 changed files with 377 additions and 315 deletions

View File

@ -1,14 +1,16 @@
return function(form, uci) return function(form, uci)
local pkg_i18n = i18n 'gluon-config-mode-autoupdater'
local enabled = uci:get_bool("autoupdater", "settings", "enabled") local enabled = uci:get_bool("autoupdater", "settings", "enabled")
if enabled then if enabled then
form:section( form:section(
Section, nil, Section, nil,
translate('This node will automatically update its firmware when a new version is available.') pkg_i18n.translate('This node will automatically update its firmware when a new version is available.')
) )
else else
form:section( form:section(
Section, nil, Section, nil,
translate('Automatic updates are disabled. They can be enabled in <em>Advanced settings</em>.') pkg_i18n.translate('Automatic updates are disabled. They can be enabled in <em>Advanced settings</em>.')
) )
end end
end end

View File

@ -1,16 +1,18 @@
return function(form, uci) return function(form, uci)
local pkg_i18n = i18n 'gluon-config-mode-contact-info'
local site = require 'gluon.site' local site = require 'gluon.site'
local owner = uci:get_first("gluon-node-info", "owner") local owner = uci:get_first("gluon-node-info", "owner")
local s = form:section(Section, nil, translate( local s = form:section(Section, nil, pkg_i18n.translate(
'Please provide your contact information here to ' 'Please provide your contact information here to '
.. 'allow others to contact you. Note that ' .. 'allow others to contact you. Note that '
.. 'this information will be visible <em>publicly</em> ' .. 'this information will be visible <em>publicly</em> '
.. 'on the internet together with your node\'s coordinates.' .. 'on the internet together with your node\'s coordinates.'
)) ))
local o = s:option(Value, "contact", translate("Contact info"), translate("e.g. E-mail or phone number")) local o = s:option(Value, "contact", pkg_i18n.translate("Contact info"), pkg_i18n.translate("e.g. E-mail or phone number"))
o.default = uci:get("gluon-node-info", owner, "contact") o.default = uci:get("gluon-node-info", owner, "contact")
o.optional = not site.config_mode.owner.obligatory(false) o.optional = not site.config_mode.owner.obligatory(false)
-- without a minimal length, an empty string will be accepted even with "optional = false" -- without a minimal length, an empty string will be accepted even with "optional = false"

View File

@ -1,7 +1,9 @@
<%- <%-
local site_i18n = i18n 'gluon-site'
local sysconfig = require 'gluon.sysconfig' local sysconfig = require 'gluon.sysconfig'
local msg = _translate('gluon-config-mode:welcome') local msg = site_i18n._translate('gluon-config-mode:welcome')
if not msg then return end if not msg then return end
-%> -%>
<p> <p>

View File

@ -1,3 +1,5 @@
local site_i18n = i18n 'gluon-site'
local site = require 'gluon.site' local site = require 'gluon.site'
local sysconfig = require 'gluon.sysconfig' local sysconfig = require 'gluon.sysconfig'
local pretty_hostname = require 'pretty_hostname' local pretty_hostname = require 'pretty_hostname'
@ -7,7 +9,7 @@ local uci = require("simple-uci").cursor()
local hostname = pretty_hostname.get(uci) local hostname = pretty_hostname.get(uci)
local contact = uci:get_first('gluon-node-info', 'owner', 'contact') local contact = uci:get_first('gluon-node-info', 'owner', 'contact')
local msg = _translate('gluon-config-mode:reboot') local msg = site_i18n._translate('gluon-config-mode:reboot')
if not msg then return end if not msg then return end
renderer.render_string(msg, { renderer.render_string(msg, {

View File

@ -1,2 +1,4 @@
package 'gluon-config-mode-core'
entry({}, alias("wizard")) entry({}, alias("wizard"))
entry({"wizard"}, model("gluon-config-mode/wizard"), _("Wizard"), 5) entry({"wizard"}, model("gluon-config-mode/wizard"), _("Wizard"), 5)

View File

@ -26,6 +26,7 @@ f.reset = false
local s = f:section(Section) local s = f:section(Section)
s.template = "gluon/config-mode/welcome" s.template = "gluon/config-mode/welcome"
s.package = "gluon-config-mode-core"
local commit = {'gluon-setup-mode'} local commit = {'gluon-setup-mode'}
local run = {} local run = {}
@ -57,6 +58,7 @@ function f:write()
end end
f.template = "gluon/config-mode/reboot" f.template = "gluon/config-mode/reboot"
f.package = "gluon-config-mode-core"
f.hidenav = true f.hidenav = true
if nixio.fork() == 0 then if nixio.fork() == 0 then

View File

@ -1,4 +1,6 @@
return function(form, uci) return function(form, uci)
local site_i18n = i18n 'gluon-site'
local fs = require 'nixio.fs' local fs = require 'nixio.fs'
local json = require 'jsonc' local json = require 'jsonc'
local site = require 'gluon.site' local site = require 'gluon.site'
@ -24,8 +26,8 @@ return function(form, uci)
return list return list
end end
local s = form:section(Section, nil, translate('gluon-config-mode:domain-select')) local s = form:section(Section, nil, site_i18n.translate('gluon-config-mode:domain-select'))
local o = s:option(ListValue, 'domain', translate('gluon-config-mode:domain')) local o = s:option(ListValue, 'domain', site_i18n.translate('gluon-config-mode:domain'))
if configured then if configured then
o.default = selected_domain o.default = selected_domain

View File

@ -1,4 +1,7 @@
return function(form, uci) return function(form, uci)
local pkg_i18n = i18n 'gluon-config-mode-geo-location'
local site_i18n = i18n 'gluon-site'
local site = require 'gluon.site' local site = require 'gluon.site'
local location = uci:get_first("gluon-node-info", "location") local location = uci:get_first("gluon-node-info", "location")
@ -11,25 +14,25 @@ return function(form, uci)
return uci:get_bool("gluon-node-info", location, "altitude") return uci:get_bool("gluon-node-info", location, "altitude")
end end
local text = translate( local text = pkg_i18n.translate(
'If you want the location of your node to ' .. 'If you want the location of your node to ' ..
'be displayed on the map, you can enter its coordinates here.' 'be displayed on the map, you can enter its coordinates here.'
) )
if show_altitude() then if show_altitude() then
text = text .. ' ' .. translate("gluon-config-mode:altitude-help") text = text .. ' ' .. site_i18n.translate("gluon-config-mode:altitude-help")
end end
local s = form:section(Section, nil, text) local s = form:section(Section, nil, text)
local o local o
local share_location = s:option(Flag, "location", translate("Show node on the map")) local share_location = s:option(Flag, "location", pkg_i18n.translate("Show node on the map"))
share_location.default = uci:get_bool("gluon-node-info", location, "share_location") share_location.default = uci:get_bool("gluon-node-info", location, "share_location")
function share_location:write(data) function share_location:write(data)
uci:set("gluon-node-info", location, "share_location", data) uci:set("gluon-node-info", location, "share_location", data)
end end
o = s:option(Value, "latitude", translate("Latitude"), translatef("e.g. %s", "53.873621")) o = s:option(Value, "latitude", pkg_i18n.translate("Latitude"), pkg_i18n.translatef("e.g. %s", "53.873621"))
o.default = uci:get("gluon-node-info", location, "latitude") o.default = uci:get("gluon-node-info", location, "latitude")
o:depends(share_location, true) o:depends(share_location, true)
o.datatype = "float" o.datatype = "float"
@ -37,7 +40,7 @@ return function(form, uci)
uci:set("gluon-node-info", location, "latitude", data) uci:set("gluon-node-info", location, "latitude", data)
end end
o = s:option(Value, "longitude", translate("Longitude"), translatef("e.g. %s", "10.689901")) o = s:option(Value, "longitude", pkg_i18n.translate("Longitude"), pkg_i18n.translatef("e.g. %s", "10.689901"))
o.default = uci:get("gluon-node-info", location, "longitude") o.default = uci:get("gluon-node-info", location, "longitude")
o:depends(share_location, true) o:depends(share_location, true)
o.datatype = "float" o.datatype = "float"
@ -46,7 +49,7 @@ return function(form, uci)
end end
if show_altitude() then if show_altitude() then
o = s:option(Value, "altitude", translate("gluon-config-mode:altitude-label"), translatef("e.g. %s", "11.51")) o = s:option(Value, "altitude", site_i18n.translate("gluon-config-mode:altitude-label"), pkg_i18n.translatef("e.g. %s", "11.51"))
o.default = uci:get("gluon-node-info", location, "altitude") o.default = uci:get("gluon-node-info", location, "altitude")
o:depends(share_location, true) o:depends(share_location, true)
o.datatype = "float" o.datatype = "float"

View File

@ -1,8 +1,10 @@
return function(form, uci) return function(form, uci)
local pkg_i18n = i18n 'gluon-config-mode-hostname'
local pretty_hostname = require "pretty_hostname" local pretty_hostname = require "pretty_hostname"
local s = form:section(Section) local s = form:section(Section)
local o = s:option(Value, "hostname", translate("Node name")) local o = s:option(Value, "hostname", pkg_i18n.translate("Node name"))
o.default = pretty_hostname.get(uci) o.default = pretty_hostname.get(uci)
function o:write(data) function o:write(data)

View File

@ -1,3 +1,5 @@
local site_i18n = i18n 'gluon-site'
local uci = require("simple-uci").cursor() local uci = require("simple-uci").cursor()
local lutil = require "gluon.web.util" local lutil = require "gluon.web.util"
local fs = require "nixio.fs" local fs = require "nixio.fs"
@ -23,15 +25,15 @@ local msg
if has_tunneldigger then if has_tunneldigger then
local tunneldigger_enabled = uci:get_bool("tunneldigger", "mesh_vpn", "enabled") local tunneldigger_enabled = uci:get_bool("tunneldigger", "mesh_vpn", "enabled")
if not tunneldigger_enabled then if not tunneldigger_enabled then
msg = _translate('gluon-config-mode:novpn') msg = site_i18n._translate('gluon-config-mode:novpn')
end end
elseif has_fastd then elseif has_fastd then
local fastd_enabled = uci:get_bool("fastd", "mesh_vpn", "enabled") local fastd_enabled = uci:get_bool("fastd", "mesh_vpn", "enabled")
if fastd_enabled then if fastd_enabled then
pubkey = util.trim(lutil.exec("/etc/init.d/fastd show_key mesh_vpn")) pubkey = util.trim(lutil.exec("/etc/init.d/fastd show_key mesh_vpn"))
msg = _translate('gluon-config-mode:pubkey') msg = site_i18n._translate('gluon-config-mode:pubkey')
else else
msg = _translate('gluon-config-mode:novpn') msg = site_i18n._translate('gluon-config-mode:novpn')
end end
end end

View File

@ -8,7 +8,9 @@ return function(form, uci)
return return
end end
local msg = translate( local pkg_i18n = i18n 'gluon-config-mode-mesh-vpn'
local msg = pkg_i18n.translate(
'Your internet connection can be used to establish a ' .. 'Your internet connection can be used to establish a ' ..
'VPN connection with other nodes. ' .. 'VPN connection with other nodes. ' ..
'Enable this option if there are no other nodes reachable ' .. 'Enable this option if there are no other nodes reachable ' ..
@ -21,7 +23,7 @@ return function(form, uci)
local o local o
local meshvpn = s:option(Flag, "meshvpn", translate("Use internet connection (mesh VPN)")) local meshvpn = s:option(Flag, "meshvpn", pkg_i18n.translate("Use internet connection (mesh VPN)"))
meshvpn.default = uci:get_bool("fastd", "mesh_vpn", "enabled") or uci:get_bool("tunneldigger", "mesh_vpn", "enabled") meshvpn.default = uci:get_bool("fastd", "mesh_vpn", "enabled") or uci:get_bool("tunneldigger", "mesh_vpn", "enabled")
function meshvpn:write(data) function meshvpn:write(data)
if has_fastd then if has_fastd then
@ -32,7 +34,7 @@ return function(form, uci)
end end
end end
local limit = s:option(Flag, "limit_enabled", translate("Limit bandwidth")) local limit = s:option(Flag, "limit_enabled", pkg_i18n.translate("Limit bandwidth"))
limit:depends(meshvpn, true) limit:depends(meshvpn, true)
limit.default = uci:get_bool("simple-tc", "mesh_vpn", "enabled") limit.default = uci:get_bool("simple-tc", "mesh_vpn", "enabled")
function limit:write(data) function limit:write(data)
@ -41,7 +43,7 @@ return function(form, uci)
uci:set("simple-tc", "mesh_vpn", "ifname", "mesh-vpn") uci:set("simple-tc", "mesh_vpn", "ifname", "mesh-vpn")
end end
o = s:option(Value, "limit_ingress", translate("Downstream (kbit/s)")) o = s:option(Value, "limit_ingress", pkg_i18n.translate("Downstream (kbit/s)"))
o:depends(limit, true) o:depends(limit, true)
o.default = uci:get("simple-tc", "mesh_vpn", "limit_ingress") o.default = uci:get("simple-tc", "mesh_vpn", "limit_ingress")
o.datatype = "uinteger" o.datatype = "uinteger"
@ -49,7 +51,7 @@ return function(form, uci)
uci:set("simple-tc", "mesh_vpn", "limit_ingress", data) uci:set("simple-tc", "mesh_vpn", "limit_ingress", data)
end end
o = s:option(Value, "limit_egress", translate("Upstream (kbit/s)")) o = s:option(Value, "limit_egress", pkg_i18n.translate("Upstream (kbit/s)"))
o:depends(limit, true) o:depends(limit, true)
o.default = uci:get("simple-tc", "mesh_vpn", "limit_egress") o.default = uci:get("simple-tc", "mesh_vpn", "limit_egress")
o.datatype = "uinteger" o.datatype = "uinteger"

View File

@ -1,3 +1,6 @@
package 'gluon-web-admin'
local root = node() local root = node()
if not root.target then if not root.target then
root.target = alias("admin") root.target = alias("admin")

View File

@ -9,6 +9,9 @@ You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
]]-- ]]--
package 'gluon-web-admin'
local fs = require 'nixio.fs' local fs = require 'nixio.fs'
local tmpfile = "/tmp/firmware.img" local tmpfile = "/tmp/firmware.img"
@ -106,17 +109,23 @@ local function action_upgrade(http, renderer)
renderer.render("layout", { renderer.render("layout", {
content = "admin/upgrade", content = "admin/upgrade",
bad_image = has_image and not has_support, env = {
bad_image = has_image and not has_support,
},
pkg = 'gluon-web-admin',
}) })
-- Step 2: present uploaded file, show checksum, confirmation -- Step 2: present uploaded file, show checksum, confirmation
elseif step == 2 then elseif step == 2 then
renderer.render("layout", { renderer.render("layout", {
content = "admin/upgrade_confirm", content = "admin/upgrade_confirm",
checksum = image_checksum(tmpfile), env = {
filesize = fs.stat(tmpfile).size, checksum = image_checksum(tmpfile),
flashsize = storage_size(), filesize = fs.stat(tmpfile).size,
keepconfig = (http:formvalue("keepcfg") == "1"), flashsize = storage_size(),
keepconfig = (http:formvalue("keepcfg") == "1"),
},
pkg = 'gluon-web-admin',
}) })
elseif step == 3 then elseif step == 3 then
if http:formvalue("keepcfg") == "1" then if http:formvalue("keepcfg") == "1" then
@ -127,6 +136,7 @@ local function action_upgrade(http, renderer)
renderer.render("layout", { renderer.render("layout", {
content = "admin/upgrade_reboot", content = "admin/upgrade_reboot",
hidenav = true, hidenav = true,
pkg = 'gluon-web-admin',
}) })
end end
end end

View File

@ -1 +1,3 @@
package 'gluon-web-autoupdater'
entry({"admin", "autoupdater"}, model("admin/autoupdater"), _("Automatic updates"), 80) entry({"admin", "autoupdater"}, model("admin/autoupdater"), _("Automatic updates"), 80)

View File

@ -1 +1,3 @@
package 'gluon-web-logging'
entry({"admin", "logging"}, model("admin/logging"), _("Logging"), 85) entry({"admin", "logging"}, model("admin/logging"), _("Logging"), 85)

View File

@ -26,14 +26,14 @@ define Build/Configure
endef endef
define Build/Compile define Build/Compile
$(call GluonBuildI18N,gluon-mesh-vpn-fastd,i18n) $(call GluonBuildI18N,gluon-web-mesh-vpn-fastd,i18n)
$(call GluonSrcDiet,./luasrc,$(PKG_BUILD_DIR)/luadest/) $(call GluonSrcDiet,./luasrc,$(PKG_BUILD_DIR)/luadest/)
endef endef
define Package/gluon-web-mesh-vpn-fastd/install define Package/gluon-web-mesh-vpn-fastd/install
$(CP) ./files/* $(1)/ $(CP) ./files/* $(1)/
$(CP) $(PKG_BUILD_DIR)/luadest/* $(1)/ $(CP) $(PKG_BUILD_DIR)/luadest/* $(1)/
$(call GluonInstallI18N,gluon-mesh-vpn-fastd,$(1)) $(call GluonInstallI18N,gluon-web-mesh-vpn-fastd,$(1))
endef endef
define Package/gluon-web-mesh-vpn-fastd/postinst define Package/gluon-web-mesh-vpn-fastd/postinst

View File

@ -1 +1,3 @@
package 'gluon-web-mesh-vpn-fastd'
entry({"admin", "mesh_vpn_fastd"}, model("admin/mesh_vpn_fastd"), _("Mesh VPN"), 50) entry({"admin", "mesh_vpn_fastd"}, model("admin/mesh_vpn_fastd"), _("Mesh VPN"), 50)

View File

@ -6,6 +6,7 @@ local f = Form(translate('Mesh VPN'))
local s = f:section(Section) local s = f:section(Section)
local mode = s:option(Value, 'mode') local mode = s:option(Value, 'mode')
mode.package = "gluon-web-mesh-vpn-fastd"
mode.template = "gluon/model/mesh-vpn-fastd" mode.template = "gluon/model/mesh-vpn-fastd"
local methods = uci:get('fastd', 'mesh_vpn', 'method') local methods = uci:get('fastd', 'mesh_vpn', 'method')

View File

@ -1 +1,3 @@
package 'gluon-web-network'
entry({"admin", "network"}, model("admin/network"), _("Network"), 40) entry({"admin", "network"}, model("admin/network"), _("Network"), 40)

View File

@ -1 +1,3 @@
package 'gluon-web-node-role'
entry({"admin", "noderole"}, model("admin/noderole"), "Node role", 60) entry({"admin", "noderole"}, model("admin/noderole"), "Node role", 60)

View File

@ -1 +1,3 @@
package 'gluon-web-private-wifi'
entry({"admin", "privatewifi"}, model("admin/privatewifi"), _("Private WLAN"), 30) entry({"admin", "privatewifi"}, model("admin/privatewifi"), _("Private WLAN"), 30)

View File

@ -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
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
<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
<% 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
</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
%> %>

View File

@ -1 +1,3 @@
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)

View File

@ -1,6 +1,6 @@
-- 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)
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)
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,68 +143,6 @@ 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 subdisp = setmetatable({
node = function(...)
return _node({...})
end,
entry = function(path, target, title, order)
local c = _node(path, true)
c.target = target
c.title = title
c.order = order
return c
end,
alias = function(...)
local req = {...}
return function()
http:redirect(build_url(http, req))
end
end,
call = function(func, ...)
local args = {...}
return function()
func(http, renderer, unpack(args))
end
end,
template = function(view)
return function()
renderer.render("layout", {content = view})
end
end,
model = function(name)
return function()
local hidenav = false
local model = require "gluon.web.model"
local maps = model.load(name, renderer)
for _, map in ipairs(maps) do
map:parse(http)
end
for _, map in ipairs(maps) do
map:handle()
hidenav = hidenav or map.hidenav
end
renderer.render("layout", {
content = "model/wrapper",
maps = maps,
hidenav = hidenav,
})
end
end,
_ = function(text)
return text
end,
}, { __index = _G })
local function createtree() local function createtree()
local base = util.libpath() .. "/controller/" local base = util.libpath() .. "/controller/"
@ -216,6 +150,80 @@ function dispatch(http, request)
local function load_ctl(path) local function load_ctl(path)
local ctl = assert(loadfile(path)) local ctl = assert(loadfile(path))
local _pkg
local subdisp = setmetatable({
package = function(name)
_pkg = name
end,
node = function(...)
return _node({...})
end,
entry = function(path, target, title, order)
local c = _node(path, true)
c.target = target
c.title = title
c.order = order
c.pkg = _pkg
return c
end,
alias = function(...)
local req = {...}
return function()
http:redirect(build_url(http, req))
end
end,
call = function(func, ...)
local args = {...}
return function()
func(http, renderer, unpack(args))
end
end,
template = function(view)
local pkg = _pkg
return function()
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, pkg)
for _, map in ipairs(maps) do
map:parse(http)
end
for _, map in ipairs(maps) do
map:handle()
hidenav = hidenav or map.hidenav
end
renderer.render("layout", {
content = "model/wrapper",
env = {
maps = maps,
},
hidenav = hidenav,
})
end
end,
_ = function(text)
return text
end,
}, { __index = _G })
local env = setmetatable({}, { __index = subdisp }) local env = setmetatable({}, { __index = subdisp })
setfenv(ctl, env) setfenv(ctl, env)
@ -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", {
"No page is registered at '/" .. table.concat(request, "/") .. "'.\n" .. content = "error404",
"If this URL belongs to an extension, make sure it is properly installed.\n" 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",
},
pkg = 'gluon-web',
}) })
return return
end end
@ -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", {
"Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" .. content = "error500",
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)") 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)"),
},
pkg = 'gluon-web',
}) })
end end
end end

View File

@ -0,0 +1,54 @@
-- 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

View File

@ -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
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)
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)
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)
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

View File

@ -1,39 +1,59 @@
-- 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)
--- 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)
"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

View File

@ -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 {
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)
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,111 +111,43 @@ 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)
{ {
const lmo_entry_t *ea = a, *eb = b; const lmo_entry_t *ea = a, *eb = 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) uint32_t hash = sfh_hash(key, keylen);
const lmo_entry_t *e = lmo_find_entry(cat, hash);
if (!e)
return false; return false;
uint32_t hash = sfh_hash(key, keylen); *out = cat->data + ntohl(e->offset);
*outlen = ntohl(e->length);
for (const lmo_archive_t *ar = lmo_active_catalog->archives; ar; ar = ar->next) { if (*out + *outlen > cat->end)
const lmo_entry_t *e = lmo_find_entry(ar, hash); return false;
if (!e)
continue;
*out = ar->data + ntohl(e->offset); return true;
*outlen = ntohl(e->length);
if (*out + *outlen > ar->end)
continue;
return true;
}
return false;
} }

View File

@ -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

View File

@ -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)
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));
return 1;
}
static int template_L_translate(lua_State *L) { lmo_catalog_t *cat = lua_newuserdata(L, sizeof(*cat));
size_t len; if (!lmo_load(cat, file)) {
char *tr; lua_pop(L, 1);
size_t trlen; return 0;
const char *key = luaL_checklstring(L, 1, &len); }
if (lmo_translate(key, len, &tr, &trlen)) luaL_getmetatable(L, TEMPLATE_CATALOG);
lua_pushlstring(L, tr, trlen); lua_setmetatable(L, -2);
else
lua_pushnil(L);
return 1; return 1;
} }
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;
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[] = { 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;
} }