Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<div align="center">

A Rust versions of [LinguiJS Macro](https://lingui.dev/ref/macro) [<img src="https://img.shields.io/badge/beta-yellow"/>](https://github.com/lingui/swc-plugin)
A Rust version of [LinguiJS Macro](https://lingui.dev/ref/macro) [<img src="https://img.shields.io/badge/beta-yellow"/>](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)
Expand Down Expand Up @@ -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
},
],
],
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"files": [],
"peerDependencies": {
"@lingui/core": "6"
"@lingui/core": "5 || 6"
},
"peerDependenciesMeta": {
"@swc/core": {
Expand Down
27 changes: 20 additions & 7 deletions src/generate_id.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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"
)
}
}
8 changes: 7 additions & 1 deletion src/js_macro_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ where

let mut props: Vec<PropOrSpread> = 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 {
Expand Down Expand Up @@ -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(),
))
Expand Down
8 changes: 6 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
));
}

Expand Down
55 changes: 55 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub struct LinguiJsOptions {
runtime_modules: Option<RuntimeModulesConfigMap>,
#[serde(default)]
strip_non_essential_fields: Option<bool>,
#[serde(default)]
use_lingui_v5_id_generation: Option<bool>,
}

#[derive(Deserialize, Debug, PartialEq)]
Expand All @@ -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
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -128,6 +133,7 @@ mod lib_tests {
)),
}),
strip_non_essential_fields: None,
use_lingui_v5_id_generation: None,
}
)
}
Expand All @@ -152,6 +158,7 @@ mod lib_tests {
use_lingui: None,
}),
strip_non_essential_fields: None,
use_lingui_v5_id_generation: None,
}
)
}
Expand Down Expand Up @@ -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::<LinguiJsOptions>(
r#"{
"useLinguiV5IdGeneration": true,
"runtimeModules": {}
}"#,
)
.unwrap();

let options = config.into_options("development");
assert!(options.use_lingui_v5_id_generation);

let config = serde_json::from_str::<LinguiJsOptions>(
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::<LinguiJsOptions>(
r#"{
"runtimeModules": {}
}"#,
)
.unwrap();

let options = config.into_options("development");
assert!(!options.use_lingui_v5_id_generation);

let config = serde_json::from_str::<LinguiJsOptions>(
r#"{
"runtimeModules": {}
}"#,
)
.unwrap();

let options = config.into_options("production");
assert!(!options.use_lingui_v5_id_generation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { i18n as $_i18n } from "@lingui/core";
$_i18n._(/*i18n*/ {
id: "SO/WB8",
message: "Hello World",
context: "my context"
});
15 changes: 15 additions & 0 deletions tests/js_t.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
});
"#
);
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down