{"id":21444673,"url":"https://github.com/xyzsd/fluent","last_synced_at":"2025-07-14T18:31:37.322Z","repository":{"id":44093578,"uuid":"365298111","full_name":"xyzsd/fluent","owner":"xyzsd","description":"A Java implementation of the Mozilla Project Fluent localization system","archived":false,"fork":false,"pushed_at":"2022-06-08T13:25:47.000Z","size":175,"stargazers_count":14,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-02-26T10:04:21.768Z","etag":null,"topics":["fluent","ftl","globalization","i18n","internationalization","java","l10n","localization","plural"],"latest_commit_sha":null,"homepage":"","language":"Java","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/xyzsd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-05-07T16:49:44.000Z","updated_at":"2024-02-20T03:43:40.000Z","dependencies_parsed_at":"2022-08-24T14:37:12.536Z","dependency_job_id":null,"html_url":"https://github.com/xyzsd/fluent","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/xyzsd%2Ffluent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xyzsd%2Ffluent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xyzsd%2Ffluent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xyzsd%2Ffluent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xyzsd","download_url":"https://codeload.github.com/xyzsd/fluent/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225991526,"owners_count":17556324,"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":["fluent","ftl","globalization","i18n","internationalization","java","l10n","localization","plural"],"created_at":"2024-11-23T02:20:43.609Z","updated_at":"2025-07-14T18:31:37.309Z","avatar_url":"https://github.com/xyzsd.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fluent\nA Java implementation of the [Mozilla Project Fluent][mozFluentGH] localization \nframework. The Fluent framework is designed to unleash the expressive power of\nnatural language translations.\n\nThe syntax of the Fluent Translation List, `FTL` is designed to be simple, yet is powerful \nenough to represent complex natural-language constructs such as plurals, conjugations,\nand gender. Learn more about Project Fluent at [projectfluent.org][mozProjectFluent].\n\nIntroductory Example\n--------------------\nGiven the following example FTL:\n```\n# Simple things are simple.\nhello-user = Hello, {$userName}!\n\n# Complex things are possible.\nshared-photos =\n    {$userName} {$photoCount -\u003e\n        [one] added a new photo\n       *[other] added {$photoCount} new photos\n    } to {$userGender -\u003e\n        [male] his stream\n        [female] her stream\n       *[other] their stream\n    }.\n```\nWe can use it as follows:\n```java\n// read the FTL and parse it into the data model\nFluentResource resource = FTLParser.parse( FTLStream.of( Files.readString(\"hello.ftl\") ) );\n\n// create the FluentBundle, which is used to manipulate the data model and perform localization\nFluentBundle bundle = FluentBundle.builder( Locale.US, ICUFunctionFactory.INSTANCE )\n        .addResource( resource )\n        .build();\n\n// The format() method is the simplest way to format a message.\nfinal String helloUser = bundle.format(\n        \"hello-user\",                       // the message name \n        Map.of( \"userName\", \"Billy\" )       // Map of parameters to substitute\n        );\nSystem.out.println( helloUser );        // output: \"Hello, Billy!\"\n\n// The following examples using the same message, but with different parameters.\n \n// output: \"Anne added a new photo to her stream.\"\nMap\u003cString, ?\u003e args1 = Map.of(\n        \"userName\", \"Anne\",\n        \"userGender\", \"female\",\n        \"photoCount\", 1\n        );\nSystem.out.println( bundle.format(\"shared-photos\", args1) ); \n\n// output: \"Billy added 5 new photos to his stream.\"\nMap\u003cString, ?\u003e args2 = Map.of(\n        \"userName\", \"Billy\",\n        \"userGender\", \"male\",\n        \"photoCount\", 5\n        );\nSystem.out.println( bundle.format(\"shared-photos\", args2) ); \n\n// output: \"Chris added 10 new photos to their stream.\"\nMap\u003cString, ?\u003e args3 = Map.of(\n        \"userName\", \"Chris\",\n        \"userGender\", \"unspecified\",\n        \"photoCount\", 10\n        );\nSystem.out.println( bundle.format(\"shared-photos\", args3) ); \n        \n```\n\nStatus\n------\n - [x] currently targeting JDK 17\n - [x] Usable\u0026mdash;though not optimal\u0026mdash;API. \n   - overall API shape may change. \n   - the goal is to keep easy things easy, and difficult things possible.\n - [x] Modularization: currently, automatic modules are used.\n   - goal is full modularization support\n - [x] Tests: mosts tests are currently high-level\n      - more tests are needed\n      - better test organization\n - [ ] Documentation\n      - not all classes have the documentation they deserve (... such as FluentBundle)\n- [ ] Examples\n  - Single simple example included currently\n   \n   \n\n\n\nDifferences from *fluent-rs* and *fluent.js*\n--------------------------------------------\n### DATETIME()\nThere is no DATETIME() function. Instead, use TEMPORAL().\nThe TEMPORAL() function supports pattern-based formatting in addition to predefined localized forms.\n\n### NUMBER()\nMost options of the NUMBER() function are supported. To match a number in a select clause as a String, rather than \nas a plural form (*consider this carefully*), type=\"string\" must be specified.\n\nSee the NUMBER() function documentation (NumberFn) for more information.\n\n### Support for parameters containing lists\nInitial support has been added for parameters contained in a `List\u003c?\u003e` or a `Set\u003c?\u003e`. \nLists can be heterogeneous. Functions can be nested and will be applied to each item. By default, the resultant list\nwill be comma-separated. However, using the JOIN() function, conjunctions and alternative delimiters are\nsupported. To preserve ordering, use `List` instead of  `Set`, or sort with STRINGSORT() or NUMSORT().\n\nSupport for lists extends to select statements as well; the select will apply to each item in the list. \nNote that there is currently no notion of or way to capture the item currently being iterated on in a select clause. \nTherefore, the use of lists in clauses with multiple select statements can be tricky.\n\nUsing the example FTL in the introduction:\n```java\nfinal String helloUser = bundle.format(\n        \"hello-user\",                       \n            Map.of( \"userName\", List.of(\"Billy\",\"Silly\",\"Willy\" ) )   \n        );\nSystem.out.println( helloUser );        // output: \"Hello, Billy, Silly, Willy!\"\n```\n\nThough when lists are expected, the formatting can be customized:\n\n```\n# modified FTL from introductory example\nhello-user = Hello, { JOIN($userName, separator:\", \", junction:\", and \", pairSeparator:\" and \") }!\n```\n\nNow:\n```java\n// output: \"Hello, Billy, Silly, and Willy!\nSystem.out.println(\n        bundle.format(\n            \"hello-user\",                       \n            Map.of( \"userName\", List.of(\"Billy\",\"Silly\",\"Willy\" ) )   \n        )\n    );\n\n// output: \"Hello, Betty and Yeti!\nSystem.out.println(\n        bundle.format(\n            \"hello-user\",\n            Map.of( \"userName\", List.of(\"Betty\",\"Yeti\" ) )\n        )\n    );\n```\n\n### Supported Types\nDuring parameter substitution, the following types are supported:\n   * Strings\n   * Numeric Types:\n      * `long` (with narrower types treated as a `long`)\n      * `double` (and narrower floating types)\n      * `BigDecimal` (and `BigInteger`)\n         * useful to retain precision, particularly trailing zeros\n   * TemporalAdjuster implementations\n\nCustom types can be added as needed.\n\n### Built-in functions\nFluent depends on *[cldr-plural-rules][cldrPlurals]* or *[ICU][icuPlurals]* for language pluralization rules.\nEither `fluent-functions-cldr` or `fluent-functions-icu` must be used along with the `fluent-base` package.\nBoth can be used simultaneously, though not within the same FluentBundle.\n\nA number of additional functions are included. More functions can be easily added, and existing functions can \nremoved or changed. \n\nFunctions currently include:\n   - NUMBER()\n      - handles localization of numeric values and pluralization (cardinal and ordinal forms).\n      - `useGrouping`, `minimumIntegerDigits`, `minimumFractionDigits`, `maximumFractionDigits`, \n        `minimumSignificantDigits`, and `maximumSignificantDigits` are supported.\n      - when converting a number to its plural form, formatting is ignored and the number is used\n        in its original form. If precise control over leading/trailing digits is needed, use a BigDecimal.\n      - `style` can be used to display currency or percentages. \n      - `type` used to specify pluralization\n   - TEMPORAL()\n      - Currently used instead of DATETIME(). Implementing DATETIME in a manner similar to `Intl.DateTimeFormat`\n        is complex but could be considered in the future.\n   - JOIN()\n      - Used to format lists. See multi-item lists above.\n   - COUNT()\n      - Count the number of items in a list.\n   - NUMSORT()\n      - Sort a list of numbers ascending or descending.\n   - STRINGSORT()\n      - Sort Strings\n   - ABS()\n      - Absolute value of a number\n   - IADD()\n      - Add an integer (or long) to an integer (or long)\n   - COMPACT()\n      - format a number using the localized compact representation\n        for example `COMPACT(10000)` would become `10K`\n   - CURRENCY()\n      - format a number using the localized currency representation\n   - DECIMAL()\n      - format a number using a number-format pattern\n   - SIGN()\n      - sign of a number; e.g., `SIGN(5)` becomes `\"positive\"`\n   - CASE()\n      - localized conversion of Strings to upper or lower case.\n\n\nFunction Composition\n--------------------\nFunctions can be composed. For example, given the following FTL:\n\n```\nexample = { NUMBER(NUMSORT($list, order:\"descending\"), minimumFractionDigits:2, useGrouping:\"true\") }\n```\n\nand associated code:\n\n```java\n...\n\nfinal List\u003cNumber\u003e NUMLIST = List.of(\n        3184, 538754, 1734.3489, 193547.37771, 0L, 0.0d, \n        new BigDecimal( \"193547.37772\" ), \n        new BigDecimal( \"-10.000001000\" ), \n        new BigDecimal( \".00000120\" )\n        );\n\nString result = bundle.format( \"example\", Map.of( \"$list\", NUMLIST ) );\nSystem.out.println(result);\n```\n\n`result` will be `538,754.00, 193,547.378, 193,547.378, 3,184.00, 1,734.349, 0.00, 0.00, 0.00, -10.00`.\n\nDocumentation\n-------------\nAvailable for [download][dlMavenCentral_base], (functions: [download here][dlMavenCentral_functions_icu]).\n\nOnline:\n- [fluent-base][docsOnlineBase]\n- [fluent-functions-cldr][docsOnlineCLDR]\n- [fluent-functions-icu][docsOnlineICU]\n\nDownload\n--------\n[Download][dlJAR] the latest JARs or depend via Maven:\n\n```xml\n\u003cdependency\u003e\n   \u003cgroupId\u003enet.xyzsd.fluent\u003c/groupId\u003e\n   \u003cartifactId\u003efluent-base\u003c/artifactId\u003e\n   \u003cversion\u003e0.72\u003c/version\u003e\n   \u003ctype\u003emodule\u003c/type\u003e\n\u003c/dependency\u003e\n```\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003enet.xyzsd.fluent\u003c/groupId\u003e\n    \u003cartifactId\u003efluent-functions-cldr\u003c/artifactId\u003e\n    \u003cversion\u003e0.72\u003c/version\u003e\n\u003c/dependency\u003e\n```\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003enet.xyzsd.fluent\u003c/groupId\u003e\n    \u003cartifactId\u003efluent-functions-icu\u003c/artifactId\u003e\n    \u003cversion\u003e0.72\u003c/version\u003e\n\u003c/dependency\u003e\n\n```\nor Gradle:\n```kotlin\nimplementation(\"net.xyzsd.fluent:fluent-base:0.72\")\nimplementation(\"net.xyzsd.fluent:fluent-functions-cldr:0.72\")\nimplementation(\"net.xyzsd.fluent:fluent-functions-icu:0.72\")\n```\n\nOnly one of the `fluent-functions-...` packages is required along with `fluent-base`.\n\n### Working with `-SNAPSHOT` Versions\nSnapshot versions may be available from Maven central repository. \n\nThe specific snapshot must be specifically requested. Please note that -SNAPSHOT releases\nare for development only, may not be stable, and will be automatically removed 90 days after\ncreation.\n\nTo use a snapshot, setup your `build.gradle.kts` file as so:\n```kotlin\nrepositories {\n    maven {\n        setUrl(\"https://central.sonatype.com/repository/maven-snapshots/\")\n        name = \"Central Portal Snapshots\"\n\n        // Only search this repository for the specific dependency\n        content {\n            includeModule(\"net.xyzsd.fluent\", \"fluent-base\")\n            includeModule(\"net.xyzsd.fluent\", \"fluent-functions-icu\")\n        }\n    }\n\n    mavenCentral()\n}\n```\nand then in the dependencies section specify the snapshot:\n```kotlin\ndependencies {\n    implementation(\"net.xyzsd.fluent:fluent-base:0.72-SNAPSHOT\")\n    // ... etc.\n    // ...\n}\n```\n\n\n\nAcknowledgements\n----------------\nPortions of this project are based on `fluent-rs`.\n\nLicense\n-------\nCopyright 2021, 2025 xyzsd\n\nLicensed under either of\n\n * Apache License, Version 2.0\n   (see LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license\n   (see LICENSE-MIT) or http://opensource.org/licenses/MIT)\n\nat your option.\n\n[cldrPlurals]: https://github.com/xyzsd/cldr-plural-rules\n[icuPlurals]: https://github.com/unicode-org/icu/tree/main/icu4j\n[mozFluentGH]: https://github.com/projectfluent/fluent/\n[mozProjectFluent]:  https://projectfluent.org/\n[dlJAR]: https://github.com/xyzsd/fluent/releases\n[dlMavenCentral_base]: https://central.sonatype.com/artifact/net.xyzsd.fluent/fluent-base/versions\n[dlMavenCentral_functions_icu]: https://central.sonatype.com/artifact/net.xyzsd.fluent/fluent-functions-icu/versions\n[docsOnlineBase]: https://javadoc.io/doc/net.xyzsd.fluent/fluent-base/latest/index.html\n[docsOnlineCLDR]: https://javadoc.io/doc/net.xyzsd.fluent/fluent-functions-cldr\n[docsOnlineICU]: https://javadoc.io/doc/net.xyzsd.fluent/fluent-functions-icu\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxyzsd%2Ffluent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxyzsd%2Ffluent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxyzsd%2Ffluent/lists"}