docs: drop older docs
authorPaul Donald <redacted>
Thu, 12 Feb 2026 17:43:09 +0000 (18:43 +0100)
committerPaul Donald <redacted>
Sun, 15 Feb 2026 18:51:08 +0000 (19:51 +0100)
Signed-off-by: Paul Donald <redacted>
docs/CBI.md [deleted file]
docs/LuCI-0.10.md [deleted file]
docs/ModulesHowTo.md [deleted file]
docs/Templates.md [deleted file]
docs/i18n.md [deleted file]

diff --git a/docs/CBI.md b/docs/CBI.md
deleted file mode 100644 (file)
index 056386f..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-# Writing LuCI CBI models
-
-See [online wiki](https://github.com/openwrt/luci/wiki/CBI) for latest version.
-
-CBI models are Lua files describing the structure of an UCI config file and the resulting HTML form to be evaluated by the CBI parser.<br />
-All CBI model files must return an object of type `luci.cbi.Map`.<br />
-For a commented example of a CBI model, see the [Writing Modules tutorial](./ModulesHowTo.md).
-
-The scope of a CBI model file is automatically extended by the contents of the module `luci.cbi` and the `translate` function from `luci.i18n`.
-
-This Reference covers **the basics** of the CBI system.
-
-
-## class Map (config, title, description)
-This is the root object of the model.
-
-* `config:` configuration filename to be mapped, see [UCI documentation](https://openwrt.org/docs/guide-user/base-system/uci) and the files in `/etc/config`
-* `title:` title shown in the UI
-* `description:` description shown in the UI
-
-#### function :section (sectionclass, ...)
-Creates a new section
-* `sectionclass`: a class object of the section
-* _additional parameters passed to the constructor of the section class_
-
-----
-
-## class NamedSection (name, type, title, description)
-An object describing an UCI section selected by the name.<br />
-To instantiate use: `Map:section(NamedSection, "name", "type", "title", "description")`
-
-* `name:` UCI section name
-* `type:` UCI section type
-* `title:` The title shown in the UI
-* `description:` description shown in the UI
-
-#### function :option(optionclass, ...)
-Creates a new option
-* `optionclass:` a class object of the section
-* _additional parameters passed to the constructor of the option class_
-
-#### property .addremove = false
-Allows the user to remove and recreate the configuration section.
-
-#### property .dynamic = false
-Marks this section as dynamic.
-Dynamic sections can contain an undefinded number of completely userdefined options.
-
-#### property .optional = true
-Parse optional options
-
-----
-
-## class TypedSection (type, title, description)
-An object describing a group of UCI sections selected by their type.<br />
-To instantiate use: `Map:section(TypedSection, "type", "title", "description")`
-* `type:` UCI section type
-* `title:` The title shown in the UI
-* `description:` description shown in the UI
-
-#### function :option(optionclass, ...)
-Creates a new option
-* `optionclass:` a class object of the section
-* _additional parameters passed to the constructor of the option class_
-
-#### function :depends(key, value)
-Only show this option field if another option `key` is set to `value` in the same section.<br />
-If you call this function several times the dependencies will be linked with **"or"**
-
-#### function .filter(self, section) -abstract-
-You can override this function to filter certain sections that will not be parsed.
-The filter function will be called for every section that should be parsed and returns `nil` for sections that should be filtered.
-For all other sections it should return the section name as given in the second parameter.
-
-#### property .addremove = false
-Allows the user to remove and recreate the configuration section
-
-#### property .dynamic = false
-Marks this section as dynamic.
-Dynamic sections can contain an undefinded number of completely userdefined options.
-
-#### property .optional = true
-Parse optional options
-
-#### property .anonymous = false
-Do not show UCI section names
-
-----
-
-## class Value (option, title, description)
-An object describing an option in a section of a UCI File. Creates a standard text field in the formular.<br />
-To instantiate use: `NamedSection:option(Value, "option", "title", "description")`<br />
- or `TypedSection:option(Value, "option", "title", "description")`
-* `option:` UCI option name
-* `title:` The title shown in the UI
-* `description:` description shown in the UI
-
-#### function :depends(key, value)
-Only show this option field if another option `key` is set to `value` in the same section.<br />
-If you call this function several times the dependencies will be linked with **"or"**
-
-#### function :value(key, value)
-Convert this text field into a combobox if possible and add a selection option.
-
-#### property .default = nil
-The default value
-
-#### property .maxlength = nil
-The maximum input length (of chars) of the value
-
-#### property .optional = false
-Marks this option as optional, implies `.rmempty = true`
-
-#### property .rmempty = true
-Removes this option from the configuration file when the user enters an empty value
-
-#### property .size = nil
-The maximum number of chars displayed by form field
-
-----
-
-## class ListValue (option, title, description)
-An object describing an option in a section of a UCI File.<br />
-Creates a list box or list of radio (for selecting one of many choices) in the formular.<br />
-To instantiate use: `NamedSection:option(ListValue, "option", "title", "description")`<br />
-or `TypedSection:option(ListValue, "option", "title", "description")`
-* `option:` UCI option name
-* `title:` The title shown in the UI
-* `description:` description shown in the UI
-
-#### function :depends(key, value)
-Only show this option field if another option `key` is set to `value` in the same section.<br />
-If you call this function several times the dependencies will be linked with **"or"**
-
-#### function :value(key, value)
-Adds an entry to the selection list
-
-#### property .widget = "select"
-`select` shows a selection list, `radio` shows a list of radio buttons inside form
-
-#### property .default = nil
-The default value
-
-#### property .optional = false
-Marks this option as optional, implies `.rmempty = true`
-
-#### property .rmempty = true
-Removes this option from the configuration file when the user enters an empty value
-
-#### property .size = nil
-The size of the form field
-
-----
-
-## class Flag (option, title, description)
-An object describing an option with two possible values in a section of a UCI File.<br />
-Creates a checkbox field in the formular.<br />
-To instantiate use: `NamedSection:option(Flag, "option", "title", "description")`<br />
- or `TypedSection:option(Flag, "option", "title", "description")`
-* `option:` UCI option name
-* `title:` The title shown in the UI
-* `description:` description shown in the UI
-
-#### function :depends (key, value)
-Only show this option field if another option `key` is set to `value` in the same section.<br />
-If you call this function several times the dependencies will be linked with **"or"**
-
-#### property .default = nil
-The default value
-
-#### property .disabled = 0
-the value that should be set if the checkbox is unchecked
-
-#### property .enabled = 1
-the value that should be set if the checkbox is checked
-
-#### property .optional = false
-Marks this option as optional, implies `.rmempty = true`
-
-#### property .rmempty = true
-Removes this option from the configuration file when the user enters an empty value
-
-----
-
-## class MultiValue (option, title, description)
-An object describing an option in a section of a UCI File.<br />
-Creates a list of checkboxed or a multiselectable list as form fields.<br />
-To instantiate use: `NamedSection:option(MultiValue, "option", "title", "description")`<br />
- or `TypedSection:option(MultiValue, "option", "title", "description")`
-* `option:` UCI option name
-* `title:` The title shown in the UI
-* `description:` description shown in the UI
-
-#### function :depends (key, value)
-Only show this option field if another option `key` is set to `value` in the same section.<br />
-If you call this function several times the dependencies will be linked with **"or"**
-
-#### function :value(key, value)
-Adds an entry to the list
-
-#### property .widget = "checkbox"
-`select` shows a selection list, `checkbox` shows a list of checkboxes inside form
-
-#### property .delimiter = " "
-The string which will be used to delimit the values inside stored option
-
-#### property .default = nil
-The default value
-
-#### property .optional = false
-Marks this option as optional, implies `.rmempty = true`
-
-#### property .rmempty = true
-Removes this option from the configuration file when the user enters an empty value
-
-#### property .size = nil
-The size of the form field (only used if property `.widget = "select"`)
-
-----
-
-## class StaticList (option, title, description)
-Similar to the `MultiValue`, but stores selected Values into a UCI list instead of a character-separated option.
-
-----
-
-## class DynamicList (option, title, description)
-A extensible list of user-defined values. Stores Values into a UCI list
-
-----
-
-## class DummyValue (option, title, description)
-Creates a readonly text in the form. !It writes no data to UCI!<br />
-To instantiate use: `NamedSection:option(DummyValue, "option", "title", "description")`<br />
- or `TypedSection:option(DummyValue, "option", "title", "description")`
-* `option:` UCI option name
-* `title:` The title shown in the UI
-* `description:` description shown in the UI
-
-#### function :depends (key, value)
-Only show this option field if another option `key` is set to `value` in the same section.<br />
-If you call this function several times the dependencies will be linked with **"or"**
-
-----
-
-## class TextValue (option, title, description)
-An object describing a multi-line textbox in a section in a non-UCI form.
-
-----
-
-## class Button (option, title, description)
-An object describing a Button in a section in a non-UCI form.
diff --git a/docs/LuCI-0.10.md b/docs/LuCI-0.10.md
deleted file mode 100644 (file)
index d39122c..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-# New in LuCI 0.10
-
-See [online wiki](https://github.com/openwrt/luci/wiki/LuCI-0.10) for latest version.
-
-This document describes new features and incompatibilities to LuCI 0.9.x.
-It is targeted at module authors developing external addons to LuCI.
-
-## I18N Changes
-
-### API
-
-The call conventions for the i18n api changed, there is no dedicated translation
-key anymore and the english text is used for lookup instead. This was done to
-ease the maintenance of language files.
-
-Code that uses `translate()` or `i18n()` must be changed as follows:
-
-```lua
--- old style:
-translate("some_text", "Some Text")
-translatef("some_format_text", "Some formatted Text: %d", 123)
-
--- new style:
-translate("Some Text")
-translatef("Some formatted Text: %d", 123)
-```
-
-Likewise for templates:
-
-```html
-<!-- old style: -->
-<%:some_text Some Text%>
-
-<!-- new style: -->
-<%:Some Text%>
-```
-
-If code must support both LuCI 0.9.x and 0.10.x versions, it is suggested to write the calls as follows:
-```lua
-translate("Some Text", "Some Text")
-```
-
-An alternative is wrapping translate() calls into a helper function:
-```lua
-function tr(key, alt)
-    return translate(key) or translate(alt) or alt
-end
-```
-
-... which is used as follows:
-```lua
-tr("some_key", "Some Text")
-```
-
-### Translation File Format
-
-Translation catalogs are now maintained in `*.po` format files.
-During build those get translated into [*.lmo archives](https://github.com/openwrt/luci/wiki/LMO).
-
-#### Components built within the LuCI tree
-
-If components using translations are built along with the LuCI tree, the newly added *.po file are automatically
-compiled into *.lmo archives during the build process. In order to bundle the appropriate *.lmo files into the
-corresponding *.ipk packages, component Makefiles must include a "PO" variable specifying the files to include.
-
-Given a module `applications/example/` which uses `po/en/example.po` and `po/en/example-extra.po`,
-the `applications/example/Makefile` must be changed as follows:
-
-```Makefile
-PO = example example-extra
-
-include ../../build/config.mk
-include ../../build/module.mk
-```
-
-#### Standalone components
-
-Authors who externally package LuCI components must prepare required `*.lmo` archives themselves.
-To convert existing Lua based message catalogs to the `*.po` format, the `build/i18n-lua2po.pl` helper script can be used.
-In order to convert `*.po` files into `*.lmo` files, the standalone `po2lmo` utility must be compiled as follows:
-
-```
-$ svn co http://svn.luci.subsignal.org/luci/branches/luci-0.10/libs/lmo
-$ cd lmo/
-$ make
-$ ./src/po2lmo translations.po translations.lmo
-```
-
-Note that at the time of writing, the utility program needs Lua headers installed on the system in order to compile properly.
-
-## CBI
-
-### Datatypes
-
-The server side UVL validation has been dropped to reduce space requirements on the target.
-Instead it is possible to define datatypes for CBI widgets now:
-
-```lua
-opt = section:option(Value, "optname", "Title Text")
-opt.datatype = "ip4addr"
-```
-
-User provided data is validated once on the frontend via JavaScript and on the server side prior to saving it.
-A list of possible datatypes can be found in the [luci.cbi.datatypes](https://github.com/openwrt/luci/blob/master/modules/luci-compat/luasrc/cbi/datatypes.lua) class.
-
-### Validation
-
-Server-side validator functions can now return custom error messages to provide better feedback on invalid input.
-
-```lua
-opt = section:option(Value, "optname", "Title Text")
-
-function opt.validate(self, value, section)
-    if input_is_valid(value) then
-        return value
-    else
-        return nil, "The value is invalid because ..."
-    end
-end
-```
-
-### Tabs
-
-It is now possible to break up CBI sections into multiple tabs to better organize longer forms.
-The TypedSection and NamedSection classes gained two new functions to define tabs, `tab()` and `taboption()`.
-
-```lua
-sct = map:section(TypedSection, "name", "type", "Title Text")
-
-sct:tab("general", "General Tab Title", "General Tab Description")
-sct:tab("advanced", "Advanced Tab Title", "Advanced Tab Description")
-
-opt = sct:taboption("general", Value, "optname", "Title Text")
-```
-
-The `tab()` function declares a new tab and takes up to three arguments:
-  * Internal name of the tab, must be unique within the section
-  * Title text of the tab
-  * Optional description text for the tab
-
-The `taboption()` function wraps `option()` and assigns the option object to the given tab.
-It takes up to five arguments:
-
-  * Name of the tab to assign the option to
-  * Option type, e.g. Value or DynamicList
-  * Option name
-  * Title text of the option
-  * Optional description text of the option
-
-If tabs are used within a particular section, the `option()` function must not be used,
-doing so results in undefined behaviour.
-
-### Hooks
-
-The CBI gained support for `hooks` which can be used to trigger additional actions during the
-life-cycle of a map:
-
-```lua
-map = Map("config", "Title Text")
-
-function map.on_commit(self)
-    -- do something if the UCI configuration got committed
-end
-```
-
-The following hooks are defined:
-
-* `on_cancel`: The user pressed cancel within a multistep Delegator or a SimpleForm instance 
-* `on_init`: The CBI is about to render the Map object
-* `on_parse`: The CBI is about to read received HTTP form values
-* `on_save`, `on_before_save`: The CBI is about to save modified UCI configuration files
-* `on_after_save`: Modified UCI configuration files just got saved
-* `on_before_commit`: The CBI is about to commit the changes
-* `on_commit`, `on_after_commit`, `on_before_apply`: Modified configurations got committed and the CBI is about to restart associated services
-* `on_apply`, `on_after_apply`: All changes where completely applied (only works on Map instances with the apply_on_parse attribute set)
-
-### Sortable Tables
-
-TypedSection instances which use the `cbi/tblsection` template may now use a new attribute `sortable` to allow the user to reorder table rows.
-
-```lua
-sct = map:section(TypedSection, "name", "type", "Title Text")
-sct.template = "cbi/tblsection"
-sct.sortable = true
-```
-
-## JavaScript
-
-The LuCI 0.10 branch introduced a new JavaScript file `xhr.js` which provides support routines for `XMLHttpRequest` operations.
-Each theme must include this file in the `<head>` area of the document for forms to work correctly.
-
-It should be included like this:
-
-```html
-<script src="<%=resource%>/xhr.js"></script>
-```
diff --git a/docs/ModulesHowTo.md b/docs/ModulesHowTo.md
deleted file mode 100644 (file)
index 75b7e75..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-# HowTo: Write Lua based Modules (deprecated for client side modules)
-
-See [online wiki](https://github.com/openwrt/luci/wiki/ModulesHowTo) for latest version.
-
-**Note:** If you plan to integrate your module into LuCI, you should read the [Module Reference](./Modules.md) in advance.
-
-This tutorial describes how to write your own modules for the LuCI WebUI.
-For this tutorial we refer to your LuCI installation directory as `lucidir` (`/usr/lib/lua/luci` on your OpenWRT device) and assume your LuCI installation is reachable through your webserver via `http://192.168.1.1/cgi-bin/luci`.
-
-The recommended way to set up a development environment:
-
-- Install OpenWRT on your router/device (You could use a QEMU or VirtualBox image instead)
-
-- Install SSHFS on your host
-
-- Mount your routers' root (`/`) someplace on your development host (eg. `/mnt/router`)
-
-- Then open `/mnt/router/(lucidir)` in your favorite development studio
-
-Extra:
-- Add configurations to your dev studio which will delete the luci cache (detailed below) and then open a browser window to your routers' configuration page in order to see your module/application.
-
-
-When testing, if you have edited index files, be sure to remove the folder `/tmp/luci-modulecache/*` and the file(s) `/tmp/luci-indexcache*`, then refresh the LUCI page to see your edits.
-
-## The dispatching process
-LuCI uses a dispatching tree that is built by executing the index-Function of every available controller.
-The CGI-environment variable `PATH_INFO` will be used as the path in this dispatching tree, e.g.: `/cgi-bin/luci/foo/bar/baz`
-resolves to `foo.bar.baz`.
-
-To register a function in the dispatching tree, use the `entry`-function of `luci.dispatcher`. It takes 4 arguments (2 are optional):
-```lua
-entry(path, target, title=nil, order=nil)
-```
-
-* `path` is a table that describes the position in the dispatching tree: For example a path of `{"foo", "bar", "baz"}` would insert your node in `foo.bar.baz`.
-* `target` describes the action that will be taken when a user requests the node. There are several predefined actions, of which the 3 most important (call, template, cbi) are described later on this page
-* `title` defines the title that will be visible to the user in the menu (optional)
-* `order` is a number with which nodes on the same level will be sorted in the menu (optional)
-
-You can assign more attributes by manipulating the node table returned by the entry-function. A few example attributes:
-
-* `i18n` defines which translation file should be automatically loaded when the page gets requested
-* `dependent` protects plugins to be called out of their context if a parent node is missing
-* `leaf` stops parsing the request at this node and goes no further in the dispatching tree
-* `sysauth` requires the user to authenticate with a given system user account
-
-
-# Naming and the module file
-Now we can start writing modules. Choose the category and name of your new digital child.
-
-Let's assume you want to create a new application `myapp` with a module `mymodule`.
-
-So you have to create a new sub-directory `lucidir/controller/myapp` with a file `mymodule.lua` with the following content:
-```lua
-module("luci.controller.myapp.mymodule", package.seeall)
-
-function index()
-
-end
-```
-
-The first line is required for Lua to correctly identify the module and create its scope.
-The `index`-Function will be used to register actions in the dispatching tree.
-
-
-
-## Teaching your new child (Actions)
-So it has a name, but no actions.
-
-We assume you want to reuse your module myapp.mymodule that you began in the last step.
-
-
-### Actions
-Reopen `lucidir/controller/myapp/mymodule.lua` and just add a function to it with:
-```lua
-module("luci.controller.myapp.mymodule", package.seeall)
-
-function index()
-    entry({"click", "here", "now"}, call("action_tryme"), "Click here", 10).dependent=false
-end
-function action_tryme()
-    luci.http.prepare_content("text/plain")
-    luci.http.write("Haha, rebooting now...")
-    luci.sys.reboot()
-end
-```
-
-And now visit the path `/cgi-bin/luci/click/here/now` (`http://192.168.1.1/luci/click/here/now` if you are using the development environment) in your browser.
-
-These action functions simply have to be added to a dispatching entry.
-
-As you may or may not know: CGI specification requires you to send a `Content-Type` header before you can send your content. You will find several shortcuts (like the one used above) as well as redirecting functions in the module `luci.http`
-
-### Views
-If you only want to show the user text or some interesting family photos, it may be enough to use an HTML-template.
-These templates can also include some Lua code but be aware that writing whole office-suites by only using these templates might be considered "dirty" by other developers.
-
-Now let's create a little template `lucidir/view/myapp-mymodule/helloworld.htm` with the content:
-
-```html
-<%+header%>
-<h1><%:Hello World%></h1> 
-<%+footer%>
-```
-
-
-and add the following line to the `index`-Function of your module file.
-```lua
-entry({"my", "new", "template"}, template("myapp-mymodule/helloworld"), "Hello world", 20).dependent=false
-```
-
-Now visit the path `/cgi-bin/luci/my/new/template` (`http://192.168.1.1/luci/my/new/template`) in your browser.
-
-You may notice those special `<% %>`-Tags, these are [template markups](./Templates.md) used by the LuCI template processor.
-It is always good to include header and footer at the beginning and end of a template as those create the default design and menu.
-
-### CBI models
-The CBI is one of the coolest features of LuCI.
-It creates a formulae based user interface and saves its contents to a specific UCI config file.
-You only have to describe the structure of the configuration file in a CBI model file and Luci does the rest of the work.
-This includes generating, parsing and validating an XHTML form and reading and writing the UCI file.
-
-So let's be serious at least for this paragraph and create a practical example `lucidir/model/cbi/myapp-mymodule/netifaces.lua` with the following contents:
-
-```lua
-m = Map("network", "Network") -- We want to edit the uci config file /etc/config/network
-
-s = m:section(TypedSection, "interface", "Interfaces") -- Especially the "interface"-sections
-s.addremove = true -- Allow the user to create and remove the interfaces
-function s:filter(value)
-   return value ~= "loopback" and value -- Don't touch loopback
-end 
-s:depends("proto", "static") -- Only show those with "static"
-s:depends("proto", "dhcp") -- or "dhcp" as protocol and leave PPPoE and PPTP alone
-
-p = s:option(ListValue, "proto", "Protocol") -- Creates an element list (select box)
-p:value("static", "static") -- Key and value pairs
-p:value("dhcp", "DHCP")
-p.default = "static"
-
-s:option(Value, "ifname", "interface", "the physical interface to be used") -- This will give a simple textbox
-
-s:option(Value, "ipaddr", translate("ip", "IP Address")) -- Yes, this is an i18n function ;-)
-
-s:option(Value, "netmask", "Netmask"):depends("proto", "static") -- You may remember this "depends" function from above
-
-mtu = s:option(Value, "mtu", "MTU")
-mtu.optional = true -- This one is very optional
-
-dns = s:option(Value, "dns", "DNS-Server")
-dns:depends("proto", "static")
-dns.optional = true
-function dns:validate(value) -- Now, that's nifty, eh?
-    return value:match("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") -- Returns nil if it doesn't match otherwise returns match
-end
-
-gw = s:option(Value, "gateway", "Gateway")
-gw:depends("proto", "static")
-gw.rmempty = true -- Remove entry if it is empty
-
-return m -- Returns the map
-```
-
-and of course remember to add something like this to your module's `index`-Function.
-```lua
-entry({"admin", "network", "interfaces"}, cbi("myapp-mymodule/netifaces"), "Network interfaces", 30).dependent=false
-```
-
-There are many more features. See [the CBI reference](./CBI.md) and the modules shipped with LuCI.
diff --git a/docs/Templates.md b/docs/Templates.md
deleted file mode 100644 (file)
index ed51bcf..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-## Templates
-
-See [online wiki](https://github.com/openwrt/luci/wiki/Templates) for latest version.
-
-LuCI has a simple regex based template processor which parses HTML-files to Lua functions and allows to store precompiled template files.
-The simplest form of a template is just an ordinary HTML-file. It will be printed out to the user as is.
-
-In LuCI every template is an object with an own scope
-It can therefore be instanced and each instance can have a different scope.
-As every template processor. LuCI supports several special markups. Those are enclosed in `<% %>`-Tags.
-
-By adding `-` (dash) right after the opening `<%` every whitespace before the markup will be stripped.
-Adding a `-` right before the closing `%>` will equivalently strip every whitespace behind the markup.
-
-
-## Builtin functions and markups
-### Including Lua code
-*Markup:*
-```
-<% code %>
-```
-
-### Writing variables and function values
-*Syntax:*
-```
-<% write (value) %>
-```
-
-*Short-Markup:*
-```
-<%=value%>
-```
-
-### Including templates
-*Syntax:*
-```
-<% include (templatename) %>
-```
-
-*Short-Markup:*
-```
-<%+templatename%>
-```
-
-
-### Translating
-*Syntax:*
-```
-<%= translate("Text to translate") %>
-```
-
-*Short-Markup:*
-```
-<%:Text to translate%>
-```
-
-
-### Commenting
-*Markup:*
-```
-<%# comment %>
-```
-
-## Builtin constants
-* `REQUEST_URI`: The current URL (without server part)
-* `controller`: Path to the Luci main dispatcher
-* `resource`: Path to the resource directory
-* `media`: Path to the active theme directory
diff --git a/docs/i18n.md b/docs/i18n.md
deleted file mode 100644 (file)
index a9ba013..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-# Internationalization (i18n)
-
-See [online wiki](https://github.com/openwrt/luci/wiki/i18n) for latest version.
-
-## Use translation function
-
-### Translations in JavaScript
-
-Wrap translatable strings with `_()` e.g.  `_('string to translate')` and the `i18n-scan.pl` and friends will correctly identify these strings for translation.
-
-If you have multi line strings you can split them with concatenation:
-```js
-var mystr = _('this string will translate ' +
-       'correctly even though it is ' +
-       'a multi line string!');
-```
-
-You may also use line continuations `\` syntax:
-
-```js
-var mystr = _('this string will translate \
-       correctly even though it is \
-       a multi line string');
-```
-
-Usually if you have multiple sentences you may need to use a line break. Use the `<br />` HTML tag like so:
-```js
-var mystr = _('Port number.') + '<br />' +
-       _('E.g. 80 for HTTP');
-```
-Use `<br />` and **not** `<br>` or `<br/>`.
-
-If you have a link inside a translation, move its attributes out of a translation key:
-```js
-var mystr = _('For further information <a %s>check the wiki</a>')
-       .format('href="https://openwrt.org/docs/" target="_blank" rel="noreferrer"')
-```
-This will generate a full link with HTML `For further information <a href="https://openwrt.org/docs/" target="_blank" rel="noreferrer">check the wiki</a>`. The `noreferrer` is important so that it is opened in a new tab (`target="_blank"`).
-
-### Translations in LuCI lua+html templates
-Use the `<%: text to translate %>` as documented on [Templates](./Templates.md)
-
-### Translations in Lua controller code and Lua CBIs
-As hinted at in the Templates doc, the `%:` invokes a `translate()` function.
-In most controller contexts, this is already available for you, but if necessary, is available for include in `luci.i18n.translate`
-
-
-## Translation files
-Translations are saved in the folder `po/` within each individual LuCI component directory, e.g. `applications/luci-app-acl/po/`.
-The template is in `po/templates/<package>.pot`.
-The individual language translation files can be found at `po/[lang]/[package].po`.
-
-In order to use the commands below you need to have the `gettext` utilities (`msgcat`, `msgfmt`, `msgmerge`) installed on your system.
-On Debian/Ubuntu, install them with `sudo apt install gettext`.
-
-### Initialize po files
-
-When you add or update an app, run from your `applications/luci-app-acl/` app folder:
-
-    ../../build/i18n-add-language.sh
-
-This creates the skeleton .po files for all available languages open for translation for your app.
-
-Or from the luci repo root:
-
-    ./build/i18n-add-language.sh
-
-This creates the skeleton .po files for all existing languages open for translation for all sub-folders.
-
-### Rebuild po files (for existing languages)
-After you make changes to a package, run:
-
-    ./build/i18n-sync.sh applications/[application]
-
-Example:
-
-    ./build/i18n-sync.sh applications/luci-app-acl
-
-This only updates those language .po files that already exist in `applications/luci-app-acl/po/`. See the previous step to add a new language.
-
-Note: the directory argument can be omitted to update all po template and po files.
-
-
-Some packages share translation files, in this case you need to scan through all their folders:
-
-    ./build/i18n-scan.pl applications/[package-1] applications/[package-2] applications/[package-n] > [location of shared template]/[application].pot
-
-This is what the `mkbasepot.sh` script does for the `luci-base` module:
-
-    ./build/i18n-scan.pl \
-        modules/luci-base modules/luci-compat modules/luci-lua-runtime \
-        modules/luci-mod-network modules/luci-mod-status modules/luci-mod-system \
-        protocols themes \
-    > modules/luci-base/po/templates/base.pot
-
-*Note:* The translation catalog for the base system covers multiple components. Use the following commands to update it:
-
-    ./build/mkbasepot.sh
-    ./build/i18n-update.pl
-
-### LMO files
-The `*.po` files are big so Luci needs them in a compact compiled [LMO format](./LMO.md).
-Luci reads `*.lmo` translations from the `/usr/lib/lua/luci/i18n/` folder.
-E.g. `luci-app-acl` has an Arabic translation in `luci-i18n-acl-ar` package that installs `/usr/lib/lua/luci/i18n/acl.ar.lmo` file.
-
-In order to quickly convert a single `.po` file to `.lmo` file for testing on the target system use the `po2lmo` utility.
-You will need to compile it from the `luci-base` module:
-
-     $ cd modules/luci-base/src/
-     $ make po2lmo
-     $ ./po2lmo
-     Usage: ./po2lmo input.po output.lmo
-
-Now you can compile and upload the translation:
-
-     ./po2lmo ../../../applications/luci-app-acl/po/ar/acl.po ./acl.ar.lmo
-     scp ./acl.ar.lmo root@192.168.1.1:/usr/lib/lua/luci/i18n/
-
-You can change languages in [System /Language and Style](http://192.168.1.1/cgi-bin/luci/admin/system/system) and check the translation.
git clone https://git.99rst.org/PROJECT