{"id":15766913,"url":"https://github.com/seriyps/gettexter","last_synced_at":"2025-10-03T15:36:48.744Z","repository":{"id":14331312,"uuid":"17040794","full_name":"seriyps/gettexter","owner":"seriyps","description":"GNU-gettext compatible Erlang translation app.","archived":false,"fork":false,"pushed_at":"2023-09-08T22:51:51.000Z","size":71,"stargazers_count":17,"open_issues_count":2,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-25T01:43:55.064Z","etag":null,"topics":["erlang","gnu-gettext","i18n","l10n","mo-files","plural"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/seriyps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2014-02-21T00:38:34.000Z","updated_at":"2024-12-10T10:13:48.000Z","dependencies_parsed_at":"2024-10-25T12:48:17.230Z","dependency_job_id":"c5e697c3-02a2-4e6e-8fd4-d82f64f2e58a","html_url":"https://github.com/seriyps/gettexter","commit_stats":{"total_commits":36,"total_committers":4,"mean_commits":9.0,"dds":"0.11111111111111116","last_synced_commit":"65839c2ff04f84f897cf81192a47fef74ba47b41"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seriyps%2Fgettexter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seriyps%2Fgettexter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seriyps%2Fgettexter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seriyps%2Fgettexter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seriyps","download_url":"https://codeload.github.com/seriyps/gettexter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252802433,"owners_count":21806500,"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":["erlang","gnu-gettext","i18n","l10n","mo-files","plural"],"created_at":"2024-10-04T13:03:32.258Z","updated_at":"2025-10-03T15:36:43.703Z","avatar_url":"https://github.com/seriyps.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"gettexter - gettext for erlang\n==============================\n\nGoals of this project is:\n\n* To be more or less compatible with GNU gettext (API and tools).\n* To be as fast as possible in concurrent environment.\n\nYou may use this app to translate libraries as well as your own business apps\nand use them in single installation with no conflicts.\nLibraries may ship their own translations inside their distribution.\n\nQuick Start\n-----------\n\nYou definitely should be familiar with [GNU gettext](http://www.gnu.org/software/gettext/manual/gettext.html)\nbefore start using this library.\n\nIn Erlang code include `shortcuts.hrl` header file.\n\n```erlang\n-include_lib(\"gettexter/include/shortcuts.hrl\").\n```\n\nMark your translatable strings with:\n\n* `?_(\"\")` - `gettext` (regular)\n* `?N_(\"\", \"\", N)` - `ngettext` (plural)\n* `?P_(Ctx, \"\")` - `pgettext` (respecting msgctx)\n* `?NP_(Ctx, \"\", \"\", N)` - `npgettext` (ngettext + pgettext)\n* `?D_(Domain, \"\")` - `dgettext` (domain/namespace lookups, plus `?DN_`, `?DP_`, `?DNP_`)\n* `?NO_(\"\")` - gettext noop\n\n\n```erlang\n% file: my_app/src/my_module.erl\n-module(my_module).\n-define(GETTEXT_DOMAIN, my_app).\n-include_lib(\"gettexter/include/shortcuts.hrl\").\n\nmain(Name, What, N) -\u003e\n    gettexter:bindtextdomain(?GETTEXT_DOMAIN, \"/../priv/locales\"), % from where load locales\n    gettexter:textdomain(?GETTEXT_DOMAIN), % domain for current process\n    gettexter:setlocale(lc_messages, \"en\"),  % locale for current process\n\n    Question = case What of\n                 sleep -\u003e ?NO_(\"Wanna sleep?\");\n                 eat -\u003e ?NO_(\"Wanna eat?\")\n               end,\n    Time = io_lib:format(?N_(\"It's ~p hour\", \"It's ~p hours\", N), [N]),\n    %% /* Translators: this is hello message */\n    io:format(?_(\"Hello, ~p! ~ts. ~ts\"), [Name, Time, ?_(Question)]).\n```\n\nExtract messages from sources to .pot file by `xgettext` command. Take note\nthat this only work for string literals and not binaries.\n\n```bash\nexport APP=my_app\n\nxgettext -o priv/locale/${APP}.pot --package-name=${APP} -d ${APP} --sort-by-file -L C \\\n    --keyword='NO_' --keyword='_' --keyword='N_:1,2' \\\n    --keyword='P_:1c,2' --keyword='NP_:1c,2,3' \\\n    --keyword='D_:2' --keyword='DN_:2,3' --keyword='DP_:2c,3' --keyword='DNP_:2c,3,4' \\\n    --add-comments=Translators \\\n    src/my_module.erl src/*.erl\n```\n\nInitialize new locale's .po file by `msginit`\n\n```bash\nmkdir -p priv/locale/ru/LC_MESSAGES/\nmsginit -i priv/locale/${APP}.pot -o priv/locale/ru/LC_MESSAGES/${APP}.po --locale=ru\n```\n\nOr actualize existing locale's .po file by `msgmerge`\n\n```bash\nmsgmerge -U priv/locale/ru/LC_MESSAGES/${APP}.po priv/locale/${APP}.pot\n```\n\nWhen translations are finished, generate locale's binary .mo files by `msgfmt`\n\n```bash\nmsgfmt --check -o priv/locale/ru/LC_MESSAGES/${APP}.mo priv/locale/ru/LC_MESSAGES/${APP}.po\n```\nIt's **strongly recommended** to not add .mo files to your repository! So, add\n`*.mo` to .gitignore / .hgignore and generate them in compile-time (by rebar\npost-compile hook or so).\n\nAPI\n---\n\nApi tries to be compatible with [GNU gettext API](http://www.gnu.org/software/gettext/manual/gettext.html#gettext).\nIf you find some discrepancy (not explicitly documented) - please report.\n\n### Gettext lookups\nAll lookup functions are able to take both binaries or strings. They will\nreturn what is given to them. Mixed textual types is not supported. \n\nEach lookup function and macros has it's arity + 1 companion, which accept\nexplicit locale as last argument. So, `gettexter:gettext(text())` has\n`gettexter:gettext(text(), locale())`, `?_(text())` has `?_(text(), locale())`\nand so on.\n\nFor more information see the documentation.\n\n```erlang\ngettexter:gettext(text()) -\u003e text().  % '?_' macro\n```\nMain gettext call. Uses locale, activated by `setlocale/2`.\n\n```erlang\ngettexter:ngettext(Singular :: text(), Plural :: text(),\n                   Count :: integer()) -\u003e text().  % '?N_' macro\n```\nPlural gettext call.\n\n```erlang\ngettexter:pgettext(Context :: text(), text()) -\u003e text().  % '?P_'\ngettexter:pngettext(Context :: text(), text(), text(),\n                    integer()) -\u003e text().  % '?PN_'\n```\nGettext calls with respect to `msgctx` ('p' means 'particular').\n\n```erlang\ngettexter:dgettext(Domain :: atom(), text()) -\u003e text().  % '?D_'\n% and other 'gettexter:d{n,p,pn}gettext' plus '?D*_' macroses\n```\nGettext calls, which will search in a specific domain (namespace).\n(See `bindtextdomain`).\n\n### Gettext configuration\n\n```erlang\ngettexter:bindtextdomain(Domain :: atom(), LocaleDir :: file:filename()) -\u003e ok.\n```\nSetup directory from which .mo files will be loaded like\n`${LocaleDir}/${Locale}/LC_MESSAGES/${Domain}.mo`.\nDomain **MUST** be specified for library applications (see `dgettext`).\n\nBy default, `Domain` is `application:get_application()` and `LocaleDir` is\n`filename:absname(filename:join(code:priv_dir(Domain), \"locale\"))`.\nIf `LocaleDir` is relative, absolute path will be calculated, unlike original\n`gettext` does (relative to cwd), but relative to `code:priv_dir(Domain)`.\n\nThis function don't reload already loaded locales for `Domain`, so, should be called\nbefore `setlocale` or `ensure_loaded` calls.\n\nThis function usualy called only once at application initialization/configuration phase.\n\n```erlang\ngettexter:setlocale(lc_messages, Locale :: text()) -\u003e ok.\ngettexter:getlocale(lc_messages) -\u003e text() | undefined.\n```\nGet / Set default locale for **current process**. It also loads .mo files for\n`Locale` from the `LocaleDir`s for each `Domain` (if not loaded yet).\n1'st argumet currently has only one value - `lc_messages` atom.\n*Note: `getlocale` is not standard GNU gettext function.*\n\n```erlang\ngettexter:textdomain() -\u003e atom() | undefined.\ngettexter:textdomain(Domain :: atom()) -\u003e ok.\n```\nGet/Set default domain (namespace) for **current process**.\nIf you call it like `textdomain(my_app)`, then `gettext(Key)` calls will be\nconverted to `dgettext(my_app, Key)`.\nXXX: it's highly preferable to use `dgettext` directly and don't use this\nAPI function, if localized strings can be rendered in different processes.\n\n```erlang\ngettexter:bind_textdomain_codeset(Domain :: atom(), Charset :: string()) -\u003e ok.\n```\nSet encoding, on which all `gettexter:*gettext` responses should be converted.\nThis add significant performance overhead, and require 'iconv' dependency, if\n.po file's `Content-Type` and `Charset` isn't the same.\n\nXXX: Note, that `gettexter:gettext(Key)` call will be finally converted to\n`gettexter:dpgettext(undefined, undefined, Key)`, which will try to extract\n`Domain` and `Locale` from current process dictionary.\n\nXXX: When developing library, you may want to re-define `?_`, `?N_`, `?P_` macroses\nto call `dgettext` and use your module's domain by default.\nYou can do this by following trick:\n\n```erlang\n-define(GETTEXT_DOMAIN, my_domain).\n-include_lib(\"gettexter/include/shortcuts.hrl\").\n```\nThis will modify macroses (except `?_D*`) to use `d*gettext(my_domain, ...)` by default.\n\n### Proprietary APIs\n\nThis apis has no GNU gettext equiualents, but may be useful in Erlang apps.\n\n```erlang\ngettexter:which_domains(Locale) -\u003e [atom()].\n```\nWhich domains are loaded from .mo files to gettext server for `Locale`.\n\n```erlang\ngettexter:which_locales(Domain) -\u003e [locale()].\n```\nWhich locales are loaded from .mo files to gettext server for `Domain`.\n\n```erlang\ngettexter:ensure_loaded(TextDomain, lc_messages, Locale) -\u003e\n    {ok, already} | {ok, MoFile :: file:filename()} | {error, term()}.\n```\nEnsure, that locale is loaded from .mo file to gettexter server. If locale\nisn't loaded, all `gettext` lookups to it will return default value `Msgid`.\nThis function may be called at application start-up or configuration time,\nonce for each supported locale.\nIn case of error, all data for this combination of `Domain` and `Locale` will\nbe removed from gettexter server to avoid incomplete/broken data.\n\n```erlang\nreload(Domain) -\u003e list().\n\nreload(Domain, [locale()]) -\u003e [{locale(), ok, file:filename()} |\n                               {locale(), error, any()}].\n```\nRe-reads the phrases from `.mo` files bound by `bindtextdomain/2` (eg, if those files changed).\nFunction `reload/1` reloads ALL the locales of the domain `Domain`.\n\n```erlang\ngettexter:reset() -\u003e ok.\n```\nRemove all gettext stuff, added by `setlocale` or `textdomain`, from process\ndictionary (but not from locale data storage). May be used to reuse process\nfor the next client.\n\nGlossary\n--------\n\n* Domain: namespace for translations. In practice, the name of .po/.mo file, which\n          in most cases, named as your OTP application.\n* Locale: not strictly speaking, just name of translation's language, like \"en\",\n          \"en_GB\", \"ru\", \"pt_BR\", etc. Usualy Locale contains also rules of\n          plural form calculation, date/time/number formatting, currency etc.\n* LC_MESSAGES: locale category, which contains translated application's strings (in\n          .mo/.po format).\n\nUse with ErlyDTL\n----------------\n\n[Erlydtl](http://github.com/erlydtl/erlydtl) \u003e= 0.9.4 supports plural forms and contexts.\nSo, you can use all gettexter features with it. To enable translation features of\nerlydtl, you should wrap all translatable strings in `{%trans%}`, `{%blocktrans%}` and `_()`,\ngenerate .po and .mo files and then pass `translation_fun` and `locales` in compile-time, or\n`translation_fun` and `locale` in render-time.\n\nExample `translation_fun`:\n\n```erlang\nTransFun = fun({Str, {StrPlural, N}}, {Locale, Ctx}) -\u003e\n               gettexter:pngettext(Ctx, Str, StrPlural, N, Locale);\n              ({Str, {StrPlural, N}}, Locale) -\u003e\n               gettexter:ngettext(Str, StrPlural, N, Locale);\n              (Str, {Locale, Ctx}) -\u003e\n               gettexter:pgettext(Ctx, Str, Locale);\n              (Str, Locale) -\u003e\n               gettexter:gettext(Str, Locale)\n           end.\n```\n\nTemplate:\n```django\n{# file: src/tpl.dtl #}\n{%trans \"Hello\"%}\n{%trans \"Hello\" context \"ctx\"%}\n{% blocktrans count cnt=cnt %}Apple{%plural%}Apples{%endblocktrans%}\n```\n.po file\n```po\n#file: priv/locale/ru/LC_MESSAGES/test_app.po + compiled .mo\nmsgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Plural-Forms: nplurals=3; plural=n%10==1 \u0026\u0026 n%100!=11 ? 0 : n%10\u003e=2 \u0026\u0026 n\"\n\"%10\u003c=4 \u0026\u0026 (n%100\u003c10 || n%100\u003e=20) ? 1 : 2;\\n\"\n\nmsgid \"Hello\"\nmsgstr \"Привет\"\n\nmsgctxt \"ctx\"\nmsgid \"Hello\"\nmsgstr \"Контекстный привет\"\n\nmsgid \"Apple\"\nmsgid_plural \"Apples\"\nmsgstr[0] \"Яблоко\"\nmsgstr[1] \"Яблока\"\nmsgstr[2] \"Яблок\"\n```\nCode:\n```erlang\n1\u003e application:start(gettexter).\n2\u003e Domain = test_app.\n3\u003e % next step may be skipped, if `code:priv_dir(Domain)` is ok (if Domain is appname)\n3\u003e gettexter:bindtextdomain(Domain, \u003c/path/to/app/priv\u003e ++ \"/locale\").\n4\u003e gettexter:textdomain(Domain).\n5\u003e erlydtl:compile_file(\"src/tpl.dtl\", t).\n6\u003e TransFun = '....'. % see above\n7\u003e {ok, R} = t:render(),  io:format(\"~ts~n\", [iolist_to_binary(R)]).\nHello\nHello\n\nApple\n8\u003e {ok, R1} = t:render([{cnt, 2}], [{locale, \"ru\"}, {translation_fun, TransFun}]).\n9\u003e io:format(\"~ts~n\", [iolist_to_binary(R1)]).\nПривет\nКонтекстный привет\n\nЯблока\n\n```\n\nTODO\n----\n\n### Binary keys extraction\n\nExpression, `?_(\u003c\u003c\"...\"\u003e\u003e)` is not well handled by `xgettext`, so, variants:\n\n* Use `?NO_` hack: `Key = \u003c\u003c?NO_(\"text\")\u003e\u003e, Translated = ?_(Key)`\n\n* Write own extractor like xgettext, but for erlang code. Also, `.pot` serializer\n  will be needed then.\n\n* Send patches to GNU gettext.\n\n### Custom locale loaders\n\nMaybe allow `bindtextdomain/2` 2'nd argument be a `{M, F, A}` or `fun M:F/N` to\nallow locale loading customization?\n\n### Generate .beam module with compiled-in locales for extra-fast access.\n\nSince ETS lookups require heap copying, smth like static .beam module with\ncompiled-in phrases and plural rules (!!!) may  be generated.\nPros: extra fast access speed; no memory copying; compiled plural rules.\nCons: slow update; hackish.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseriyps%2Fgettexter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseriyps%2Fgettexter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseriyps%2Fgettexter/lists"}