i18n-embed-fl-0.10.0/.cargo_vcs_info.json0000644000000001530000000000100133410ustar { "git": { "sha1": "659b53c45bc68b0742696bc8f53fc6cace83b11b" }, "path_in_vcs": "i18n-embed-fl" }i18n-embed-fl-0.10.0/CHANGELOG.md000064400000000000000000000124611046102023000137470ustar 00000000000000# Changelog for `i18n-embed-fl` ## v0.10.0 ### Dependencies + Bump `fluent` to `0.17` and `fluent-syntax` to `0.12`. + Bump `i18n-embed` to `0.16`. These are significant updates to core dependencies, thus I consider this a breaking release. ### Fixes + Fixed non-reproducible builds. Fixes [#150](https://github.com/kellpossible/cargo-i18n/issues/150). Thanks to [@qarmin](https://github.com/qarmin) for fixing this long standing issue! ### Documentation + Fixed some typos. Thanks to [@YgorSouza](https://github.com/YgorSouza). ## v0.9.4 ### New Features + Dashmap is now an optional dependency. Part of ongoing fix for [#131](https://github.com/kellpossible/cargo-i18n/issues/144) to reduce the number of dependencies. Thanks to [@mrtryhard](https://github.com/mrtryhard) for the contribution and [@bikeshedder](https://github.com/bikeshedder) for the review! ### Fixes + Support recursive message argument resolution. Fix for [#144](https://github.com/kellpossible/cargo-i18n/issues/144). Many thanks to [@cbs228](https://github.com/cbs228) for this contribution. ## v0.9.3 ### Internal + Removed dependency on `lazy_static` #135 thanks to [@mrtryhard](https://github.com/mrtryhard). ## v0.9.2 ### Fixes + Switch to proc-macro-error2 [#133](https://github.com/kellpossible/cargo-i18n/pull/133), fixes [#113](https://github.com/kellpossible/cargo-i18n/issues/113), thanks to [@mrtryhard](https://github.com/mrtryhard). + Fix compile warning for default features of `syn` crate. ## v0.9.1 ### Fixes + Fix broken test/build due to version bump needed in root Cargo.toml. + Use domain override thanks to @Matt3o12 [#124](https://github.com/kellpossible/cargo-i18n/pull/124). ## v0.9.0 ### Internal + Bump `strsim` to `0.11`. + Bump `dashmap` to `6.0`. ### Breaking + Bump `i18n-embed` to `0.15.0`. ### Fixes + Fallback to `std::env::var("CARGO_PKG_NAME")` Fixes [#97](https://github.com/kellpossible/cargo-i18n/issues/97) ## v0.8.0 ### Breaking + The loader argument is now wrapped in brackets when expanded. This means that the return type for the macro will always be `String`, previously if the loader was a reference (e.g. `fl!(&loader, "message")`) the returned value would be `&String`, which was misleading (see [112](https://github.com/kellpossible/cargo-i18n/issues/112)) and not useful. ## v0.7.0 ### Internal + Bump dependencies and use workspace dependencies. ## v0.6.7 + Update to syn version `2.0`. ## v0.6.6 + Fix for [#104](https://github.com/kellpossible/cargo-i18n/issues/104), include files necessary for running tests in crate. ## v0.6.5 ### New Features + Support fluent attributes [#98](https://github.com/kellpossible/cargo-i18n/pull/98) thanks to [@Almost-Senseless-Coder](https://github.com/Almost-Senseless-Coder)! + Tweaked the `fl!()` macro definition such that it optionally accepts an attribute ID in addition to a message ID and arguments. + Implemented compile-time verification of attributes. ### Internal + Bump `i18n-embed` dependency to version `0.13.5`. + Bump `env_logger` dev dependency to version `0.10`. + Fix clippy warnings. ## v0.6.4 + Update `dashmap` to version `5.1`. + Update `rust-embed` to `6.3` to address [RUSTSEC-2021-0126](https://rustsec.org/advisories/RUSTSEC-2021-0126.html). ## v0.6.3 + Revert `dashmap` back to `4.0` due to [security warning](https://rustsec.org/advisories/RUSTSEC-2022-0002.html) ## v0.6.2 + Update `dashmap` to version `5.1`. ## v0.6.1 + Fix for #76, add missing `syn` dependency with `full` feature flag specified. ## v0.6.0 ### Documentation + Don't reference specific `i18n-embed` version number. ### Breaking Changes + Update `i18n-embed` to version `0.13`. + Update `rust-embed` to version `6`. + Update `fluent` to version `0.16`. ## v0.5.0 ### Breaking Changes + Updated `fluent` to version `0.15`. ## v0.4.0 ### Breaking Changes + Update `i18n-embed` to version `0.11`. ### Internal Changes + Refactoring during the fix for [#60](https://github.com/kellpossible/cargo-i18n/issues/60). ## v0.3.1 ### Internal Changes + Safer use of DashMap's new `4.0` API thanks to [#56](https://github.com/kellpossible/cargo-i18n/pull/56). ## v0.3.0 + Update `fluent` dependency to version `0.14`. + Update to `dashmap` version `4.0`, and fix breaking change. ## v0.2.0 + Bumped version to reflect potential breaking changes present in the new version of `fluent`, `0.13` which is exposed in this crate's public API. And yanked previous version of `i18n-embed-fl`: `0.1.6`. ## v0.1.6 ### Internal Changes + Update to `fluent` version `0.13`. + Fixes to address breaking changes in `fluent-syntax` version `0.10`. ## v0.1.5 ### New Features + Updated readme with example convenience wrapper macro. + Added suggestions for message ids (ranked by levenshtein distance) to the error message when the current one fails to match. ## v0.1.4 + Enable the args hashmap option `fl!(loader, "message_id", args())` to be parsed as an expression, instead of just an ident. ## v0.1.3 + Fix bug where message check wasn't occurring with no arguments or with hashmap arguments. ## v0.1.2 + Change the `loader` argument to be an expression, instead of an ident, so it allows more use cases. ## v0.1.1 + Remove `proc_macro_diagnostic` feature causing problems compiling on stable, and use `proc-macro-error` crate instead. ## v0.1.0 + Initial version, introduces the `fl!()` macro. i18n-embed-fl-0.10.0/Cargo.lock0000644000000552740000000000100113320ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys", ] [[package]] name = "arc-swap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "basic-toml" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", ] [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "env_filter" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", "jiff", "log", ] [[package]] name = "find-crate" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ "toml", ] [[package]] name = "fluent" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477" dependencies = [ "fluent-bundle", "unic-langid", ] [[package]] name = "fluent-bundle" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4" dependencies = [ "fluent-langneg", "fluent-syntax", "intl-memoizer", "intl_pluralrules", "rustc-hash", "self_cell", "smallvec", "unic-langid", ] [[package]] name = "fluent-langneg" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" dependencies = [ "unic-langid", ] [[package]] name = "fluent-syntax" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ "memchr", "thiserror 2.0.12", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "i18n-config" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef" dependencies = [ "basic-toml", "log", "serde", "serde_derive", "thiserror 1.0.69", "unic-langid", ] [[package]] name = "i18n-embed" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a217bbb075dcaefb292efa78897fc0678245ca67f265d12c351e42268fcb0305" dependencies = [ "arc-swap", "fluent", "fluent-langneg", "fluent-syntax", "i18n-embed-impl", "intl-memoizer", "log", "parking_lot", "rust-embed", "thiserror 1.0.69", "unic-langid", "walkdir", ] [[package]] name = "i18n-embed-fl" version = "0.10.0" dependencies = [ "dashmap", "doc-comment", "env_logger", "find-crate", "fluent", "fluent-syntax", "i18n-config", "i18n-embed", "pretty_assertions", "proc-macro-error2", "proc-macro2", "quote", "rust-embed", "strsim", "syn", "unic-langid", ] [[package]] name = "i18n-embed-impl" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" dependencies = [ "find-crate", "i18n-config", "proc-macro2", "quote", "syn", ] [[package]] name = "intl-memoizer" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" dependencies = [ "type-map", "unic-langid", ] [[package]] name = "intl_pluralrules" version = "7.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" dependencies = [ "unic-langid", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "jiff" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", "serde", ] [[package]] name = "jiff-static" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "lock_api" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "parking_lot" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro-error-attr2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "proc-macro-error2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", "syn", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rust-embed" version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" dependencies = [ "rust-embed-impl", "rust-embed-utils", "walkdir", ] [[package]] name = "rust-embed-impl" version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", "syn", "walkdir", ] [[package]] name = "rust-embed-utils" version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" dependencies = [ "sha2", "walkdir", ] [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "self_cell" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "type-map" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ "rustc-hash", ] [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unic-langid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" dependencies = [ "unic-langid-impl", ] [[package]] name = "unic-langid-impl" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" dependencies = [ "serde", "tinystr", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" [[package]] name = "zerovec" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "zerofrom", ] i18n-embed-fl-0.10.0/Cargo.toml0000644000000040430000000000100113410ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "i18n-embed-fl" version = "0.10.0" authors = ["Luke Frisken "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Macro to perform compile time checks when using the i18n-embed crate and the fluent localization system" readme = "README.md" categories = [ "localization", "internationalization", "development-tools", ] license = "MIT" repository = "https://github.com/kellpossible/cargo-i18n/tree/master/i18n-embed-fl" [features] dashmap = ["dep:dashmap"] [lib] name = "i18n_embed_fl" path = "src/lib.rs" proc-macro = true [[test]] name = "fl_macro" path = "tests/fl_macro.rs" [dependencies.dashmap] version = "6.0" optional = true [dependencies.find-crate] version = "0.6" [dependencies.fluent] version = "0.17" [dependencies.fluent-syntax] version = "0.12" [dependencies.i18n-config] version = "0.4.7" [dependencies.i18n-embed] version = "0.16.0" features = [ "fluent-system", "filesystem-assets", ] [dependencies.proc-macro-error2] version = "2.0.1" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.strsim] version = "0.11" [dependencies.syn] version = "2.0" features = [ "derive", "proc-macro", "parsing", "printing", "extra-traits", "full", ] [dependencies.unic-langid] version = "0.9" [dev-dependencies.doc-comment] version = "0.3" [dev-dependencies.env_logger] version = "0.11" [dev-dependencies.pretty_assertions] version = "1.4" [dev-dependencies.rust-embed] version = "8.0" i18n-embed-fl-0.10.0/Cargo.toml.orig000064400000000000000000000023321046102023000150210ustar 00000000000000[package] name = "i18n-embed-fl" description = "Macro to perform compile time checks when using the i18n-embed crate and the fluent localization system" categories = ["localization", "internationalization", "development-tools"] version = "0.10.0" authors = ["Luke Frisken "] edition = "2018" license = "MIT" repository = "https://github.com/kellpossible/cargo-i18n/tree/master/i18n-embed-fl" [lib] proc-macro = true [dependencies] dashmap = { version = "6.0", optional = true } find-crate = { workspace = true } fluent = { workspace = true } fluent-syntax = { workspace = true } i18n-config = { workspace = true } i18n-embed = { workspace = true, features = ["fluent-system", "filesystem-assets"]} proc-macro2 = { workspace = true } proc-macro-error2 = "2.0.1" quote = { workspace = true } strsim = "0.11" unic-langid = { workspace = true } [dependencies.syn] workspace = true features = ["derive", "proc-macro", "parsing", "printing", "extra-traits", "full"] [dev-dependencies] doc-comment = { workspace = true } env_logger = { workspace = true } pretty_assertions = { workspace = true } rust-embed = { workspace = true } [features] # Uses dashmap implementation for `fl!()` macro lookups. dashmap = ["dep:dashmap"]i18n-embed-fl-0.10.0/LICENSE.txt000064400000000000000000000020601046102023000137530ustar 00000000000000Copyright 2020 Luke Frisken Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. © 2020 Luke Friskeni18n-embed-fl-0.10.0/README.md000064400000000000000000000063251046102023000134170ustar 00000000000000# i18n-embed-fl [![crates.io badge](https://img.shields.io/crates/v/i18n-embed-fl.svg)](https://crates.io/crates/i18n-embed-fl) [![docs.rs badge](https://docs.rs/i18n-embed-fl/badge.svg)](https://docs.rs/i18n-embed-fl/) [![license badge](https://img.shields.io/github/license/kellpossible/cargo-i18n)](https://github.com/kellpossible/cargo-i18n/blob/master/i18n-embed-fl/LICENSE.txt) [![github actions badge](https://github.com/kellpossible/cargo-i18n/workflows/Rust/badge.svg)](https://github.com/kellpossible/cargo-i18n/actions?query=workflow%3ARust) This crate provides a macro to perform compile time checks when using the [i18n-embed](https://crates.io/crates/i18n-embed) crate and the [fluent](https://www.projectfluent.org/) localization system. See [docs](https://docs.rs/i18n-embed-fl/), and [i18n-embed](https://crates.io/crates/i18n-embed) for more information. **[Changelog](https://github.com/kellpossible/cargo-i18n/blob/master/i18n-embed-fl/CHANGELOG.md)** ## Example Set up a minimal `i18n.toml` in your crate root to use with `cargo-i18n` (see [cargo i18n](../README.md#configuration) for more information on the configuration file format): ```toml # (Required) The language identifier of the language used in the # source code for gettext system, and the primary fallback language # (for which all strings must be present) when using the fluent # system. fallback_language = "en-GB" # Use the fluent localization system. [fluent] # (Required) The path to the assets directory. # The paths inside the assets directory should be structured like so: # `assets_dir/{language}/{domain}.ftl` assets_dir = "i18n" ``` Create a fluent localization file for the `en-GB` language in `i18n/en-GB/{domain}.ftl`, where `domain` is the rust path of your crate (`_` instead of `-`): ```fluent hello-arg = Hello {$name}! ``` Simple set up of the `FluentLanguageLoader`, and obtaining a message formatted with an argument: ```rust use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, LanguageLoader, }; use i18n_embed_fl::fl; use rust_embed::RustEmbed; #[derive(RustEmbed)] #[folder = "i18n/"] struct Localizations; let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); assert_eq!( "Hello \u{2068}Bob 23\u{2069}!", // Compile time check for message id, and the `name` argument, // to ensure it matches what is specified in the `fallback_language`'s // fluent resource file. fl!(loader, "hello-arg", name = format!("Bob {}", 23)) ) ``` ## Convenience Macro You will notice that this macro requires `loader` to be specified in every call. For you project you may have access to a statically defined loader, and you can create a convenience macro wrapper so this doesn't need to be imported and specified every time. ```rust macro_rules! fl { ($message_id:literal) => {{ i18n_embed_fl::fl!($crate::YOUR_STATIC_LOADER, $message_id) }}; ($message_id:literal, $($args:expr),*) => {{ i18n_embed_fl::fl!($crate::YOUR_STATIC_LOADER, $message_id, $($args), *) }}; } ``` This can now be invoked like so: `fl!("message-id")`, `fl!("message-id", args)` and `fl!("message-id", arg = "value")`. i18n-embed-fl-0.10.0/i18n/en-US/i18n_embed_fl.ftl000064400000000000000000000010021046102023000167340ustar 00000000000000hello-world = Hello World! hello-arg = Hello {$name}! .attr = Hello {$name}'s attribute! hello-arg-2 = Hello {$name1} and {$name2}! hello-attr = Uninspiring. .text = Hello, attribute! hello-recursive = Hello { hello-recursive-descent } .attr = Why hello { hello-recursive-descent } .again = Why hello { hello-recursive-descent.attr } hello-recursive-descent = to you, {$name}! .attr = again, {$name}! hello-select = { $attr -> *[no] { hello-recursive } [yes] { hello-recursive.attr } } i18n-embed-fl-0.10.0/i18n.toml000064400000000000000000000007131046102023000136070ustar 00000000000000# (Required) The language identifier of the language used in the # source code for gettext system, and the primary fallback language # (for which all strings must be present) when using the fluent # system. fallback_language = "en-US" # Use the fluent localization system. [fluent] # (Required) The path to the assets directory. # The paths inside the assets directory should be structured like so: # `assets_dir/{language}/{domain}.ftl` assets_dir = "i18n"i18n-embed-fl-0.10.0/src/lib.rs000064400000000000000000000756131046102023000140510ustar 00000000000000use fluent::concurrent::FluentBundle; use fluent::{FluentAttribute, FluentMessage, FluentResource}; use fluent_syntax::ast::{CallArguments, Expression, InlineExpression, Pattern, PatternElement}; use i18n_embed::{fluent::FluentLanguageLoader, FileSystemAssets, LanguageLoader}; use proc_macro::TokenStream; use proc_macro_error2::{abort, emit_error, proc_macro_error}; use quote::quote; use std::{ collections::{HashMap, HashSet}, path::Path, sync::OnceLock, }; #[cfg(feature = "dashmap")] use dashmap::mapref::one::Ref; #[cfg(not(feature = "dashmap"))] use std::sync::{Arc, RwLock}; use syn::{parse::Parse, parse_macro_input, spanned::Spanned}; use unic_langid::LanguageIdentifier; #[cfg(doctest)] #[macro_use] extern crate doc_comment; #[cfg(doctest)] doctest!("../README.md"); #[derive(Debug)] enum FlAttr { /// An attribute ID got provided. Attr(syn::Lit), /// No attribute ID got provided. None, } impl Parse for FlAttr { fn parse(input: syn::parse::ParseStream) -> syn::Result { if !input.is_empty() { let fork = input.fork(); fork.parse::()?; if fork.parse::().is_ok() && (fork.parse::().is_ok() || fork.is_empty()) { input.parse::()?; let literal = input.parse::()?; Ok(Self::Attr(literal)) } else { Ok(Self::None) } } else { Ok(Self::None) } } } #[derive(Debug)] enum FlArgs { /// `fl!(LOADER, "message", "optional-attribute", args)` where `args` is a /// `HashMap<&'a str, FluentValue<'a>>`. HashMap(syn::Expr), /// ```ignore /// fl!(LOADER, "message", "optional-attribute", /// arg1 = "value", /// arg2 = value2, /// arg3 = calc_value()); /// ``` KeyValuePairs { specified_args: Vec<(syn::LitStr, Box)>, }, /// `fl!(LOADER, "message", "optional-attribute")` no arguments after the message id and optional attribute id. None, } impl Parse for FlArgs { fn parse(input: syn::parse::ParseStream) -> syn::Result { if !input.is_empty() { input.parse::()?; let lookahead = input.fork(); if lookahead.parse::().is_err() { let hash_map = input.parse()?; return Ok(FlArgs::HashMap(hash_map)); } let mut args: Vec<(syn::LitStr, Box)> = Vec::new(); while let Ok(expr) = input.parse::() { let argument_name_ident_opt = match &*expr.left { syn::Expr::Path(path) => path.path.get_ident(), _ => None, }; let argument_name_ident = match argument_name_ident_opt { Some(ident) => ident, None => { return Err(syn::Error::new( expr.left.span(), "fl!() unable to parse argument identifier", )) } } .clone(); let argument_name_string = argument_name_ident.to_string(); let argument_name_lit_str = syn::LitStr::new(&argument_name_string, argument_name_ident.span()); let argument_value = expr.right; if args .iter() .any(|(key, _value)| argument_name_lit_str == *key) { // There's no Clone implementation by default. let argument_name_lit_str = syn::LitStr::new(&argument_name_string, argument_name_ident.span()); return Err(syn::Error::new( argument_name_lit_str.span(), format!( "fl!() macro contains a duplicate argument `{}`", argument_name_lit_str.value() ), )); } args.push((argument_name_lit_str, argument_value)); // parse the next comma if there is one let _result = input.parse::(); } if args.is_empty() { let span = match input.fork().parse::() { Ok(expr) => expr.span(), Err(_) => input.span(), }; Err(syn::Error::new(span, "fl!() unable to parse args input")) } else { args.sort_by_key(|(s, _)| s.value()); Ok(FlArgs::KeyValuePairs { specified_args: args, }) } } else { Ok(FlArgs::None) } } } /// Input for the [fl()] macro. struct FlMacroInput { fluent_loader: syn::Expr, message_id: syn::Lit, attr: FlAttr, args: FlArgs, } impl Parse for FlMacroInput { fn parse(input: syn::parse::ParseStream) -> syn::Result { let fluent_loader = input.parse()?; input.parse::()?; let message_id = input.parse()?; let attr = input.parse()?; let args = input.parse()?; Ok(Self { fluent_loader, message_id, attr, args, }) } } struct DomainSpecificData { loader: FluentLanguageLoader, _assets: FileSystemAssets, } #[derive(Default)] struct DomainsMap { #[cfg(not(feature = "dashmap"))] map: RwLock>>, #[cfg(feature = "dashmap")] map: dashmap::DashMap, } #[cfg(feature = "dashmap")] impl DomainsMap { fn get(&self, domain: &String) -> Option> { self.map.get(domain) } fn entry_or_insert( &self, domain: &String, data: DomainSpecificData, ) -> Ref { self.map.entry(domain.clone()).or_insert(data).downgrade() } } #[cfg(not(feature = "dashmap"))] impl DomainsMap { fn get(&self, domain: &String) -> Option> { match self.map.read().unwrap().get(domain) { None => None, Some(data) => Some(data.clone()), } } fn entry_or_insert( &self, domain: &String, data: DomainSpecificData, ) -> Arc { self.map .write() .unwrap() .entry(domain.clone()) .or_insert(Arc::new(data)) .clone() } } fn domains() -> &'static DomainsMap { static DOMAINS: OnceLock = OnceLock::new(); DOMAINS.get_or_init(|| DomainsMap::default()) } /// A macro to obtain localized messages and optionally their attributes, and check the `message_id`, `attribute_id` /// and arguments at compile time. /// /// Compile time checks are performed using the `fallback_language` /// specified in the current crate's `i18n.toml` confiration file. /// /// This macro supports three different calling syntaxes which are /// explained in the following sections. /// /// ## No Arguments /// /// ```ignore /// fl!(loader: FluentLanguageLoader, "message_id") /// ``` /// /// This is the simplest form of the `fl!()` macro, just obtaining a /// message with no arguments. The `message_id` should be specified as /// a literal string, and is checked at compile time. /// /// ### Example /// /// ``` /// use i18n_embed::{ /// fluent::{fluent_language_loader, FluentLanguageLoader}, /// LanguageLoader, /// }; /// use i18n_embed_fl::fl; /// use rust_embed::RustEmbed; /// /// #[derive(RustEmbed)] /// #[folder = "i18n/"] /// struct Localizations; /// /// let loader: FluentLanguageLoader = fluent_language_loader!(); /// loader /// .load_languages(&Localizations, &[loader.fallback_language().clone()]) /// .unwrap(); /// /// // Invoke the fl!() macro to obtain the translated message, and /// // check the message id compile time. /// assert_eq!("Hello World!", fl!(loader, "hello-world")); /// ``` /// /// ## Individual Arguments /// /// ```ignore /// fl!( /// loader: FluentLanguageLoader, /// "message_id", /// arg1 = value, /// arg2 = "value", /// arg3 = function(), /// ... /// ) /// ``` /// /// This form of the `fl!()` macro allows individual arguments to be /// specified in the form `key = value` after the `message_id`. `key` /// needs to be a valid literal argument name, and `value` can be any /// expression that resolves to a type that implements /// `Into`. The `key`s will be checked at compile time to /// ensure that they match the arguments specified in original fluent /// message. /// /// ### Example /// /// ``` /// # use i18n_embed::{ /// # fluent::{fluent_language_loader, FluentLanguageLoader}, /// # LanguageLoader, /// # }; /// # use i18n_embed_fl::fl; /// # use rust_embed::RustEmbed; /// # #[derive(RustEmbed)] /// # #[folder = "i18n/"] /// # struct Localizations; /// # let loader: FluentLanguageLoader = fluent_language_loader!(); /// # loader /// # .load_languages(&Localizations, &[loader.fallback_language().clone()]) /// # .unwrap(); /// let calc_james = || "James".to_string(); /// pretty_assertions::assert_eq!( /// "Hello \u{2068}Bob\u{2069} and \u{2068}James\u{2069}!", /// // Invoke the fl!() macro to obtain the translated message, and /// // check the message id, and arguments at compile time. /// fl!(loader, "hello-arg-2", name1 = "Bob", name2 = calc_james()) /// ); /// ``` /// /// ## Arguments Hashmap /// /// ```ignore /// fl!( /// loader: FluentLanguageLoader, /// "message_id", /// args: HashMap< /// S where S: Into> + Clone, /// T where T: Into> + Clone> /// ) /// ``` /// /// With this form of the `fl!()` macro, arguments can be specified at /// runtime using a [HashMap](std::collections::HashMap), using the /// same signature as in /// [FluentLanguageLoader::get_args()](i18n_embed::fluent::FluentLanguageLoader::get_args()). /// When using this method of specifying arguments, they are not /// checked at compile time. /// /// ### Example /// /// ``` /// # use i18n_embed::{ /// # fluent::{fluent_language_loader, FluentLanguageLoader}, /// # LanguageLoader, /// # }; /// # use i18n_embed_fl::fl; /// # use rust_embed::RustEmbed; /// # #[derive(RustEmbed)] /// # #[folder = "i18n/"] /// # struct Localizations; /// # let loader: FluentLanguageLoader = fluent_language_loader!(); /// # loader /// # .load_languages(&Localizations, &[loader.fallback_language().clone()]) /// # .unwrap(); /// use std::collections::HashMap; /// /// let mut args: HashMap<&str, &str> = HashMap::new(); /// args.insert("name", "Bob"); /// /// assert_eq!("Hello \u{2068}Bob\u{2069}!", fl!(loader, "hello-arg", args)); /// ``` /// /// ## Attributes /// /// In all of the above patterns you can optionally include an `attribute_id` /// after the `message_id`, in which case `fl!` will attempt retrieving the specified /// attribute belonging to the specified message, optionally formatted with the provided arguments. /// /// ### Example /// /// ``` /// # use i18n_embed::{ /// # fluent::{fluent_language_loader, FluentLanguageLoader}, /// # LanguageLoader, /// # }; /// # use i18n_embed_fl::fl; /// # use rust_embed::RustEmbed; /// # #[derive(RustEmbed)] /// # #[folder = "i18n/"] /// # struct Localizations; /// # let loader: FluentLanguageLoader = fluent_language_loader!(); /// # loader /// # .load_languages(&Localizations, &[loader.fallback_language().clone()]) /// # .unwrap(); /// use std::collections::HashMap; /// /// let mut args: HashMap<&str, &str> = HashMap::new(); /// args.insert("name", "Bob"); /// /// assert_eq!("Hello \u{2068}Bob\u{2069}'s attribute!", fl!(loader, "hello-arg", "attr", args)); /// ``` #[proc_macro] #[proc_macro_error] pub fn fl(input: TokenStream) -> TokenStream { let input: FlMacroInput = parse_macro_input!(input as FlMacroInput); let fluent_loader = input.fluent_loader; let message_id = input.message_id; let domain = { let manifest = find_crate::Manifest::new().expect("Error reading Cargo.toml"); manifest.crate_package().map(|pkg| pkg.name).unwrap_or( std::env::var("CARGO_PKG_NAME").expect("Error fetching `CARGO_PKG_NAME` env"), ) }; let domain_data = if let Some(domain_data) = domains().get(&domain) { domain_data } else { let crate_paths = i18n_config::locate_crate_paths() .unwrap_or_else(|error| panic!("fl!() is unable to locate crate paths: {}", error)); let config_file_path = &crate_paths.i18n_config_file; let config = i18n_config::I18nConfig::from_file(config_file_path).unwrap_or_else(|err| { abort! { proc_macro2::Span::call_site(), format!( "fl!() had a problem reading i18n config file {config_file_path:?}: {err}" ); help = "Try creating the `i18n.toml` configuration file."; } }); let fluent_config = config.fluent.unwrap_or_else(|| { abort! { proc_macro2::Span::call_site(), format!( "fl!() had a problem parsing i18n config file {config_file_path:?}: \ there is no `[fluent]` subsection." ); help = "Add the `[fluent]` subsection to `i18n.toml`, \ along with its required `assets_dir`."; } }); // Use the domain override in the configuration. let domain = fluent_config.domain.unwrap_or(domain); let assets_dir = Path::new(&crate_paths.crate_dir).join(fluent_config.assets_dir); let assets = FileSystemAssets::try_new(assets_dir).unwrap(); let fallback_language: LanguageIdentifier = config.fallback_language; let loader = FluentLanguageLoader::new(&domain, fallback_language.clone()); loader .load_languages(&assets, &[fallback_language.clone()]) .unwrap_or_else(|err| match err { i18n_embed::I18nEmbedError::LanguageNotAvailable(file, language_id) => { if fallback_language != language_id { panic!( "fl!() encountered an unexpected problem, \ the language being loaded (\"{0}\") is not the \ `fallback_language` (\"{1}\")", language_id, fallback_language ) } abort! { proc_macro2::Span::call_site(), format!( "fl!() was unable to load the localization \ file for the `fallback_language` \ (\"{fallback_language}\"): {file}" ); help = "Try creating the required fluent localization file."; } } _ => panic!( "fl!() had an unexpected problem while \ loading language \"{0}\": {1}", fallback_language, err ), }); let data = DomainSpecificData { loader, _assets: assets, }; domains().entry_or_insert(&domain, data) }; let message_id_string = match &message_id { syn::Lit::Str(message_id_str) => { let message_id_str = message_id_str.value(); Some(message_id_str) } unexpected_lit => { emit_error! { unexpected_lit, "fl!() `message_id` should be a literal rust string" }; None } }; let attr = input.attr; let attr_str; let attr_lit = match &attr { FlAttr::Attr(literal) => match literal { syn::Lit::Str(string_lit) => { attr_str = Some(string_lit.value()); Some(literal) } unexpected_lit => { attr_str = None; emit_error! { unexpected_lit, "fl!() `message_id` should be a literal rust string" }; None } }, FlAttr::None => { attr_str = None; None } }; // If we have already confirmed that the loader has the message. // `false` if we haven't checked, or we have checked but no // message was found. let mut checked_loader_has_message = false; // Same procedure for attributes let mut checked_message_has_attribute = false; let gen = match input.args { FlArgs::HashMap(args_hash_map) => { if attr_lit.is_none() { quote! { (#fluent_loader).get_args(#message_id, #args_hash_map) } } else { quote! { (#fluent_loader).get_attr_args(#message_id, #attr_lit, #args_hash_map) } } } FlArgs::None => { if attr_lit.is_none() { quote! { (#fluent_loader).get(#message_id) } } else { quote! { (#fluent_loader).get_attr(#message_id, #attr_lit) } } } FlArgs::KeyValuePairs { specified_args } => { let mut arg_assignments = proc_macro2::TokenStream::default(); for (key, value) in &specified_args { arg_assignments = quote! { #arg_assignments args.insert(#key, #value.into()); } } if attr_lit.is_none() { if let Some(message_id_str) = &message_id_string { checked_loader_has_message = domain_data .loader .with_fluent_message_and_bundle(message_id_str, |message, bundle| { check_message_args(message, bundle, &specified_args); }) .is_some(); } let gen = quote! { (#fluent_loader).get_args_concrete( #message_id, { let mut args = std::collections::HashMap::new(); #arg_assignments args }) }; gen } else { if let Some(message_id_str) = &message_id_string { if let Some(attr_id_str) = &attr_str { let attr_res = domain_data.loader.with_fluent_message_and_bundle( message_id_str, |message, bundle| match message.get_attribute(attr_id_str) { Some(attr) => { check_attribute_args(attr, bundle, &specified_args); true } None => false, }, ); checked_loader_has_message = attr_res.is_some(); checked_message_has_attribute = attr_res.unwrap_or(false); } } let gen = quote! { (#fluent_loader).get_attr_args_concrete( #message_id, #attr_lit, { let mut args = std::collections::HashMap::new(); #arg_assignments args }) }; gen } } }; if let Some(message_id_str) = &message_id_string { if !checked_loader_has_message && !domain_data.loader.has(message_id_str) { let suggestions = fuzzy_message_suggestions(&domain_data.loader, message_id_str, 5).join("\n"); let hint = format!( "Perhaps you are looking for one of the following messages?\n\n\ {suggestions}" ); emit_error! { message_id, format!( "fl!() `message_id` validation failed. `message_id` \ of \"{0}\" does not exist in the `fallback_language` (\"{1}\")", message_id_str, domain_data.loader.current_language(), ); help = "Enter the correct `message_id` or create \ the message in the localization file if the \ intended message does not yet exist."; hint = hint; }; } else if let Some(attr_id_str) = &attr_str { if !checked_message_has_attribute && !&domain_data.loader.has_attr(message_id_str, attr_id_str) { let suggestions = &domain_data .loader .with_fluent_message(message_id_str, |message| { fuzzy_attribute_suggestions(&message, attr_id_str, 5).join("\n") }) .unwrap(); let hint = format!( "Perhaps you are looking for one of the following attributes?\n\n\ {suggestions}" ); emit_error! { attr_lit, format!( "fl!() `attribute_id` validation failed. `attribute_id` \ of \"{0}\" does not exist in the `fallback_language` (\"{1}\")", attr_id_str, domain_data.loader.current_language(), ); help = "Enter the correct `attribute_id` or create \ the attribute associated with the message in the localization file if the \ intended attribute does not yet exist."; hint = hint; }; } } } gen.into() } fn fuzzy_message_suggestions( loader: &FluentLanguageLoader, message_id_str: &str, n_suggestions: usize, ) -> Vec { let mut scored_messages: Vec<(String, usize)> = loader.with_message_iter(loader.fallback_language(), |message_iter| { message_iter .map(|message| { ( message.id.name.to_string(), strsim::levenshtein(message_id_str, message.id.name), ) }) .collect() }); scored_messages.sort_by_key(|(_message, score)| *score); scored_messages.truncate(n_suggestions); scored_messages .into_iter() .map(|(message, _score)| message) .collect() } fn fuzzy_attribute_suggestions( message: &FluentMessage<'_>, attribute_id_str: &str, n_suggestions: usize, ) -> Vec { let mut scored_attributes: Vec<(String, usize)> = message .attributes() .map(|attribute| { ( attribute.id().to_string(), strsim::levenshtein(attribute_id_str, attribute.id()), ) }) .collect(); scored_attributes.sort_by_key(|(_attr, score)| *score); scored_attributes.truncate(n_suggestions); scored_attributes .into_iter() .map(|(attribute, _score)| attribute) .collect() } fn check_message_args( message: FluentMessage<'_>, bundle: &FluentBundle, specified_args: &Vec<(syn::LitStr, Box)>, ) where R: std::borrow::Borrow, { if let Some(pattern) = message.value() { let mut args = Vec::new(); args_from_pattern(pattern, bundle, &mut args); let args_set: HashSet<&str> = args.into_iter().collect(); let key_args: Vec = specified_args .iter() .map(|(key, _value)| { let arg = key.value(); if !args_set.contains(arg.as_str()) { let available_args: String = args_set .iter() .map(|arg| format!("`{arg}`")) .collect::>() .join(", "); emit_error! { key, format!( "fl!() argument `{0}` does not exist in the \ fluent message. Available arguments: {1}.", &arg, available_args ); help = "Enter the correct arguments, or fix the message \ in the fluent localization file so that the arguments \ match this macro invocation."; }; } arg }) .collect(); let key_args_set: HashSet<&str> = key_args.iter().map(|v| v.as_str()).collect(); let unspecified_args: Vec = args_set .iter() .filter_map(|arg| { if !key_args_set.contains(arg) { Some(format!("`{arg}`")) } else { None } }) .collect(); if !unspecified_args.is_empty() { emit_error! { proc_macro2::Span::call_site(), format!( "fl!() the following arguments have not been specified: {}", unspecified_args.join(", ") ); help = "Enter the correct arguments, or fix the message \ in the fluent localization file so that the arguments \ match this macro invocation."; }; } } } fn check_attribute_args( attr: FluentAttribute<'_>, bundle: &FluentBundle, specified_args: &Vec<(syn::LitStr, Box)>, ) where R: std::borrow::Borrow, { let pattern = attr.value(); let mut args = Vec::new(); args_from_pattern(pattern, bundle, &mut args); let args_set: HashSet<&str> = args.into_iter().collect(); let key_args: Vec = specified_args .iter() .map(|(key, _value)| { let arg = key.value(); if !args_set.contains(arg.as_str()) { let available_args: String = args_set .iter() .map(|arg| format!("`{arg}`")) .collect::>() .join(", "); emit_error! { key, format!( "fl!() argument `{0}` does not exist in the \ fluent attribute. Available arguments: {1}.", &arg, available_args ); help = "Enter the correct arguments, or fix the attribute \ in the fluent localization file so that the arguments \ match this macro invocation."; }; } arg }) .collect(); let key_args_set: HashSet<&str> = key_args.iter().map(|v| v.as_str()).collect(); let unspecified_args: Vec = args_set .iter() .filter_map(|arg| { if !key_args_set.contains(arg) { Some(format!("`{arg}`")) } else { None } }) .collect(); if !unspecified_args.is_empty() { emit_error! { proc_macro2::Span::call_site(), format!( "fl!() the following arguments have not been specified: {}", unspecified_args.join(", ") ); help = "Enter the correct arguments, or fix the attribute \ in the fluent localization file so that the arguments \ match this macro invocation."; }; } } fn args_from_pattern<'m, R>( pattern: &Pattern<&'m str>, bundle: &'m FluentBundle, args: &mut Vec<&'m str>, ) where R: std::borrow::Borrow, { pattern.elements.iter().for_each(|element| { if let PatternElement::Placeable { expression } = element { args_from_expression(expression, bundle, args) } }); } fn args_from_expression<'m, R>( expr: &Expression<&'m str>, bundle: &'m FluentBundle, args: &mut Vec<&'m str>, ) where R: std::borrow::Borrow, { match expr { Expression::Inline(inline_expr) => { args_from_inline_expression(inline_expr, bundle, args); } Expression::Select { selector, variants } => { args_from_inline_expression(selector, bundle, args); variants.iter().for_each(|variant| { args_from_pattern(&variant.value, bundle, args); }) } } } fn args_from_inline_expression<'m, R>( inline_expr: &InlineExpression<&'m str>, bundle: &'m FluentBundle, args: &mut Vec<&'m str>, ) where R: std::borrow::Borrow, { match inline_expr { InlineExpression::FunctionReference { id: _, arguments: call_args, } => { args_from_call_arguments(call_args, bundle, args); } InlineExpression::TermReference { id: _, attribute: _, arguments: Some(call_args), } => { args_from_call_arguments(call_args, bundle, args); } InlineExpression::VariableReference { id } => args.push(id.name), InlineExpression::Placeable { expression } => { args_from_expression(expression, bundle, args) } InlineExpression::MessageReference { id, attribute: None, } => { bundle .get_message(&id.name) .and_then(|m| m.value()) .map(|p| args_from_pattern(p, bundle, args)); } InlineExpression::MessageReference { id, attribute: Some(attribute), } => { bundle .get_message(&id.name) .and_then(|m| m.get_attribute(&attribute.name)) .map(|m| m.value()) .map(|p| args_from_pattern(p, bundle, args)); } _ => {} } } fn args_from_call_arguments<'m, R>( call_args: &CallArguments<&'m str>, bundle: &'m FluentBundle, args: &mut Vec<&'m str>, ) where R: std::borrow::Borrow, { call_args.positional.iter().for_each(|expr| { args_from_inline_expression(expr, bundle, args); }); call_args.named.iter().for_each(|named_arg| { args_from_inline_expression(&named_arg.value, bundle, args); }) } i18n-embed-fl-0.10.0/tests/fl_macro.rs000064400000000000000000000100641046102023000154250ustar 00000000000000use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, LanguageLoader, }; use i18n_embed_fl::fl; use rust_embed::RustEmbed; use std::collections::HashMap; #[derive(RustEmbed)] #[folder = "i18n/"] struct Localizations; #[test] fn with_args_hashmap() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); let mut args: HashMap<&str, &str> = HashMap::new(); args.insert("name", "Bob"); pretty_assertions::assert_eq!("Hello \u{2068}Bob\u{2069}!", fl!(loader, "hello-arg", args)); } #[test] fn with_args_hashmap_expr() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); let args_expr = || { let mut args: HashMap<&str, &str> = HashMap::new(); args.insert("name", "Bob"); args }; pretty_assertions::assert_eq!( "Hello \u{2068}Bob\u{2069}!", fl!(loader, "hello-arg", args_expr()) ); } #[test] fn with_loader_expr() { let loader = || { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); loader }; pretty_assertions::assert_eq!("Hello World!", fl!(loader(), "hello-world")); } #[test] fn with_one_arg_lit() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); pretty_assertions::assert_eq!( "Hello \u{2068}Bob\u{2069}!", fl!(loader, "hello-arg", name = "Bob") ); } #[test] fn with_attr() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); pretty_assertions::assert_eq!("Hello, attribute!", fl!(loader, "hello-attr", "text")); } #[test] fn with_attr_and_args() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); pretty_assertions::assert_eq!( "Hello \u{2068}Bob\u{2069}'s attribute!", fl!(loader, "hello-arg", "attr", name = "Bob") ); } #[test] fn with_args_in_messagereference() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); pretty_assertions::assert_eq!( "Hello to you, \u{2068}Bob\u{2069}!", fl!(loader, "hello-recursive", name = "Bob") ); } #[test] fn with_args_in_messagereference_attr() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); pretty_assertions::assert_eq!( "Why hello to you, \u{2068}Bob\u{2069}!", fl!(loader, "hello-recursive", "attr", name = "Bob") ); } #[test] fn with_args_in_messagereference_attr_to_attr() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); pretty_assertions::assert_eq!( "Why hello again, \u{2068}Bob\u{2069}!", fl!(loader, "hello-recursive", "again", name = "Bob") ); } #[test] fn with_args_in_select_messagereference() { let loader: FluentLanguageLoader = fluent_language_loader!(); loader .load_languages(&Localizations, &[loader.fallback_language().clone()]) .unwrap(); pretty_assertions::assert_eq!( "Hello to you, \u{2068}Bob\u{2069}!", fl!(loader, "hello-select", attr = "", name = "Bob") ); pretty_assertions::assert_eq!( "Why hello to you, \u{2068}Bob\u{2069}!", fl!(loader, "hello-select", attr = "yes", name = "Bob") ); }