diff --git a/README.md b/README.md index 4d7c330..e083534 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-A Rust versions of [LinguiJS Macro](https://lingui.dev/ref/macro) [](https://github.com/lingui/swc-plugin) +A Rust version of [LinguiJS Macro](https://lingui.dev/ref/macro) [](https://github.com/lingui/swc-plugin) [![npm](https://img.shields.io/npm/v/@lingui/swc-plugin?logo=npm&cacheSeconds=1800)](https://www.npmjs.com/package/@lingui/swc-plugin) [![npm](https://img.shields.io/npm/dt/@lingui/swc-plugin?cacheSeconds=500)](https://www.npmjs.com/package/@lingui/swc-plugin) @@ -51,8 +51,17 @@ https://swc.rs/docs/configuration/swcrc // "trans": ["@lingui/react", "Trans"] // } // Lingui strips non-essential fields in production builds for performance. + // Docs https://lingui.dev/guides/optimizing-bundle-size // You can override the default behavior with: // "stripNonEssentialFields": false/true + + // Compatibility option allows to use v6.* SWC Plugin release channel with @lingui/cli@5.* + // Controls the BASE64 alphabet used for generating message IDs. + // - false (default): Uses URL-safe BASE64 alphabet (Lingui v6 behavior) + // - true: Uses standard BASE64 alphabet (Lingui v5 behavior for compatibility) + // + // IMPORTANT: This option is temporal and will be removed in the next major release. + // "useLinguiV5IdGeneration": true }, ], ], @@ -127,6 +136,7 @@ To learn more about SWC Plugins compatibility check this issue https://github.co - Version `0.1.0` ~ `0.*` compatible with `@lingui/core@3.*` - Version `4.*` compatible with `@lingui/core@4.*` - Version `5.*` compatible with `@lingui/core@5.*` +- Version `6.*` compatible with `@lingui/core@5.*` with `useLinguiV5IdGeneration: true` and `@lingui/core@6.*` ## License diff --git a/package.json b/package.json index 25d9302..9425553 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "files": [], "peerDependencies": { - "@lingui/core": "6" + "@lingui/core": "5 || 6" }, "peerDependenciesMeta": { "@swc/core": { diff --git a/src/generate_id.rs b/src/generate_id.rs index 3bf82b7..cca67c5 100644 --- a/src/generate_id.rs +++ b/src/generate_id.rs @@ -1,15 +1,16 @@ -use data_encoding::BASE64URL; +use data_encoding::{BASE64, BASE64URL}; use sha2::{Digest, Sha256}; const UNIT_SEPARATOR: &char = &'\u{001F}'; -pub fn generate_message_id(message: &str, context: &str) -> String { +pub fn generate_message_id(message: &str, context: &str, use_lingui_v5: bool) -> String { let mut hasher = Sha256::new(); hasher.update(format!("{message}{UNIT_SEPARATOR}{context}")); let result = hasher.finalize(); - BASE64URL.encode(result.as_ref())[0..6].into() + let encoder = if use_lingui_v5 { BASE64 } else { BASE64URL }; + encoder.encode(result.as_ref())[0..6].into() } #[cfg(test)] @@ -18,20 +19,32 @@ mod tests { #[test] fn test_generate_message_id() { - assert_eq!(generate_message_id("my message", ""), "vQhkQx") + assert_eq!(generate_message_id("my message", "", false), "vQhkQx") } #[test] fn test_generate_message_id_with_context() { assert_eq!( - generate_message_id("my message", "custom context"), + generate_message_id("my message", "custom context", false), "gGUeZH" ) } #[test] - fn test_generate_message_id_url_save() { + fn test_generate_message_id_url_safe() { // this normally should produce `SO/WB8` but with urlsafe result should be different - assert_eq!(generate_message_id("Hello World", "my context"), "SO_WB8") + assert_eq!( + generate_message_id("Hello World", "my context", false), + "SO_WB8" + ) + } + + #[test] + fn test_generate_message_id_v5_compatibility() { + // When using v5 mode (non-url-safe), should produce BASE64 encoding with / and + + assert_eq!( + generate_message_id("Hello World", "my context", true), + "SO/WB8" + ) } } diff --git a/src/js_macro_folder.rs b/src/js_macro_folder.rs index 4134edf..99f31dd 100644 --- a/src/js_macro_folder.rs +++ b/src/js_macro_folder.rs @@ -35,7 +35,12 @@ where let mut props: Vec = vec![create_key_value_prop( "id", - generate_message_id(&parsed.message_str, "").into(), + generate_message_id( + &parsed.message_str, + "", + self.ctx.options.use_lingui_v5_id_generation, + ) + .into(), )]; if !self.ctx.options.strip_non_essential_fields { @@ -123,6 +128,7 @@ where generate_message_id( &parsed.message_str, context_val.as_deref().unwrap_or_default(), + self.ctx.options.use_lingui_v5_id_generation, ) .into(), )) diff --git a/src/lib.rs b/src/lib.rs index 68af411..cbc4fd9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,8 +135,12 @@ where message_descriptor_props.push(create_key_value_prop( "id", - generate_message_id(&parsed.message_str, &context_attr_val.unwrap_or_default()) - .into(), + generate_message_id( + &parsed.message_str, + &context_attr_val.unwrap_or_default(), + self.ctx.options.use_lingui_v5_id_generation, + ) + .into(), )); } diff --git a/src/options.rs b/src/options.rs index a76743c..2c557ac 100644 --- a/src/options.rs +++ b/src/options.rs @@ -6,6 +6,8 @@ pub struct LinguiJsOptions { runtime_modules: Option, #[serde(default)] strip_non_essential_fields: Option, + #[serde(default)] + use_lingui_v5_id_generation: Option, } #[derive(Deserialize, Debug, PartialEq)] @@ -32,6 +34,7 @@ impl LinguiJsOptions { strip_non_essential_fields: self .strip_non_essential_fields .unwrap_or(matches!(env_name, "production")), + use_lingui_v5_id_generation: self.use_lingui_v5_id_generation.unwrap_or(false), runtime_modules: RuntimeModulesConfigMapNormalized { i18n: ( self.runtime_modules @@ -78,12 +81,14 @@ impl LinguiJsOptions { pub struct LinguiOptions { pub strip_non_essential_fields: bool, pub runtime_modules: RuntimeModulesConfigMapNormalized, + pub use_lingui_v5_id_generation: bool, } impl Default for LinguiOptions { fn default() -> LinguiOptions { LinguiOptions { strip_non_essential_fields: false, + use_lingui_v5_id_generation: false, runtime_modules: RuntimeModulesConfigMapNormalized { i18n: ("@lingui/core".into(), "i18n".into()), trans: ("@lingui/react".into(), "Trans".into()), @@ -128,6 +133,7 @@ mod lib_tests { )), }), strip_non_essential_fields: None, + use_lingui_v5_id_generation: None, } ) } @@ -152,6 +158,7 @@ mod lib_tests { use_lingui: None, }), strip_non_essential_fields: None, + use_lingui_v5_id_generation: None, } ) } @@ -203,4 +210,52 @@ mod lib_tests { let options = config.into_options("production"); assert!(options.strip_non_essential_fields); } + + #[test] + fn test_use_lingui_v5_id_generation_config() { + let config = serde_json::from_str::( + r#"{ + "useLinguiV5IdGeneration": true, + "runtimeModules": {} + }"#, + ) + .unwrap(); + + let options = config.into_options("development"); + assert!(options.use_lingui_v5_id_generation); + + let config = serde_json::from_str::( + r#"{ + "useLinguiV5IdGeneration": false, + "runtimeModules": {} + }"#, + ) + .unwrap(); + + let options = config.into_options("production"); + assert!(!options.use_lingui_v5_id_generation); + } + + #[test] + fn test_use_lingui_v5_id_generation_default() { + let config = serde_json::from_str::( + r#"{ + "runtimeModules": {} + }"#, + ) + .unwrap(); + + let options = config.into_options("development"); + assert!(!options.use_lingui_v5_id_generation); + + let config = serde_json::from_str::( + r#"{ + "runtimeModules": {} + }"#, + ) + .unwrap(); + + let options = config.into_options("production"); + assert!(!options.use_lingui_v5_id_generation); + } } diff --git a/tests/__swc_snapshots__/tests/js_t.rs/js_should_use_v5_generate_id_with_parameter.js b/tests/__swc_snapshots__/tests/js_t.rs/js_should_use_v5_generate_id_with_parameter.js new file mode 100644 index 0000000..d80eea9 --- /dev/null +++ b/tests/__swc_snapshots__/tests/js_t.rs/js_should_use_v5_generate_id_with_parameter.js @@ -0,0 +1,6 @@ +import { i18n as $_i18n } from "@lingui/core"; +$_i18n._(/*i18n*/ { + id: "SO/WB8", + message: "Hello World", + context: "my context" +}); diff --git a/tests/js_t.rs b/tests/js_t.rs index 6816127..f6e799c 100644 --- a/tests/js_t.rs +++ b/tests/js_t.rs @@ -222,3 +222,18 @@ to!( t({ message: 'Ola', context: `My Context`}) "# ); + +to!( + js_should_use_v5_generate_id_with_parameter, + LinguiOptions { + use_lingui_v5_id_generation: true, + ..Default::default() + }, + r#" + import { t } from '@lingui/core/macro' + t({ + message: "Hello World", + context: "my context" + }); + "# +); diff --git a/yarn.lock b/yarn.lock index 299f113..77c22ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -228,7 +228,7 @@ __metadata: typescript: "npm:^5.9.3" vitest: "npm:^4.0.18" peerDependencies: - "@lingui/core": 6 + "@lingui/core": 5 || 6 peerDependenciesMeta: "@swc/core": optional: true