mirror of
				https://git.openwrt.org/project/luci.git
				synced 2025-10-31 10:49:03 +08:00 
			
		
		
		
	docs: Synchronize with Wiki
The Wiki on GitHub has newer version of documentation. Still we may have references to the docs folder. Update all pages but also add a notice: See [online wiki](https://github.com/openwrt/luci/wiki/) for latest version. The LAR page is removed because there no any mentions of it anywhere in Luci. This closes #2360 Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
This commit is contained in:
		
							
								
								
									
										159
									
								
								docs/CBI.md
									
									
									
									
									
								
							
							
						
						
									
										159
									
								
								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. | ||||
|  | ||||
| @ -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 | ||||
| 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 | ||||
| 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`. | ||||
|  | ||||
|  | ||||
| # 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. | ||||
| ## 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. | ||||
|  | ||||
| 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_*. | ||||
| Example: | ||||
| ```sh | ||||
| curl http://<hostname>/cgi-bin/luci/rpc/auth --data ' | ||||
| { | ||||
|   "id": 1, | ||||
|   "method": "login", | ||||
|   "params": [ | ||||
|     "youruser", | ||||
|     "somepassword" | ||||
|   ] | ||||
| }' | ||||
| ``` | ||||
|  | ||||
| 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) | ||||
| ## Exported Libraries | ||||
| ### uci | ||||
| The UCI-Library `/rpc/uci` offers functionality to interact with the Universal Configuration Interface. | ||||
|  | ||||
| ## 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) | ||||
| **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) | ||||
|  | ||||
| ## fs | ||||
| The Filesystem library */rpc/fs* offers functionality to interact with the filesystem on the host machine. | ||||
| *Exported Functions:* | ||||
| Example: | ||||
| ```sh | ||||
| curl http://<hostname>/cgi-bin/luci/rpc/uci?auth=yourtoken --data ' | ||||
| { | ||||
|   "method": "get_all", | ||||
|   "params": [ "network" ] | ||||
| }' | ||||
| ``` | ||||
|  | ||||
| * [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. | ||||
| ### fs | ||||
| The Filesystem library `/rpc/fs` offers functionality to interact with the filesystem on the host machine. | ||||
|  | ||||
| ## 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.* | ||||
| **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) | ||||
|  | ||||
| ## 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) | ||||
|  | ||||
							
								
								
									
										87
									
								
								docs/LAR.md
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								docs/LAR.md
									
									
									
									
									
								
							| @ -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. | ||||
							
								
								
									
										161
									
								
								docs/LMO.md
									
									
									
									
									
								
							
							
						
						
									
										161
									
								
								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 | ||||
|  | ||||
| # Reference Implementation | ||||
| 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; | ||||
| 	} | ||||
|  | ||||
| 	/* 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. | ||||
|  | ||||
| @ -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") | ||||
| 	 | ||||
| ```lua | ||||
| tr("some_key", "Some Text") | ||||
| ``` | ||||
|  | ||||
| ## Translation File Format | ||||
| ### Translation File Format | ||||
|  | ||||
| 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 catalogs are now maintained in `*.po` format files. | ||||
| During build those get translated into [*.lmo archives](https://github.com/openwrt/luci/wiki/LMO). | ||||
|  | ||||
| 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. | ||||
|  | ||||
| ### 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-sided validator function can now return custom error messages to provide better feedback on invalid input. | ||||
| Server-side validator functions can now return custom error messages to provide better feedback on invalid input. | ||||
|  | ||||
| 	 | ||||
| 	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 | ||||
| 	 | ||||
| ```lua | ||||
| opt = section:option(Value, "optname", "Title Text") | ||||
|  | ||||
| ## Tabs | ||||
| 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()_. | ||||
| 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") | ||||
| 	... | ||||
| 	 | ||||
| ```lua | ||||
| sct = map:section(TypedSection, "name", "type", "Title Text") | ||||
|  | ||||
| The _tab()_ function is declares a new tab and takes up to three arguments: | ||||
| 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> | ||||
| 	 | ||||
| ```html | ||||
| <script type="text/javascript" src="<%=resource%>/xhr.js"></script> | ||||
| ``` | ||||
|  | ||||
							
								
								
									
										130
									
								
								docs/Modules.md
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								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 | ||||
| 	 | ||||
| ```Makefile | ||||
| include $(TOPDIR)/rules.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 | ||||
| 	 | ||||
| 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)) | ||||
| ``` | ||||
|  | ||||
| @ -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`. | ||||
|  | ||||
| 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*. | ||||
| 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) | ||||
|  | ||||
| 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. | ||||
|  | ||||
| # Show me the way (The dispatching process) | ||||
| ## 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. | ||||
|  | ||||
| We assume you want to create a new application "myapp" with a module "mymodule". | ||||
| Let's assume you want to create a new application `myapp` with a module `mymodule`. | ||||
|  | ||||
| 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 | ||||
| 	 | ||||
| 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. | ||||
| 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. | ||||
| ### 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 real pratical example *_lucidir_/model/cbi/myapp-mymodule/netifaces.lua* with the following contents: | ||||
| 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: | ||||
|  | ||||
| 	 | ||||
| 	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 | ||||
| 	 | ||||
| ```lua | ||||
| m = Map("network", "Network") -- We want to edit the uci config file /etc/config/network | ||||
|  | ||||
| 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 | ||||
| 	 | ||||
| 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 | ||||
|  | ||||
| There are many more features, see [wiki:Documentation/CBI the CBI reference] and the modules shipped with LuCI. | ||||
| 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. | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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 %> | ||||
| 	 | ||||
| ``` | ||||
| <%# 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| | ||||
| ## 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 | ||||
|  | ||||
| @ -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: | ||||
| 	 | ||||
| 	<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: | ||||
| ```html | ||||
| <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 | ||||
| 	 | ||||
| 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: | ||||
| 	 | ||||
| 	#!/bin/sh | ||||
| 	[ -n "${IPKG_INSTROOT}" ] || { | ||||
| 	        ( . /etc/uci-defaults/luci-theme-mytheme ) &&        rm -f /etc/uci-defaults/luci-theme-mytheme | ||||
| 	} | ||||
| 	 | ||||
| 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. | ||||
| 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. | ||||
|  | ||||
							
								
								
									
										114
									
								
								docs/i18n.md
									
									
									
									
									
								
							
							
						
						
									
										114
									
								
								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. | ||||
		Reference in New Issue
	
	Block a user
	 Sergey Ponomarev
					Sergey Ponomarev