diff options
author | Jo-Philipp Wich <jo@mein.io> | 2023-08-12 01:44:06 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-12 01:44:06 +0200 |
commit | 306da3c30ad9a334ba60e6e8431726b1220b8ccd (patch) | |
tree | 6839460fa242e64474ae98f2ccea2072fd1d0d3e /docs | |
parent | 4c3584bbdc1abe377c4b85099740f61d3213c1c5 (diff) | |
parent | 155d2bdd6ed787c553a1f338ee77478a1e3a1262 (diff) |
Merge pull request #6513 from stokito/luci-docs
docs: Synchronize with Wiki
Diffstat (limited to 'docs')
-rw-r--r-- | docs/CBI.md | 159 | ||||
-rw-r--r-- | docs/JsonRpcHowTo.md | 166 | ||||
-rw-r--r-- | docs/LAR.md | 87 | ||||
-rw-r--r-- | docs/LMO.md | 159 | ||||
-rw-r--r-- | docs/LuCI-0.10.md | 244 | ||||
-rw-r--r-- | docs/Modules.md | 132 | ||||
-rw-r--r-- | docs/ModulesHowTo.md | 246 | ||||
-rw-r--r-- | docs/README.md | 2 | ||||
-rw-r--r-- | docs/Templates.md | 87 | ||||
-rw-r--r-- | docs/ThemesHowTo.md | 103 | ||||
-rw-r--r-- | docs/i18n.md | 116 |
11 files changed, 792 insertions, 709 deletions
diff --git a/docs/CBI.md b/docs/CBI.md index 933380d22f..056386f8c2 100644 --- a/docs/CBI.md +++ b/docs/CBI.md @@ -1,78 +1,83 @@ +# Writing LuCI CBI models -# 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#cbimodels). +See [online wiki](https://github.com/openwrt/luci/wiki/CBI) for latest version. -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** +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_) +## 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 +* `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_, ...) +#### function :section (sectionclass, ...) Creates a new section -* **sectionclass**: a class object of the section +* `sectionclass`: a class object of the section * _additional parameters passed to the constructor of the section class_ ---- -## class NamedSection (_name, type, title, description_) +## 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 +* `name:` UCI section name +* `type:` UCI section type +* `title:` The title shown in the UI +* `description:` description shown in the UI -#### function :option(_optionclass_, ...) +#### function :option(optionclass, ...) Creates a new option -* **optionclass:** a class object of the section +* `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. +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_) +## 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 +* `type:` UCI section type +* `title:` The title shown in the UI +* `description:` description shown in the UI -#### function :option(_optionclass_, ...) +#### function :option(optionclass, ...) Creates a new option - **optionclass:** a class object of the section - _additional parameters passed to the constructor of the option class_ +* `optionclass:` a class object of the section +* _additional parameters passed to the constructor of the option class_ -#### function :depends(_key, value_) -Only select those sections where _key == value_ <br /> +#### 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- +#### 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. +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. +Marks this section as dynamic. +Dynamic sections can contain an undefinded number of completely userdefined options. #### property .optional = true Parse optional options @@ -82,16 +87,16 @@ Do not show UCI section names ---- -## class Value (_option, title, description_) +## 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 +* `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 /> +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) @@ -101,10 +106,10 @@ Convert this text field into a combobox if possible and add a selection option. The default value #### property .maxlength = nil -The maximum inputlength (of chars) of the value +The maximum input length (of chars) of the value #### property .optional = false -Marks this option as optional, implies .rmempty = true +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 @@ -114,30 +119,30 @@ The maximum number of chars displayed by form field ---- -## class ListValue (_option, title, description_) +## 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 +* `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 /> +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_) +#### 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 +`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 +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 @@ -147,17 +152,17 @@ The size of the form field ---- -## class Flag (_option, title, description_) +## 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 /> +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 +* `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 /> +#### 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 @@ -170,31 +175,31 @@ the value that should be set if the checkbox is unchecked the value that should be set if the checkbox is checked #### property .optional = false -Marks this option as optional, implies .rmempty = true +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_) +## 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 /> +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 +* `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 /> +#### 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_) +#### 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 +`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 @@ -203,44 +208,44 @@ The string which will be used to delimit the values inside stored option The default value #### property .optional = false -Marks this option as optional, implies .rmempty = true +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"_) +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 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_) +## class DynamicList (option, title, description) A extensible list of user-defined values. Stores Values into a UCI list ---- -## class DummyValue (_option, title, description_) +## 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 /> +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 +* `option:` UCI option name +* `title:` The title shown in the UI +* `description:` description shown in the UI -#### property :depends (_key, value_) -Only show this option field if another option _key_ is set to _value_ in the same section.<br /> +#### 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_) +## 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_) +## class Button (option, title, description) An object describing a Button in a section in a non-UCI form. diff --git a/docs/JsonRpcHowTo.md b/docs/JsonRpcHowTo.md index 76d61f86e3..46aeb05659 100644 --- a/docs/JsonRpcHowTo.md +++ b/docs/JsonRpcHowTo.md @@ -1,66 +1,120 @@ -LuCI provides some of its libraries to external applications through a JSON-RPC API. +# HowTo: Using the JSON-RPC API + +See [online wiki](https://github.com/openwrt/luci/wiki/JsonRpcHowTo) for latest version. + +LuCI provides some of its libraries to external applications through a [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) API. This Howto shows how to use it and provides information about available functions. +See also +* wiki [rpcd](https://openwrt.org/docs/techref/rpcd) +* wiki [ubus](https://openwrt.org/docs/techref/ubus) + +## Basics +To enable the API, install the following package and restart `uhttpd`: + +```bash +opkg install luci-mod-rpc luci-lib-ipkg luci-compat +/etc/init.d/uhttpd restart +``` + +LuCI comes with an efficient JSON De-/Encoder together with a JSON-RPC-Server which implements the JSON-RPC 1.0 and 2.0 (partly) specifications. +The LuCI JSON-RPC server offers several independent APIs. +Therefore you have to use **different URLs for every exported library**. +Assuming your LuCI-Installation can be reached through `/cgi-bin/luci`, any exported library can be reached via `/cgi-bin/luci/rpc/LIBRARY`. -# Basics -LuCI comes with an efficient JSON De-/Encoder together with a JSON-RPC-Server which implements the *JSON-RPC 1.0_' and 2.0 (partly) specifications. The LuCI JSON-RPC server offers several independent APIs. Therefore you have to use '_different URLs for every exported library*. -Assuming your LuCI-Installation can be reached through */cgi-bin/luci_' any exported library can be reached via '''/cgi-bin/luci/rpc/''LIBRARY_*. +## Authentication +Most exported libraries will require a valid authentication to be called with. +If you get an `HTTP 403 Forbidden` status code you are probably missing a valid authentication token. +To get such a token you have to call the `login` method of the RPC-Library `auth`. +Following our example from above this login function would be provided at `/cgi-bin/luci/rpc/auth`. +The function accepts 2 parameters: `username` and `password` (of a valid user account on the host system) and returns an authentication token. -# Authentication -Most exported libraries will require a valid authentication to be called with. If you get an *HTTP 403 Forbidden_' status code you are probably missing a valid authentication token. To get such a token you have to call the function '''login''' of the RPC-Library '''auth'''. Following our example from above this login function would be provided at '_/cgi-bin/luci/rpc/auth*. The function accepts 2 parameters: username and password (of a valid user account on the host system) and returns an authentication token. +Example: +```sh +curl http://<hostname>/cgi-bin/luci/rpc/auth --data ' +{ + "id": 1, + "method": "login", + "params": [ + "youruser", + "somepassword" + ] +}' +``` -If you want to call any exported library which requires an authentication token you have to *append it as an URL parameter _auth''''' to the RPC-Server URL. So instead of calling '''/cgi-bin/luci/rpc/''LIBRARY''''' you have to call '''/cgi-bin/luci/rpc/''LIBRARY''?auth=''TOKEN_*. +response: +```json +{"id":1,"result":"65e60c5a93b2f2c05e61681bf5e94b49","error":null} +``` + +If you want to call any exported library which requires an authentication token you have to append it as an URL parameter auth to the RPC-Server URL. +E.g. instead of calling `/cgi-bin/luci/rpc/LIBRARY` you should call `/cgi-bin/luci/rpc/LIBRARY?auth=TOKEN`. If your JSON-RPC client is Cookie-aware (like most browsers are) you will receive the authentication token also with a session cookie and probably don't have to append it to the RPC-Server URL. -# Exported Libraries -## uci -The UCI-Library */rpc/uci* offers functionality to interact with the Universal Configuration Interface. -*Exported Functions:* -* [(string) add(config, type)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.add) -* [(integer) apply(config)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.apply) -* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.changes (object) changes([config])] -* [(boolean) commit(config)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.commit) -* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.delete (boolean) delete(config, section[, option])] -* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.delete_all (boolean) delete_all(config[, type])] -* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.foreach (array) foreach(config[, type])] -* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.get (mixed) get(config, section[, option])] -* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.get_all (object) get_all(config[, section])] -* [http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.get (mixed) get_state(config, section[, option])] -* [(boolean) revert(config)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.revert) -* [(name) section(config, type, name, values)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.section) -* [(boolean) set(config, section, option, value)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.set) -* [(boolean) tset(config, section, values)](http://luci.subsignal.org/api/luci/modules/luci.model.uci.html#Cursor.tset) - -## uvl -The UVL-Library */rpc/uvl* offers functionality to validate UCI files and get schemes describing UCI files. -*Exported Functions:* -* [(array) get_scheme(scheme)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.get_scheme) -* [(array) validate(config, section, option)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.validate) -* [(array) validate_config(config)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.validate_config) -* [(array) validate_section(config, section)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.validate_section) -* [(array) validate(config, section, option)](http://luci.subsignal.org/api/luci/modules/luci.uvl.html#UVL.validate_option) - -## fs -The Filesystem library */rpc/fs* offers functionality to interact with the filesystem on the host machine. -*Exported Functions:* - -* [Complete luci.fs library](http://luci.subsignal.org/api/luci/modules/luci.fs.html) -*Note:* All functions are exported as they are except for _readfile'' which encodes its return value in base64 and ''writefile'' which only accepts base64 encoded data as second argument. Note that both functions will only be available when the ''luasocket_ packet is installed on the hostsystem. - -## sys -The System library */rpc/sys* offers functionality to interact with the operating system on the host machine. -*Exported Functions:* -* [Complete luci.sys library](http://luci.subsignal.org/api/luci/modules/luci.sys.html) -* [Complete luci.sys.group library](http://luci.subsignal.org/api/luci/modules/luci.sys.group.html) with prefix *group.* -* [Complete luci.sys.net library](http://luci.subsignal.org/api/luci/modules/luci.sys.net.html) with prefix *net.* -* [Complete luci.sys.process library](http://luci.subsignal.org/api/luci/modules/luci.sys.process.html) with prefix *process.* -* [Complete luci.sys.user library](http://luci.subsignal.org/api/luci/modules/luci.sys.user.html) with prefix *user.* -* [Complete luci.sys.wifi library](http://luci.subsignal.org/api/luci/modules/luci.sys.wifi.html) with prefix *wifi.* - -## ipkg -The IPKG library */rpc/ipkg* offers functionality to interact with the package manager (IPKG or OPKG) on the host machine. -*Exported Functions:* -* [Complete luci.model.ipkg library](http://luci.subsignal.org/api/luci/modules/luci.model.ipkg.html) +## Exported Libraries +### uci +The UCI-Library `/rpc/uci` offers functionality to interact with the Universal Configuration Interface. + +**Exported Functions:** +* [(string) add(config, type)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.add) +* [(integer) apply(config)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.apply) +* [(object) changes([config])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.changes) +* [(boolean) commit(config)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.commit) +* [(boolean) delete(config, section[, option])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.delete) +* [(boolean) delete_all(config[, type])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.delete_all) +* [(array) foreach(config[, type])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.foreach) +* [(mixed) get(config, section[, option])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.get) +* [(object) get_all(config[, section])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.get_all) +* [(mixed) get_state(config, section[, option])](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.get) +* [(boolean) revert(config)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.revert) +* [(name) section(config, type, name, values)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.section) +* [(boolean) set(config, section, option, value)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.set) +* [(boolean) tset(config, section, values)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.uci.html#Cursor.tset) + +Example: +```sh +curl http://<hostname>/cgi-bin/luci/rpc/uci?auth=yourtoken --data ' +{ + "method": "get_all", + "params": [ "network" ] +}' +``` + +### fs +The Filesystem library `/rpc/fs` offers functionality to interact with the filesystem on the host machine. + +**Exported Functions:** + +* [Complete luci.fs library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.fs.html) + +**Note:** All functions are exported as they are except for `readfile` which encodes its return value in [Base64](https://en.wikipedia.org/wiki/Base64) and `writefile` which only accepts Base64 encoded data as second argument. +Note that both functions will only be available when the `luasocket` packet is installed on the host system. + +### sys +The System library `/rpc/sys` offers functionality to interact with the operating system on the host machine. + +**Exported Functions:** +* [Complete luci.sys library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.html) +* [Complete luci.sys.group library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.group.html) prefixing the method name with `group.methodname`. +* [Complete luci.sys.net library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.net.html) prefixing the method name with `net.methodname`. +* [Complete luci.sys.process library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.process.html) prefixing the method name with `process.methodname`. +* [Complete luci.sys.user library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.user.html) with prefix `user`. +* [Complete luci.sys.wifi library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.sys.wifi.html) with prefix `wifi`. + +Example: +```sh +curl http://<hostname>/cgi-bin/luci/rpc/sys?auth=yourtoken --data ' +{ + "method": "net.conntrack" +}' +``` + +### ipkg +The IPKG library `/rpc/ipkg` offers functionality to interact with the package manager (IPKG or OPKG) on the host machine. + +**Exported Functions:** +* [Complete luci.model.ipkg library](https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/docs/api/modules/luci.model.ipkg.html) + diff --git a/docs/LAR.md b/docs/LAR.md deleted file mode 100644 index 457b076f9a..0000000000 --- a/docs/LAR.md +++ /dev/null @@ -1,87 +0,0 @@ -LAR is a simple archive format to pack multiple lua source files and arbitrary other resources into a single file. - - -# Format Specification - -A LAR archive file is divided into two parts: the payload and the index lookup table. -All segments of the archive are 4 Byte aligned to ease reading and processing of the format. -All integers are stored in network byte order, so an implementation has to use htonl() and htons() to properly read them. - -Schema: - - <payload: - <member: - <N*4 bytes: path of file #1> - <N*4 bytes: data of file #1> - > - - <member: - <N*4 bytes: path of file #2> - <N*4 bytes: data of file #2> - > - - ... - - <member: - <N*4 bytes: path of file #N> - <N*4 bytes: data of file #N> - > - > - - <index table: - <entry: - <uint32: offset for path of file #1> <uint32: length for path of file #1> - <uint32: offset for data of file #1> <uint32: length for data of file #1> - <uint16: type of file #1> <uint16: flags of file #1> - > - - <entry: - <uint32: offset for path of file #2> <uint32: length for path of file #2> - <uint32: offset for data of file #2> <uint32: length for data of file #2> - <uint16: type of file #2> <uint16: flags of file #2> - > - - ... - - <entry: - <uint32: offset for path of file #N> <uint32: length for path of file #N> - <uint32: offset for data of file #N> <uint32: length for data of file #N> - <uint16: type of file #N> <uint16: flags of file #N> - > - > - - <uint32: offset for begin of index table> - - - -# Processing - -In order to process an LAR archive, an implementation would have to do the following steps: - -## Read Index - -1. Locate and open the archive file -1. Seek to end of file - 4 bytes -1. Read 32bit index offset and swap from network to native byte order -1. Seek to index offset, calculate index length: filesize - index offset - 4 -1. Initialize a linked list for index table entries -1. Read each index entry until the index length is reached, read and byteswap 4 * 32bit int and 2 * 16bit int -1. Seek to begin of file - -## Read Member - -1. Read the archive index -1. Iterate through the linked index list, perform the following steps for each entry -1. Seek to the specified file path offset -1. Read as much bytes as specified in the file path length into a buffer -1. Compare the contents of the buffer against the path of the searched member -1. If buffer and searched path are equal, seek to the specified file data offset -1. Read data until the file data length is reached, return -1. Select the next index table entry and repeat from step 3, if there is no next entry then return - -# Reference implementation - -A reference implementation can be found here: -http://luci.subsignal.org/trac/browser/luci/trunk/contrib/lar - -The lar.pl script is a simple packer for LAR archives and cli.c provides a utility to list and dump packed LAR archives. diff --git a/docs/LMO.md b/docs/LMO.md index 961a45ba84..3b7b5f92ae 100644 --- a/docs/LMO.md +++ b/docs/LMO.md @@ -1,7 +1,12 @@ -LMO is a simple binary format to pack language strings into a more efficient form. Although it's suitable to store any kind of key-value table, it's only used for the LuCI *.po based translation system at the moment. The abbreviation "LMO" stands for "Lua Machine Objects" in the style of the GNU gettext *.mo format. +# LMO - Lua Machine Objects +See [online wiki](https://github.com/openwrt/luci/wiki/LMO) for latest version. -# Format Specification +LMO is a simple binary format to pack language strings into a more efficient form. +Although it's suitable to store any kind of key-value table, it's only used for the LuCI *.po based translation system at the moment. +The abbreviation "LMO" stands for "Lua Machine Objects" in the style of the GNU gettext *.mo format. + +## Format Specification A LMO file is divided into two parts: the payload and the index lookup table. All segments of the file are 4 Byte aligned to ease reading and processing of the format. @@ -50,95 +55,95 @@ Schema: -# Processing +## Processing In order to process a LMO file, an implementation would have to do the following steps: -## Read Index +### Read Index 1. Locate and open the archive file -1. Seek to end of file - 4 bytes (sizeof(uint32_t)) -1. Read 32bit index offset and swap from network to native byte order -1. Seek to index offset, calculate index length: filesize - index offset - 4 -1. Initialize a linked list for index table entries -1. Read each index entry until the index length is reached, read and byteswap 4 * uint32_t for each step -1. Seek to begin of file +2. Seek to end of file - 4 bytes (sizeof(uint32_t)) +3. Read 32bit index offset and swap from network to native byte order +4. Seek to index offset, calculate index length: filesize - index offset - 4 +5. Initialize a linked list for index table entries +6. Read each index entry until the index length is reached, read and byteswap 4 * uint32_t for each step +7. Seek to begin of file -## Read Entry +### Read Entry 1. Calculate the unsigned 32bit hash of the entries key value (see "Hash Function" section below) -1. Obtain the archive index -1. Iterate through the linked index list, perform the following steps for each entry: - 1. Compare the entry hash value with the calculated hash from step 1 - 2. If the hash values are equal proceed with step 4 - 3. Select the next entry and repeat from step 3.1 -1. Seek to the file offset specified in the selected entry -1. Read as much bytes as specified in the entry length into a buffer -1. Return the buffer value +2. Obtain the archive index +3. Iterate through the linked index list, perform the following steps for each entry: + 1. Compare the entry hash value with the calculated hash from step 1 + 2. If the hash values are equal proceed with step 4 + 3. Select the next entry and repeat from step 3.1 +4. Seek to the file offset specified in the selected entry +5. Read as much bytes as specified in the entry length into a buffer +6. Return the buffer value -# Hash Function +## Hash Function The current LuCI-LMO implementation uses the "Super Fast Hash" function which was kindly put in the public domain by it's original author. See http://www.azillionmonkeys.com/qed/hash.html for details. Below is the C-Implementation of this function: - - #if (defined(__GNUC__) && defined(__i386__)) - #define sfh_get16(d) (*((const uint16_t *) (d))) - #else - #define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ - +(uint32_t)(((const uint8_t *)(d))[0]) ) - #endif - - uint32_t sfh_hash(const char * data, int len) - { - uint32_t hash = len, tmp; - int rem; - - if (len <= NULL) return 0; - - rem = len & 3; - len >>= 2; - - /* Main loop */ - for (;len > 0; len--) { - hash += sfh_get16(data); - tmp = (sfh_get16(data+2) << 11) ^ hash; - hash = (hash << 16) ^ tmp; - data += 2*sizeof(uint16_t); - hash += hash >> 11; - } - - /* Handle end cases */ - switch (rem) { - case 3: hash += sfh_get16(data); - hash ^= hash << 16; - hash ^= data[sizeof(uint16_t)] << 18; - hash += hash >> 11; - break; - case 2: hash += sfh_get16(data); - hash ^= hash << 11; - hash += hash >> 17; - break; - case 1: hash += *data; - hash ^= hash << 10; - hash += hash >> 1; - } - - /* Force "avalanching" of final 127 bits */ - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - - return hash; +```c +#if (defined(__GNUC__) && defined(__i386__)) +#define sfh_get16(d) (*((const uint16_t *) (d))) +#else +#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + +uint32_t sfh_hash(const char * data, int len) +{ + uint32_t hash = len, tmp; + int rem; + + if (len <= 0 || data == NULL) return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (;len > 0; len--) { + hash += sfh_get16(data); + tmp = (sfh_get16(data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2*sizeof(uint16_t); + hash += hash >> 11; } - -# Reference Implementation + /* Handle end cases */ + switch (rem) { + case 3: hash += sfh_get16(data); + hash ^= hash << 16; + hash ^= data[sizeof(uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: hash += sfh_get16(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += *data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} +``` + +## Reference Implementation A reference implementation can be found here: -http://luci.subsignal.org/trac/browser/luci/trunk/libs/lmo/src +https://github.com/openwrt/luci/blob/master/modules/luci-base/src/template_lmo.c -The lmo_po2lmo.c executable implements a *.po to *.lmo conversation utility and lmo_lookup.c is a simple *.lmo test utility. -Lua bindings for lmo are defined in lmo_lualib.c and associated headers. +The `po2lmo.c` executable implements a `*.po` to `*.lmo` conversation utility. +Lua bindings for lmo are defined in `template_lualib.c` and associated headers. diff --git a/docs/LuCI-0.10.md b/docs/LuCI-0.10.md index 5db9895e58..69a5f33d5b 100644 --- a/docs/LuCI-0.10.md +++ b/docs/LuCI-0.10.md @@ -1,148 +1,144 @@ -[[PageOutline(2-5, Table of Contents, floated)]] +# 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 +## I18N Changes -## API +### 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: +Code that uses `translate()` or `i18n()` must be changed as follows: - - -- 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) - +```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: - - <!-- old style: --> - <%:some_text Some Text%> - - <!-- new style: --> - <%:Some Text%> - +```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: - - translate("Some Text", "Some Text") - +```lua +translate("Some Text", "Some Text") +``` An alternative is wrapping translate() calls into a helper function: - - function tr(key, alt) - return translate(key) or translate(alt) or alt - end - +```lua +function tr(key, alt) + return translate(key) or translate(alt) or alt +end +``` ... which is used as follows: - - tr("some_key", "Some Text") - - -## Translation File Format +```lua +tr("some_key", "Some Text") +``` -Translation catalogs are now maintained in *.po format files. During build those get translated -into [*.lmo archives](http://luci.subsignal.org/trac/wiki/Documentation/LMO). +### Translation File Format -LuCI ships a [utility script](http://luci.subsignal.org/trac/browser/luci/branches/luci-0.10/build/i18n-lua2po.pl) -in the build/ directory to convert old Lua translation files to the *.po format. The generated *.po files should -be placed in the appropriate subdirectories within the top po/ file in the LuCI source tree. +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 +#### 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: +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: - - PO = example example-extra - - include ../../build/config.mk - include ../../build/module.mk - +```Makefile +PO = example example-extra -### Standalone components +include ../../build/config.mk +include ../../build/module.mk +``` -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: +#### Standalone components - - $ svn co http://svn.luci.subsignal.org/luci/branches/luci-0.10/libs/lmo - $ cd lmo/ - $ make - $ ./src/po2lmo translations.po translations.lmo - +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 +## CBI -## Datatypes +### 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: - - opt = section:option(Value, "optname", "Title Text") - opt.datatype = "ip4addr" - +```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](http://luci.subsignal.org/trac/browser/luci/branches/luci-0.10/libs/web/luasrc/cbi/datatypes.lua#L26) class. +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 -## Validation +Server-side validator functions can now return custom error messages to provide better feedback on invalid input. -Server-sided validator function can now return custom error messages to provide better feedback on invalid input. +```lua +opt = section:option(Value, "optname", "Title Text") - - 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 - +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 +### 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()_. - - - 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 is declares a new tab and takes up to three arguments: +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. +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 @@ -151,52 +147,50 @@ It takes up to five arguments: * 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, +If tabs are used within a particular section, the `option()` function must not be used, doing so results in undefined behaviour. -## Hooks +### Hooks -The CBI gained support for _hooks_ which can be used to trigger additional actions during the +The CBI gained support for `hooks` which can be used to trigger additional actions during the life-cycle of a map: - - map = Map("config", "Title Text") - - function map.on_commit(self) - -- do something if the UCI configuration got committed - end - +```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 multi-step 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) || +* `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 +### 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. +TypedSection instances which use the `cbi/tblsection` template may now use a new attribute `sortable` to allow the user to reorder table rows. - - sct = map:section(TypedSection, "name", "type", "Title Text") - sct.template = "cbi/tblsection" - sct.sortable = true - - ... - +```lua +sct = map:section(TypedSection, "name", "type", "Title Text") +sct.template = "cbi/tblsection" +sct.sortable = true +``` -# JavaScript +## 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. +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: - - <script type="text/javascript" src="<%=resource%>/xhr.js"></script> -
\ No newline at end of file +```html +<script type="text/javascript" src="<%=resource%>/xhr.js"></script> +``` diff --git a/docs/Modules.md b/docs/Modules.md index 2897df9488..0c9852954e 100644 --- a/docs/Modules.md +++ b/docs/Modules.md @@ -1,94 +1,94 @@ -# Categories +# Reference: LuCI Modules + +See [online wiki](https://github.com/openwrt/luci/wiki/Modules) for latest version. + +## Categories The LuCI modules are divided into several category directories, namely: -* applications (Single applications or plugins for other modules or applications) -* i18n (Translation files) -* libs (Independent libraries) -* modules (Collections of applications) -* themes (Frontend themes) +* applications - Single applications or plugins for other modules +* i18n - Translation files +* libs - libraries of Luci +* modules - main modules of Luci itself +* protocols - network related plugins +* themes - Frontend themes -Each module goes into a subdirectory of any of this category-directories. +Each module goes into a subdirectory of this category-directories. -# Module directory +## Module directory The contents of a module directory are as follows: -## Makefile +### Makefile This is the module's makefile. If the module just contains Lua sourcecode or resources then the following Makefile should suffice. - - include ../../build/config.mk - include ../../build/module.mk - - -If you have C(++) code in your module your Makefile should at least contain the following things. - - include ../../build/config.mk - include ../../build/gccconfig.mk - include ../../build/module.mk - - compile: - # Commands to compile and link your C-code - # and to install them under the dist/ hierarchy - - clean: luaclean - # Commands to clean your compiled objects - +```Makefile +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=Title of my example applications +LUCI_DEPENDS:=+some-package +libsome-library +luci-app-anotherthing + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature +``` +If you have C(++) code in your module you should include a `src/` subdirectory containing another Makefile supporting a `clean`, a `compile` and an `install` target. +The `install` target should deploy its files relative to the predefined `$(DESTDIR)` variable, e.g. +``` +mkdir -p $(DESTDIR)/usr/bin; cp myexecutable $(DESTDIR)/usr/bin/myexecutable +``` -## src -The *src* directory is reserved for C sourcecode. +### src +The `src` directory is reserved for C sourcecode. -## luasrc -*luasrc* contains all Lua sourcecode files. These will automatically be stripped or compiled depending on the Make target and are installed in the LuCI installation directory. +### luasrc +`luasrc` contains all Lua sourcecode files. These will automatically be stripped or compiled depending on the Make target and are installed in the LuCI installation directory. -## lua -*lua* is equivalent to _luasrc_ but containing Lua files will be installed in the Lua document root. +### lua +`lua` is equivalent to `luasrc` but containing Lua files will be installed in the Lua document root. -## htdocs -All files under *htdocs* will be copied to the document root of the target webserver. +### htdocs +All files under `htdocs` will be copied to the document root of the target webserver. -## root -All directories and files under *root* will be copied to the installation target as they are. +### root +All directories and files under `root` will be copied to the installation target as they are. -## dist -*dist* is reserved for the builder to create a working installation tree that will represent the filesystem on the target machine. -*DO NOT* put any files there as they will get deleted. +### dist +`dist` is reserved for the builder to create a working installation tree that will represent the filesystem on the target machine. +**DO NOT** put any files there as they will get deleted. -## ipkg -*ipkg* contains IPKG package control files, like _preinst'', ''posinst'', ''prerm'', ''postrm''. ''conffiles_. +### ipkg +`ipkg` contains IPKG package control files, like `preinst`, `posinst`, `prerm`, `postrm`. `conffiles`. See IPKG documentation for details. -# OpenWRT feed integration -If you want to add your module to the LuCI OpenWRT feed you have to add several sections to the contrib/package/luci/Makefile. +## OpenWRT feed integration +If you want to add your module to the LuCI OpenWRT feed you have to add several sections to the `contrib/package/luci/Makefile`. For a Web UI applications this is: A package description: - - define Package/luci-app-YOURMODULE - $(call Package/luci/webtemplate) - DEPENDS+=+some-package +some-other-package - TITLE:=SHORT DESCRIPTION OF YOURMODULE - endef - - +```Makefile +define Package/luci-app-YOURMODULE + $(call Package/luci/webtemplate) + DEPENDS+=+some-package +some-other-package + TITLE:=SHORT DESCRIPTION OF YOURMODULE +endef +``` A package installation target: - - define Package/luci-app-YOURMODULE/install - $(call Package/luci/install/template,$(1),applications/YOURMODULE) - endef - +```Makefile +define Package/luci-app-YOURMODULE/install + $(call Package/luci/install/template,$(1),applications/YOURMODULE) +endef +``` A module build instruction: - - ifneq ($(CONFIG_PACKAGE_luci-app-YOURMODULE),) - PKG_SELECTED_MODULES+=applications/YOURMODULE - endif - - +```Makefile +ifneq ($(CONFIG_PACKAGE_luci-app-YOURMODULE),) + PKG_SELECTED_MODULES+=applications/YOURMODULE +endif +``` A build package call: - - $(eval $(call BuildPackage,luci-app-YOURMODULE)) - +```Makefile +$(eval $(call BuildPackage,luci-app-YOURMODULE)) +``` diff --git a/docs/ModulesHowTo.md b/docs/ModulesHowTo.md index 7efed83d67..ce9b8e5550 100644 --- a/docs/ModulesHowTo.md +++ b/docs/ModulesHowTo.md @@ -1,153 +1,171 @@ -*Note:* If you plan to integrate your module into LuCI, you should read the [wiki:Documentation/Modules Module Reference] before. +# HowTo: Write 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 if you are working with an installed version) and assume your LuCI installation is reachable through your webserver via '_/cgi-bin/luci*. +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 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) -If you are working with the development environment replace *lucidir_' with '''''/path/to/your/luci/checkout''/applications/myapplication/luasrc''' (this is a default empty module you can use for your experiments) and your LuCI installation can probably be reached via http://localhost:8080/luci/ after you ran '_make runhttpd*. +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. -# Show me the way (The dispatching process) +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. + +## Show me the way (The dispatching process) To write a module you need to understand the basics of the dispatching process in LuCI. LuCI uses a dispatching tree that will be 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 -will be resolved to foo.bar.baz +The CGI-environment variable `PATH_INFO` will be used as the path in this dispatching tree, e.g.: `/cgi-bin/luci/foo/bar/baz` +will be resolved to `foo.bar.baz` -To register a function in the dispatching tree, you can use the *entry*-function of _luci.dispatcher_. entry takes 4 arguments (2 are optional): - - entry(path, target, title=nil, order=nil) - +To register a function in the dispatching tree, you can 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 ones of which the 3 most important (call, template, cbi) are described later on 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) +* `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 ones 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 +* `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 # It's all about names (Naming and the module file) -Now that you know the basics about dispatching, we can start writing modules. But before you have to choose the category and name of your new digital child. +Now that you know the basics about dispatching, we can start writing modules. Now, 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) -We assume you want to create a new application "myapp" with a module "mymodule". +function index() -So you have to create a new subdirectory *_lucidir''/controller/myapp''' with a file '_mymodule.lua* with the following content: - - module("luci.controller.myapp.mymodule", package.seeall) - - function index() - - end - +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. +The `index`-Function will be used to register actions in the dispatching tree. -# Teaching your new child (Actions) -So it is there and has a name but it has no actions. +## 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 begun in the last step. +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 so that its content looks like this example: +### Actions +Reopen `lucidir/controller/myapp/mymodule.lua` and just add a function to it with: +```lua +module("luci.controller.myapp.mymodule", package.seeall) - - 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 - +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 type */cgi-bin/luci/click/here/now_' ('_[http://localhost:8080/luci/click/here/now]* if you are using the development environment) in your browser. +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. -You see these action functions simple have to be added to a dispatching entry. +These action functions simply have to be added to a dispatching entry. -As you might or might 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* +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 a text or some interesting familiy photos it may be enough to use a 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 called "dirty" by other developers. +### 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: +Now let's create a little template `lucidir/view/myapp-mymodule/helloworld.htm` with the content: - - <%+header%> - <h1><%:Hello World%></h1> - <%+footer%> - +```html +<%+header%> +<h1><%:Hello World%></h1> +<%+footer%> +``` -and add the following line to the index-Function of your module file. - - entry({"my", "new", "template"}, template("myapp-mymodule/helloworld"), "Hello world", 20).dependent=false - +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 type */cgi-bin/luci/my/new/template_' ('_[http://localhost:8080/luci/my/new/template]* if you are using the development environment) in your browser. +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 fancy <% %>-Tags, these are [wiki:Documentation/Templates|template markups] used by the LuCI template processor. +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. -## <a name=cbimodels></a> CBI models -The CBI is one of the uber coolest features of LuCI. It creates a formular 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 a XHTML form and reading and writing the UCI file. - -So let's be serious at least for this paragraph and create a real pratical example *_lucidir_/model/cbi/myapp-mymodule/netifaces.lua* with the following contents: - - - 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")) -- Ja, das ist eine i18n-Funktion ;-) - - 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 don't forget to add something like this to your module's index-Function. - - entry({"admin", "network", "interfaces"}, cbi("myapp-mymodule/netifaces"), "Network interfaces", 30).dependent=false - - -There are many more features, see [wiki:Documentation/CBI the CBI reference] and the modules shipped with LuCI. +### 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/README.md b/docs/README.md index 0366a64559..596f61104c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,7 @@ # LuCI Documentation +See Wiki [LuCI Technical Reference](https://openwrt.org/docs/techref/luci) + ## API Reference - [Client side JavaScript APIs](jsapi/index.html) diff --git a/docs/Templates.md b/docs/Templates.md index adf019c01a..ed51bcf4d8 100644 --- a/docs/Templates.md +++ b/docs/Templates.md @@ -1,65 +1,68 @@ +## 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 instantiated and each instance can has a different scope. As every template processor. LuCI supports several special markups. Those are enclosed in `<% %>`-Tags. +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. +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 +## Builtin functions and markups +### Including Lua code *Markup:* - - <% code %> - +``` +<% code %> +``` - -## Writing variables and function values +### Writing variables and function values *Syntax:* - - <% write (value) %> - +``` +<% write (value) %> +``` *Short-Markup:* - - <%=value%> - +``` +<%=value%> +``` -## Including templates +### Including templates *Syntax:* - - <% include (templatename) %> - +``` +<% include (templatename) %> +``` *Short-Markup:* - - <%+templatename%> - +``` +<%+templatename%> +``` -## Translating +### Translating *Syntax:* - - <%= translate("Text to translate") %> - - +``` +<%= translate("Text to translate") %> +``` *Short-Markup:* - - <%:Text to translate%> - +``` +<%:Text to translate%> +``` -## Commenting +### Commenting *Markup:* - - <%# comment %> - - -# Builtin constants -| Name | Value | ----------|--------- -|`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| +``` +<%# 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/ThemesHowTo.md b/docs/ThemesHowTo.md index ae6f8e09cd..0cc8f15b3c 100644 --- a/docs/ThemesHowTo.md +++ b/docs/ThemesHowTo.md @@ -1,76 +1,81 @@ # HowTo: Create Themes -*Note:* You should read the [Module Reference](Modules.md) and the [Template Reference](Templates.md) before. +**Note:** You should read the [Module Reference](./Modules.md) and the [Template Reference](./Templates.md) before. -We assume you want to call your new theme _mytheme_. Make sure you replace this by your module name every time this is mentionend in this Howto. +We assume you want to call your new theme `mytheme`. +Make sure you replace this by your module name everytime this is mentionend in this Howto. +## Creating the structure +At first create a new theme directory `themes/luci-theme-mytheme`. +Create a `Makefile` inside your theme directory with the following content: +```Makefile +include $(TOPDIR)/rules.mk -# Creating the structure -At first create a new theme directory *themes/_mytheme_*. +LUCI_TITLE:=Title of mytheme -Create a _Makefile_ inside your theme directory with the following content: - - include ../../build/config.mk - include ../../build/module.mk - +include ../../luci.mk +# call BuildPackage - OpenWrt buildroot signature +``` Create the following directory structure inside your theme directory. * ipkg * htdocs * luci-static - * _mytheme_ + * `mytheme` * luasrc * view * themes - * _mytheme_ + * `mytheme` * root * etc * uci-defaults +## Designing +Create two LuCI HTML-Templates named `header.htm` and `footer.htm` under `luasrc/view/themes/mytheme`. +The `header.htm` will be included at the beginning of each rendered page and the `footer.htm` at the end. +So your `header.htm` will probably contain a DOCTYPE description, headers, +the menu and layout of the page and the `footer.htm` will close all remaining open tags and may add a footer bar. +But hey that's your choice you are the designer ;-). -# Designing -Create two LuCI HTML-Templates named _header.htm'' and ''footer.htm'' under *luasrc/view/themes/''mytheme_*. -The _header.htm'' will be included at the beginning of each rendered page and the ''footer.htm_ at the end. -So your _header.htm'' will probably contain a DOCTYPE description, headers, the menu and layout of the page and the ''footer.htm_ will close all remaining open tags and may add a footer bar but hey that's your choice you are the designer ;-). +Just make sure your `header.htm` begins with the following lines: +``` +<% +require("luci.http").prepare_content("text/html") +-%> +``` -Just make sure your _header.htm_ *begins* with the following lines: - - <% - require("luci.http").prepare_content("text/html") - -%> - +This makes sure your content will be sent to the client with the right content type. +Of course you can adapt `text/html` to your needs. -This makes sure your content will be sent to the client with the right content type. Of course you can adapt _text/html_ to your needs. +Put any stylesheets, Javascripts, images, ... into `htdocs/luci-static/mytheme`. +You should refer to this directory in your header and footer templates as: `<%=media%>`. +That means for a stylesheet `htdocs/luci-static/mytheme/cascade.css` you would write: +```html +<link rel="stylesheet" type="text/css" href="<%=media%>/cascade.css" /> +``` -Put any stylesheets, Javascripts, images, ... into *htdocs/luci-static/_mytheme_*. -You should refer to this directory in your header and footer templates as: _<%=media%>''. That means for a stylesheet *htdocs/luci-static/''mytheme_/cascade.css* you would write: - - <link rel="stylesheet" type="text/css" href="<%=media%>/cascade.css" /> - - - - -# Making the theme selectable +## Making the theme selectable If you are done with your work there are two last steps to do. -To make your theme OpenWRT-capable and selectable on the settings page you should now create a file *root/etc/uci-defaults/luci-theme-_mytheme_* with the following contents: - - #!/bin/sh - uci batch <<-EOF - set luci.themes.MyTheme=/luci-static/mytheme - commit luci - EOF - - -and another file *ipkg/postinst* with the following content: - - #!/bin/sh - [ -n "${IPKG_INSTROOT}" ] || { - ( . /etc/uci-defaults/luci-theme-mytheme ) && rm -f /etc/uci-defaults/luci-theme-mytheme - } - - -This is some OpenWRT magic to correctly register the template with LuCI when it gets installed. +To make your theme OpenWrt-capable and selectable on the settings page you should now create a file `root/etc/uci-defaults/luci-theme-mytheme` with the following contents: +```sh +#!/bin/sh +uci batch <<-EOF + set luci.themes.MyTheme=/luci-static/mytheme + commit luci +EOF +exit 0 +``` + +and another file `ipkg/postinst` with the following content: +```sh +#!/bin/sh +[ -n "${IPKG_INSTROOT}" ] || { + ( . /etc/uci-defaults/luci-theme-mytheme ) && rm -f /etc/uci-defaults/luci-theme-mytheme +} +``` + +This is some OpenWrt magic to correctly register the template with LuCI when it gets installed. That's all. Now send your theme to the LuCI developers to get it into the development repository - if you like. diff --git a/docs/i18n.md b/docs/i18n.md index 226a406c2a..442ec2d0dd 100644 --- a/docs/i18n.md +++ b/docs/i18n.md @@ -1,19 +1,103 @@ -# General -Translations are saved in the folder po/ for each module and application. You find the reference in po/templates/<package>.pot. The actual translation files can be found at po/[lang]/[package].po . +# Internationalization (i18n) -In order to use the commands below you need to have the _gettext'' utilities (''msgcat'', ''msgfmt'', ''msgmerge_) installed on your system. +See [online wiki](https://github.com/openwrt/luci/wiki/i18n) for latest version. -# Rebuild po files +## 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 as they do with all the existing translations. + +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 then use the `<br />` HTML tag: +```js +var mystr = _('Port number.<br />' + + 'E.g. 80 for HTTP'); +``` + +To simplify a job for translators it may be better to split into separate keys without the `<br />`: +```js +var mystr = _('Port number.') + '<br />' + + _('E.g. 80 for HTTP'); +``` +Please use `<br />` and **not** `<br>` or `<br/>`. + +If you have a link inside a translation then try to move its attributes out of a translation key like: +```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 when making a link that 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 `%:` is actually invoking 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/`. +You find the reference in `po/templates/<package>.pot`. +The actual 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 you can install with `sudo apt install gettext`. + +### Rebuild po files If you want to rebuild the translations after you made changes to a package this is an easy way: - - ./build/i18n-scan.pl applications/[application] > applications/[application]/po/templates/[application_basename].pot - ./build/i18n-update.pl applications/[application]/po - - Example: - ./build/i18n-scan.pl applications/luci-app-firewall > applications/luci-app-firewall/po/templates/firewall.pot - ./build/i18n-update.pl applications/luci-app-firewall/po - (note that the directory argument can be omitted for i18n-update.pl to update all apps) - -*Note:* Some packages share translation files, in this case you need to scan through all their folders. The first command from above should then be: - - ./build/i18n-scan.pl applications/[package-1] applications/[package-2] applications/[package-n] > [location of shared template]/[application].pot + + ./build/i18n-scan.pl applications/[application] > applications/[application]/po/templates/[application_basename].pot + ./build/i18n-update.pl applications/[application]/po + +Example: + + ./build/i18n-scan.pl applications/luci-app-acl > applications/luci-app-acl/po/templates/acl.pot + ./build/i18n-update.pl applications/luci-app-acl/po + +Note that the directory argument can be omitted for `i18n-update.pl` to update all apps. + +Some packages share translation files, in this case you need to scan through all their folders. +The first command from above should then be: + + ./build/i18n-scan.pl applications/[package-1] applications/[package-2] applications/[package-n] > [location of shared template]/[application].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 `/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 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 language in [System /Language and Style](http://192.168.1.1/cgi-bin/luci/admin/system/system) and check the translation.
\ No newline at end of file |