Skip to content

Commit

Permalink
Merge pull request #143 from Baptistemontan/json5
Browse files Browse the repository at this point in the history
Support for JSON5 file format
  • Loading branch information
Baptistemontan authored Oct 13, 2024
2 parents e9ca44f + dac30c9 commit 3782e27
Show file tree
Hide file tree
Showing 18 changed files with 456 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ jobs:
strategy:
fail-fast: false
matrix:
tests_suites: [json, yaml, namespaces]
tests_suites: [json, json5, yaml, namespaces]
steps:
- name: "Checkout repo"
uses: actions/checkout@v4
Expand Down
8 changes: 5 additions & 3 deletions leptos_i18n/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ dynamic_load = [
show_keys_only = ["leptos_i18n_macro/show_keys_only"]
suppress_key_warnings = ["leptos_i18n_macro/suppress_key_warnings"]
json_files = ["leptos_i18n_macro/json_files"]
json5_files = ["leptos_i18n_macro/json5_files"]
yaml_files = ["leptos_i18n_macro/yaml_files"]
interpolate_display = ["leptos_i18n_macro/interpolate_display"]
track_locale_files = ["leptos_i18n_macro/track_locale_files"]
Expand All @@ -115,9 +116,10 @@ track_locale_files = ["leptos_i18n_macro/track_locale_files"]
[package.metadata.cargo-all-features]
denylist = [
# Always exclude:
"ssr", # Should always be enabled via a server integration rather than directly
"yaml_files", # See leptos_i18n_macro manifest to see why "yaml_files" and other formats are in deny list and JSON is always included
"nightly", # Requires a nightly toolchain
"ssr", # Should always be enabled via a server integration rather than directly
"yaml_files", # See leptos_i18n_macro manifest to see why "yaml_files" and other formats are in deny list and JSON is always included
"json5_files",
"nightly", # Requires a nightly toolchain

# Only passed through to `leptos_i18n_macros`, exclude to save time:
"serde",
Expand Down
6 changes: 4 additions & 2 deletions leptos_i18n_macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ syn = "2.0"
toml = "0.8"
icu = "1.5"
fixed_decimal = { version = "0.5", features = ["ryu"] }
json5 = { version = "0.4", optional = true }

[features]
default = ["json_files"]
nightly = []
suppress_key_warnings = []
json_files = []
yaml_files = ["serde_yaml"]
yaml_files = ["dep:serde_yaml"]
json5_files = ["dep:json5"]
interpolate_display = []
track_locale_files = []
experimental-islands = []
Expand All @@ -46,5 +48,5 @@ icu_compiled_data = []

[package.metadata.cargo-all-features]
# cargo-all-features don't provide a way to always include one feature in a set, so CI will just do json...
denylist = ["nightly", "yaml_files"]
denylist = ["nightly", "yaml_files", "json5_files"]
always_include_features = ["json_files"]
53 changes: 43 additions & 10 deletions leptos_i18n_macro/src/load_locales/locale.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
collections::{BTreeMap, HashSet},
fs::File,
io::{BufReader, Read},
path::{Path, PathBuf},
rc::Rc,
};
Expand All @@ -19,20 +20,24 @@ use crate::utils::key::{Key, KeyPath, VAR_COUNT_KEY};

macro_rules! define_by_format {
(json => $($tt:tt)*) => {
#[cfg(all(feature = "json_files", not(any(feature = "yaml_files"))))]
#[cfg(all(feature = "json_files", not(any(feature = "yaml_files", feature = "json5_files"))))]
$($tt)*
};
(yaml => $($tt:tt)*) => {
#[cfg(all(feature = "yaml_files", not(any(feature = "json_files"))))]
#[cfg(all(feature = "yaml_files", not(any(feature = "json_files", feature = "json5_files"))))]
$($tt)*
};
(json5 => $($tt:tt)*) => {
#[cfg(all(feature = "json5_files", not(any(feature = "json_files", feature = "yaml_files"))))]
$($tt)*
};
(none => $($tt:tt)*) => {
#[cfg(not(any(feature = "json_files", feature = "yaml_files")))]
#[cfg(not(any(feature = "json_files", feature = "yaml_files", feature = "json5_files")))]
$($tt)*
};
// for now use cfg(all(..)) but if any format is added found a better cfg.
// This is attrocious, found a better way fgs
(multiple => $($tt:tt)*) => {
#[cfg(all(feature = "json_files", feature = "yaml_files"))]
#[cfg(any(all(feature = "json_files", feature = "yaml_files"), all(feature = "json_files", feature = "json5_files"), all(feature = "yaml_files", feature = "json5_files")))]
$($tt)*
}
}
Expand All @@ -52,36 +57,63 @@ macro_rules! define_files_exts {
};
}

