{"id":21864799,"url":"https://github.com/gmugra/net.cactusthorn.localization","last_synced_at":"2025-03-21T21:11:37.181Z","repository":{"id":151118068,"uuid":"106851254","full_name":"Gmugra/net.cactusthorn.localization","owner":"Gmugra","description":"Java library for texts localization","archived":false,"fork":false,"pushed_at":"2023-12-05T22:18:14.000Z","size":227,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-26T15:33:42.234Z","etag":null,"topics":["internationalisation","java","java-library","java8","localization"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Gmugra.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":"2017-10-13T16:59:43.000Z","updated_at":"2020-11-28T13:47:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"e49f761e-e3f2-45d1-8cb3-9c82271e0f38","html_url":"https://github.com/Gmugra/net.cactusthorn.localization","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gmugra%2Fnet.cactusthorn.localization","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gmugra%2Fnet.cactusthorn.localization/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gmugra%2Fnet.cactusthorn.localization/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gmugra%2Fnet.cactusthorn.localization/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Gmugra","download_url":"https://codeload.github.com/Gmugra/net.cactusthorn.localization/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244868763,"owners_count":20523590,"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":["internationalisation","java","java-library","java8","localization"],"created_at":"2024-11-28T04:12:30.385Z","updated_at":"2025-03-21T21:11:37.175Z","avatar_url":"https://github.com/Gmugra.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# net.cactusthorn.localization\n\nJava library for texts localization\n\n## Why?\n\nThe localization possibilities which present in Java (java.text.*, java.util.Formatter and so on) are powerful but...\nNot always useful ASIS because of several issues:\n* pluralization based on java.text.ChoiceFormat is too primitive for a lot of languages. Need plural forms support.\n  * FYI: https://github.com/translate/l10n-guide/blob/master/docs/l10n/pluralforms.rst\n* parameters based on index-number are not convenient. Need \"named\" parameters.\n* java.text.Format subclasses (and, as result, everything what are using them) are not thread safe.\n  * Since Java 8, for date/time we have thread safe java.time.format.DateTimeFormatter, but numbers are still problem.\n* formats which you can use to format parameters are not flexible enough.\n  * You need \"format symbols\" there. At least.\n\n# Files\n* The library required path to directory with .properties files (UTF-8 encoded, since Java 6 java.util.Properties support that). \n* One \"main\" file per \"locale\". File names follow naming convention: languageTag.properties (e.g. **en-US.properties**, **ru-RU.properties** )\n  * FYI: https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-\n\nNote I: .properties file are not trendy but *imho* convenient: lightweight, native for java, support comments \u0026 line-breaking.\nNote II: to support any other file-format need to implement specific *LocalizationLoader* class. Not a big deal actually. \n\n## Default Files\nEach localе can have optional \"default\" file. Content of \"default\" file(if it exists) load first, and than \"overrided\" by content from \"main\" file.\nOverriding is working key by key, including system settings and formats. \nIt gives possible to override, for example, only one plural version of language key, one system setting or one property of specific format. \n\nName convension for default \"files\": default.languageTag.properties (e.g. **default.en-US.properties**, **default.ru-RU.properties** )\n\nDefault files is simple way to share same languages settings/formats/texts between multiple applications. \nOr run several instance of same application with minor changes in specificic \"main\" language file.\n\nNote I: Actually both \"default\" and \"main\" files are optional. It's fine to have at least one of these two.\\\nNote II: only one difference between \"default\" and \"main\" files is *_system.id*. Default files ignore it. \n\n## Files Syntax\nThe file consist of three groups of properties.\n### system properties\n* _system.id - any string, need to validate, that the file belong to the application which try to load it. \n* _system.languageTag - language tag same with the file name, need for paranoid validation, **REQUIRED**\n* _system.escapeHtml - escape or not HTML in the texts by default\n\nExample:\n```\n_system.id = test-app\n_system.languageTag = en-US\n_system.escapeHtml = true\n```\n\n### format properties\n\nSytax: _format.*format-name*.*format-property*\n\nExamples:\n```\n_format.curr.type = currency\n_format.curr.monetaryDecimalSeparator = *\n_format.curr.currencySymbol = $$\n\n_format.dt1.type = datetime\n_format.dt1.dateStyle = full\n_format.dt1.timeStyle = short\n\n_format.iso8601.type = datetime\n_format.iso8601.pattern = yyyy-MM-dd'T'HH:mm:ssXXX\n\n_format.numb.type = number\n_format.numb.groupingUsed = false\n\n_format.percent.type = percent\n_format.percent.percentSymbol = +\n```\n\nOnly property *type* is required. Possible values: **number**, **integer**, **percent**, **currency**, **date**, **time**, **datetime**\n\nSeven formats( same with supported types names) are always available, even if they clearly not present in the file.\n* with default for the locale properties\n* but, it is allowed to override them (e.g. *percent* in the example before)\n\nFormat properties:\n\n| Property | Can be use with types | possible values |\n| --- | --- | --- |\n| type | number, integer, percent, currency, date, time, datetime | number, integer, percent, currency, date, time, datetime |\n| pattern | number, integer, percent, currency, date, time, datetime | format pattern https://docs.oracle.com/javase/8/docs/api/java/text/DecimalFormat.html https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html |\n| groupingSeparator | number, integer, percent, currency | single character |\n| decimalSeparator | number, integer, percent, currency | single character |\n| groupingUsed | number, integer, percent, currency | true, false |\n| monetaryDecimalSeparator | currency | single character |\n| currencySymbol | currency | any string |\n| percentSymbol | percent | single character |\n| dateStyle | date, time, datetime | full, long, meduim, short (https://docs.oracle.com/javase/8/docs/api/java/time/format/FormatStyle.html) |\n| timeStyle | date, time, datetime | full, long, meduim, short (https://docs.oracle.com/javase/8/docs/api/java/time/format/FormatStyle.html) |\n\n\n### text key\nKey syntax: any-name[count number|$pluralization form index][$html]\n\nExamples:\n```\nverysimple = Very Simple\nany_name-actually = Any Any\nsuper.key = Super value\nsuper.htmlkey$html = Super \u003cbr/\u003e value\n\nx.y.z.apple = apples by default\nx.y.z.apple.0 = no any apples\nx.y.z.apple.1 = one apple\nx.y.z.apple.22$html = special case:\u003cbr/\u003e 22 apples\nx.y.z.apple.$1$html = {{count}}\u003cbr/\u003e apples\n```\n\n#### count number and pluralization form index\nThe key can have multiple versions with different count and/or pluralization form index postfixes\n\n{{count}} is special, reserved parameter. It must contain integer value and need to support pluralization.\nIf request to get localization text contain {{count}} parameter system will try to find the most appropriate version of the key:\n* First choice: key with postfix, which exactly equals {{count}} parameter value ((e.g. **.1**, **.22**)\n* Second choice: key with postfix, which equals to pluralization form index, calculateded by {{count}} parameter value ((e.g. **.$1**, **.$0**)\n* Last choice: key without count related postfixes.\n\n#### $html\nVery last, optional, key postfix. \nIf it present, it is mean that value of key will not HTML encoded, even if _system.escapeHtml = true\n\n### text parameters\nAny text-value can contain unbounded amount of parameters.\nParameter syntax: {{parameter-name,[format-name]}}\n\nExamples:\n```\nkey.with.parameters = Super {{param1}} value {{cooldate,iso8601}} xxxx {{prc,percent}}\n```\n\n* Positions of parameters in the text play no any role.\n* Parameters of the same key, in different language files can have different positions in the text.\n* Parameters of the same key, in different language files can have different formats\n* Same key, in different language files can have different set of parameters\n* All of that true also for different count number/pluralization form index in the same language file\n\n# How to use\n\n## Basic example:\n```\npackage net.cactusthorn;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.util.Locale;\n\nimport net.cactusthorn.localization.Localization;\nimport net.cactusthorn.localization.PathLocalizationLoader;\n\npublic class SimpleExample {\n\n\tstatic Locale en_US = Locale.forLanguageTag(\"en-US\");\n\t\n\tpublic static void main(String... args) {\n\t\t\n\t\ttry {\n\n\t\t\tString systemId = args[0];\n\t\t\tString l10nDirectory = args[1];\n\t\t\t\n\t\t\tLocalization localization = new PathLocalizationLoader(systemId ).from(l10nDirectory ).load();\n\t\t\t\n\t\t\tSystem.out.println(localization.get(en_US, \"super.key\"));\n\n\t\t} catch (URISyntaxException | IOException e ) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n}\n```\n\n## Loaders\nBy default loaders create an instance of *net.cactusthorn.localization.BasicLocalization* and load it with files from \"L10n\" directory, using UTF-8 character set. Only **systemId** is required.\n\n\"Full\" loader call:\n```\nnew JarLocalizationLoader(\"my-system-id\").instanceOf(LoggingLocalization.class).from(\"res/L10n\" ).encoded(StandardCharsets.UTF_8).load();\n\n```\n\n* PathLocalizationLoader - load from the disk\n* JarLocalizationLoader - load from the JAR resources\n\n## Implementations\n* BasicLocalization - straightforward implementation, which simple throw exception in any \"wrong\" situation\n* LoggingLocalization - never(except initialization phase) throw exceptions, but right them in the log(Slf4j) instead. Methods calls always return some string, with as much as possible data.\n  * https://www.slf4j.org/\n* WatchLoggingLocalization - LoggingLocalization which run Thread with WatchService to, on the fly, upload changes from the files\n  * sure, senseless to use it with JarLocalizationLoader\n  * https://docs.oracle.com/javase/8/docs/api/java/nio/file/WatchService.html\n\n## Parameters\n*Localization* interface contain several *get* methods to work with parameters in deferent forms. \nMost simple way is *net.cactusthorn.localization.Parameter* class:\nExample:\n```\nl10n.get(en_US, \"some-key\", Parameter.count(33), Parameter.of(\"param-name-1\", \"value\"), Parameter.of(\"param-name-2\", some-object));\n```\n\n## L10n Singleton \nInitialization-on-demand holder of any implementation. \n* https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom\n\nKeep in mind, that it's not clean according to true OOD to have and use something like that.\nNever the less it's working just fine.\n\nProblems with it: \n* can throw exception during initialization(when something wrong with the files), and this fact breaks initialization-on-demand pattern, actually\n  * if it fail at initialization moment, it will be dead: NoClassDefFoundError.\n\nTrade-off: need to organize initial single call of the class's special initialization-method somewhere at very start of your application.\nExample: *net.cactusthorn.L10nExample*\n\n# License\n\nReleased under the BSD 2-Clause License\n```\nCopyright (C) 2017, Alexei Khatskevich\nAll rights reserved.\n\nLicensed under the BSD 2-clause (Simplified) License (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n \nhttp://opensource.org/licenses/BSD-2-Clause\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgmugra%2Fnet.cactusthorn.localization","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgmugra%2Fnet.cactusthorn.localization","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgmugra%2Fnet.cactusthorn.localization/lists"}