{"id":17423032,"url":"https://github.com/step-/i18n-table","last_synced_at":"2025-07-31T16:16:40.900Z","repository":{"id":145039763,"uuid":"334876021","full_name":"step-/i18n-table","owner":"step-","description":"Minimal framework to speed up translated shell scripts","archived":false,"fork":false,"pushed_at":"2023-06-14T08:27:26.000Z","size":83,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-06T04:44:32.862Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/step-.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-01T08:11:16.000Z","updated_at":"2022-02-19T18:03:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"88d9c4ec-b43d-4bf8-9c71-786c22ef901b","html_url":"https://github.com/step-/i18n-table","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/step-%2Fi18n-table","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/step-%2Fi18n-table/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/step-%2Fi18n-table/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/step-%2Fi18n-table/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/step-","download_url":"https://codeload.github.com/step-/i18n-table/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246390864,"owners_count":20769478,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-17T04:39:59.280Z","updated_at":"2025-03-30T23:18:18.376Z","avatar_url":"https://github.com/step-.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# i18n-table\n\nMinimal framework to speed up translated shell scripts\n\n* Start a localized script faster with [i18n_table.sh](i18n_table.sh) and [xgettext.sh](xgettext.sh)\n* Enable occasional translators\n* Better organize translation resources\n* Decrease overhead\n\n## Introduction\n\nThis is a simple idea: combining multiple runs of the same command into a single run to cut down on process execution overhead.  Instead of running `gettext` many times--one for each localized string--your script can pack all strings together and run `gettext -es` only once.  This reduces start time proportionally to approximately `N * S`, where `N` is the number of `gettext` calls in your original script and `S` is the time it takes to spawn a sub-shell.¹\n\nAnother possible benefit is easier engagement with human translators.  By keeping all localized strings in a single file you can give occasional translators just the `i18n_table.sh` file for your script, and tell them to edit it with a text editor.  They don't need more complex translation tools.\n\nIf you are dealing with more sophisticated translators who know how to use the \"GNU gettext\" tools, and expect to be handed a \".pot\" file, this project includes\n`xgettext.sh`, which will help you create an _annotated_ .pot file from your script.\n\nSome projects known to use these tools:\n\n* SMB-browser² for [fatdog64](http://distro.ibiblio.org/fatdog/web)\n* [find-n-run](https://github.com/step-/find-n-run)²\n* fatdog-wireless-antenna² in [scripts-to-go](https://github.com/step-/scripts-to-go)\n* [f4](https://github.com/step-/f4)²\n* [desktop-wallpaper](https://github.com/step-/desktop-wallpaper)²\n\n¹ `N * time(gettext) / time(gettext) = N` if `gettext` runs in the script's shell.  However, the typical gettext stanza is `var=$(gettext \"string\")`, which spawns a sub-shell to run `gettext`, and that is where we get `S` from.  \n² Disclosure: I am the author/maintainer of this project.  \n\n## Should you use these tools in your project?\n\nIf your script calls `gettext` more than a couple of times, then time gains stack up, and you should try i18n\\_table.  I recommend trying with a new project first because when i18n\\_table is introduced as a afterthought your code must be restructured a bit -- something you might not be willing to do.  Read the next section to see what I mean.\n\nIf you are also interested in enabling easier translator engagements as I mentioned then you should try i18n\\_table.\n\n## Defining localization strings\n\n**[i18n_table.sh](i18n_table.sh)**\n\nThis file shows a sample `i18n_table` function and demostrates some common resource string usage cases.  You should adapt the function for your project then add `i18n_table` to your script.  Include it either directly or indirectly by sourcing the file.  In the direct case generate the .pot file by running:\n\n```sh\nxgettext.sh your-script-name \u003e template.pot\n```\n\nIn the sourced case adapt the extra files that are presented in section _Extra files_ further down.\n\n### POSIX shell\n\nThe scripts and examples in this repository are intended to be compatible with the POSIX shell.  Extensions for bash are treated in side notes of the [README](README.md) file.\n\n### i18n_table function\n\nWhen you use `i18n_table` rewrite your script and change each call to `$(gettext \"string\")` to a variable name referencing \"string\" in your `i18n_table`.  For instance:\n\n```sh\necho \"$(gettext \"Hello world!\")\"\n```\n\nbecomes\n\n```sh\n# define resources\ni18n_table() {\n# reindent with care\n\t{\n\tread i18n_hello world\n\t} \u003c\u003c EOF\n$(gettext -es -- \\\n\"Hello world!\\n\" \\\n)\nEOF\n}\n\n# load resources\ni18n_table\n\n# use resources\necho \"$i18n_hello_world\"\n```\n\nThe function name, that is `i18n_table`, must be either the first word in a line or the second word when the first word is `function`.\nEach variable name, like `i18n_hello` in the example above, must start with `i18n_`.  The rest of the name is your choice.  I like to use something descriptive of the English text but any valid identifier will do.\n\nIt is worth mentioning that your script _can_ also call `gettext` directly.  You are not required to move _all_ localization strings to your `i18n_table` function.  For instance, if you need `ngettext` or `eval_gettext` it might be easier to define the corresponding strings outside `i18n_table`.  There are ways to reframe even `ngettext` and `eval_gettext` into the i18n\\_table method but the extra work required may not be worth the effort.\n\nIf you want to split localization resources into multiple groups, define and call an `i18n_table` function for each group because the extraction tool looks for that name specifically.\n\n**Side note: bash arrays**\n\nHere is magic sauce to read all strings into a bash array¹'²:\n\n```sh\ntypeset -a i18n_array\ntypeset -i i=0\n\t{\n\twhile read i18n_array[$i]; do : $((++i)); done\n\t} \u003c\u003c EOF\n$(gettext -es \\\n\"item 0\\n\" \\\n\"item 1\\n\" \\\n)\nEOF\n```\n\n¹ Why not using `mapfile`, `\u003c( )`, `\u003c\u003c\u003c`, `IFS=...`, etc. instead of a `read` loop?  I tried many variations, but I always come back to `read` to ensure that items don't include leading white space.\n\n² Why the braces?  Indeed braces aren't needed; one could more simply write `while ... done \u003c\u003c EOF`.  However, the extraction tool can annotate output with `i18n_` variable names, such as `i18n_array`, when it finds them _inside_ a pair of braces_.  That is why the magic sauce shows the braces.  If you don't need name annotations omit the braces altogether.  Moreover, an array will result in just one annotation, which isn't very useful anyway.\n\n\n## Extracting localization strings\n\n**[xgettext.sh](xgettext.sh)**\n\nUse `xgettext.sh` to generate a .pot file from your script.  By default comments and location information are extracted from the function body, and resource strings are annotated with the variable name they refer to.  You can turn off comments and annotations via `xgettext.sh` command options.  To affect the standard `xgettext` run pass `xgettext` options to `xgettext.sh`.\n\n**LIMITATION**\n\n**Currently, xgettext.sh is able to parse strings delimited by double quotes only.  Single quotes and the `$''` syntax are not supported. Contact me if you need this feature for your project.**\n\n```\nUsage: xgettext.sh [OPTIONS] ['--' xgettext_OPTIONS ...] FILE\"\n\nOPTIONS:\n  --help    Print this message and exit. See also xgettext --help.\n  --no-c1   Don't output [c1] lines.\n  --no-c2   Don't output [c2] lines.\n  --test    Generate test translation.\nxgettext_OPTIONS:\n  Any xgettext(1) option.  Default presets: -o - -LShell\n\nIf the script includes a function named i18n_table:\n[c1] Comment lines within the i18n_table body are reproduced with prefix \"#.\"\n[c2] For lines starting with \"read i18n_\u003cstring\u003e\", i18n_\u003cstring\u003e is\n     prefixed with \"#.\" then output above its corresponding MSGID.\n\nLocation information is generated for lines [c0] and [c2] unless\nxgettext_OPTIONS includes option --no-location.\n\nInside the `$(gettext -es ...)` block, a line that ends with \"##\" is ignored.\nA line that ends with \"\u003c\u003c\u003c##\" marks the start of a block of ignored lines,\nwhich need not end with \"##\" themselves. The block ends at the next line that\nends with \"\u003e\u003e\u003e##\".\n```\n\n## Examples and extras\n\nI have successfully used these tools with small to mid-sized shell scripting projects. My projects tend to have similar source tree structures for shell and markdown files. I wrote `make-pot.sh` - a script that generates a pot file using xgettext.sh. Alternatively, I use a `Makefile` for the same purpose. Both are included in the repo. You could adapt them for your own project.\n\n### make-pot.sh\n\nThis [make-pot.sh](make-pot.sh) script takes a configuration file as input.  The supplied sample configuration file [cfg/make-pot.cfg](cfg/make-pot.cfg) can be copied and customized for each new project.\n\nAdvanced feature not available with `Makefile`: extract gettext message IDs from markdown files (requires the `mdview` command).\n\nNote that using `make-pot.sh` is just an option because xgettext.sh is all you need to generate a .pot file for the i18n\\_table function.\n\n### Makefile\n\nThe supplied sample [Makefile](Makefile) and [project.mk](project.mk) could be used instead of `make-pot.sh`.  Compared to the latter the makefiles are easier to configure and maintain. To configure, make a backup copy of `project.mk` then edit the basic settings in `project.mk` to reflect your project. You should not need to edit `Makefile`.\n\nFeature not available with `make-pot.sh`: insert comment block from a plain text file, like [com/default_notes.com](com/default_notes.com). Alternatively, `Makefile` can also insert comments from a `make-pot.cfg` configuration file, just like `make-pot.sh` can.\n\nNote that using `Makefile` is just an option because xgettext.sh is all you need to generate a .pot file for the i18n\\_table function.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstep-%2Fi18n-table","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstep-%2Fi18n-table","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstep-%2Fi18n-table/lists"}