#[cfg(feature = "json5_files")]
#[derive(Debug)]
pub enum Json5Error {
Serde(json5::Error),
Io(std::io::Error),
}

#[cfg(feature = "json5_files")]
impl std::fmt::Display for Json5Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Json5Error::Serde(error) => std::fmt::Display::fmt(error, f),
Json5Error::Io(error) => std::fmt::Display::fmt(error, f),
}
}
}

define_error!(json => serde_json::Error);
define_error!(json5 => Json5Error);
define_error!(yaml => serde_yaml::Error);
define_error!(none => &'static str); // whatever impl Display
define_error!(multiple => &'static str); // whatever impl Display

define_files_exts!(json => "json");
define_files_exts!(json5 => "json5");
define_files_exts!(yaml => "yaml", "yml");
define_files_exts!(none);
define_files_exts!(multiple);

define_by_format!(json =>
fn de_inner(locale_file: File, seed: LocaleSeed) -> Result<Locale, SerdeError> {
fn de_inner<R: Read>(locale_file: R, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let mut deserializer = serde_json::Deserializer::from_reader(locale_file);
serde::de::DeserializeSeed::deserialize(seed, &mut deserializer)
}
);
define_by_format!(json5 =>
fn de_inner<R: Read>(mut locale_file: R, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let mut buff = String::new();
Read::read_to_string(&mut locale_file, &mut buff).map_err(Json5Error::Io)?;
let mut deserializer = json5::Deserializer::from_str(&buff).map_err(Json5Error::Serde)?;
serde::de::DeserializeSeed::deserialize(seed, &mut deserializer).map_err(Json5Error::Serde)
}
);
define_by_format!(yaml =>
fn de_inner(locale_file: File, seed: LocaleSeed) -> Result<Locale, SerdeError> {
fn de_inner<R: Read>(locale_file: R, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let deserializer = serde_yaml::Deserializer::from_reader(locale_file);
serde::de::DeserializeSeed::deserialize(seed, deserializer)
}
);
define_by_format!(none =>
fn de_inner(locale_file: File, seed: LocaleSeed) -> Result<Locale, SerdeError> {
fn de_inner<R: Read>(locale_file: R, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let _ = (locale_file, seed);
compile_error!("No file format has been provided for leptos_i18n, supported formats are: json and yaml")
}
);
define_by_format!(multiple =>
fn de_inner(locale_file: File, seed: LocaleSeed) -> Result<Locale, SerdeError> {
fn de_inner<R: Read>(locale_file: R, seed: LocaleSeed) -> Result<Locale, SerdeError> {
let _ = (locale_file, seed);
compile_error!("Multiple file format have been provided for leptos_i18n, choose only one, supported formats are: json and yaml")
}
Expand Down Expand Up @@ -272,7 +304,8 @@ impl Locale {
}

fn de(locale_file: File, path: &mut PathBuf, seed: LocaleSeed) -> Result<Self> {
de_inner(locale_file, seed).map_err(|err| Error::LocaleFileDeser {
let reader = BufReader::new(locale_file);
de_inner(reader, seed).map_err(|err| Error::LocaleFileDeser {
path: std::mem::take(path),
err,
})
Expand Down
3 changes: 3 additions & 0 deletions tests/json5/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Cargo.lock
target
!.vscode
5 changes: 5 additions & 0 deletions tests/json5/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"lokalise.i18n-ally",
]
}
10 changes: 10 additions & 0 deletions tests/json5/.vscode/i18n-ally-custom-framework.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
languageIds:
- rust

usageMatchRegex:
- "[^\\w\\d]t!\\(\\s*[\\w.:]*,\\s*([\\w.]*)"
- "[^\\w\\d]td!\\(\\s*[\\w.:]*,\\s*([\\w.]*)"
- "[^\\w\\d]td_string!\\(\\s*[\\w.:]*,\\s*([\\w.]*)"
- "[^\\w\\d]td_display!\\(\\s*[\\w.:]*,\\s*([\\w.]*)"

monopoly: true
4 changes: 4 additions & 0 deletions tests/json5/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": "locales"
}
23 changes: 23 additions & 0 deletions tests/json5/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "json5"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
leptos = { version = "0.7.0-gamma2", features = ["ssr"] }
tests_common = { path = "../common" }
leptos_i18n = { path = "../../leptos_i18n", default-features = false, features = [
"json5_files",
"icu_compiled_data",
"track_locale_files",
] }


[package.metadata.leptos-i18n]
default = "en"
locales = ["en", "fr"]
8 changes: 8 additions & 0 deletions tests/json5/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# YAML files format tests

Test 2 things:

- Everything compile fine
- Check if the output is correct

Disclaimer: the yaml files are slightly tweaked "JSON to YAML" outputs, if it looks ugly it's because it's only 3.14% hand made.
41 changes: 41 additions & 0 deletions tests/json5/locales/en.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
click_to_change_lang: "Click to change language",
click_count: "You clicked {{ count }} times",
click_to_inc: "Click to increment the counter",
f32_range: [
"f32",
["You are broke", 0.0],
["You owe money", "..0.0"],
["You have {{ count }}€"],
],
u32_range: ["u32", [0, 0], ["1..", "1.."]],
OR_range: [
"u8",
["0 or 5", "0", 5],
["1..5 | 6..10", "1..5", "6..10"],
["10..15 | 20", "10..15", 20],
["fallback with no count"],
],
f32_OR_range: [
"f32",
["0 or 5", 0, 5],
["1..5 | 6..10", "1..5", "6..10"],
["10..15 | 20", "10..15", 20],
["fallback with no count"],
],
subkeys: {
subkey_1: "subkey_1",
subkey_2: "<b>subkey_2</b>",
subkey_3: [
["zero", 0],
["one", 1],
["{{ count }}", "_"],
],
},
defaulted_string: "this string is declared in locale en",
defaulted_interpolation: "this interpolation is declared in locale {{ locale }}",
defaulted_ranges: [
["zero", 0],
["this range is declared in locale {{ locale }}"],
],
}
37 changes: 37 additions & 0 deletions tests/json5/locales/fr.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
click_to_change_lang: "Cliquez pour changez de langue",
click_count: "Vous avez cliqué {{ count }} fois",
click_to_inc: "Cliquez pour incrémenter le compteur",
f32_range: [
"f32",
["Vous êtes pauvre", 0.0],
["Vous devez de l'argent", "..0.0"],
["Vous avez {{ count }}€"],
],
u32_range: ["u32", [0, 0], ["1..", "1.."]],
OR_range: [
"u8",
["0 or 5", "0", 5],
["1..5 | 6..10", "1..5", "6..10"],
["10..15 | 20", "10..15", 20],
["fallback sans count"],
],
f32_OR_range: [
"f32",
["0 or 5", 0, 5],
["1..5 | 6..10", "1..5", "6..10"],
["10..15 | 20", "10..15", 20],
["fallback avec tuple vide", []],
],
subkeys: {
subkey_1: "subkey_1",
subkey_2: "<b>subkey_2</b>",
subkey_3: [
["0", 0],
["{{ count }}", "_"],
],
},
defaulted_string: null,
defaulted_interpolation: null,
defaulted_ranges: null,
}
35 changes: 35 additions & 0 deletions tests/json5/src/defaulted.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use crate::i18n::*;
use tests_common::*;

#[test]
fn defaulted_string() {
let en = td!(Locale::en, defaulted_string);
assert_eq_rendered!(en, "this string is declared in locale en");
let fr = td!(Locale::fr, defaulted_string);
assert_eq_rendered!(fr, "this string is declared in locale en");
}

#[test]
fn defaulted_interpolation() {
let en = td!(Locale::en, defaulted_interpolation, locale = "en");
assert_eq_rendered!(en, "this interpolation is declared in locale en");
let fr = td!(Locale::fr, defaulted_interpolation, locale = "en");
assert_eq_rendered!(fr, "this interpolation is declared in locale en");
}

#[test]
fn defaulted_ranges() {
let count = || 0;
let en = td!(Locale::en, defaulted_ranges, locale = "en", count = count);
assert_eq_rendered!(en, "zero");
let fr = td!(Locale::fr, defaulted_ranges, locale = "en", count = count);
assert_eq_rendered!(fr, "zero");

for i in [-3, 5, 12] {
let count = move || i;
let en = td!(Locale::en, defaulted_ranges, locale = "en", count = count);
assert_eq_rendered!(en, "this range is declared in locale en");
let fr = td!(Locale::fr, defaulted_ranges, locale = "en", count = count);
assert_eq_rendered!(fr, "this range is declared in locale en");
}
}
14 changes: 14 additions & 0 deletions tests/json5/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![deny(warnings)]
leptos_i18n::load_locales!();

#[cfg(test)]
mod ranges;

#[cfg(test)]
mod subkeys;

#[cfg(test)]
mod tests;

#[cfg(test)]
mod defaulted;
Loading

0 comments on commit 3782e27

Please sign in to comment.