Skip to content

Commit

Permalink
Merge pull request #139 from Baptistemontan/dyn_load
Browse files Browse the repository at this point in the history
Dyn load
  • Loading branch information
Baptistemontan authored Oct 12, 2024
2 parents 5c4b1e1 + 1d16093 commit e9ca44f
Show file tree
Hide file tree
Showing 108 changed files with 4,372 additions and 477 deletions.
56 changes: 54 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
run: cargo test --package leptos_i18n_macro

test_ssr_examples:
name: Test ${{ matrix.examples }} example
name: Test ${{ matrix.examples }} SSR example
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down Expand Up @@ -106,8 +106,60 @@ jobs:
name: playwright-report-${{ matrix.examples }}
path: examples/ssr/${{ matrix.examples }}/playwright-report/

test_dyn_load_examples:
name: Test ${{ matrix.examples }} dynamic_load example
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
examples: [hello_world_actix, hello_world_axum, axum_island, namespaces]
steps:
- name: "Checkout repo"
uses: actions/checkout@v4

- name: "Setup Node"
uses: actions/setup-node@v4
with:
node-version: 18

- name: "Setup Rust toolchain"
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "stable"
targets: "wasm32-unknown-unknown"

- name: "Install cargo-leptos"
run: cargo install cargo-leptos --locked

- name: "Build ${{ matrix.examples }} example"
working-directory: examples/dynamic_load/${{ matrix.examples }}
run: cargo leptos build

- name: "Build e2e utils"
working-directory: examples/utils
run: npm ci

- name: "Install Playwright"
working-directory: examples/dynamic_load/${{ matrix.examples }}
run: |
npm ci
npx playwright install --with-deps
- name: "e2e testing ${{ matrix.examples }} example"
id: e2e
working-directory: examples/dynamic_load/${{ matrix.examples }}
run: npx playwright test

- name: "Upload e2e report"
uses: actions/upload-artifact@v4
# upload artifact only if e2e test failed
if: ${{ steps.e2e.outcome == 'failure' && !cancelled() }}
with:
name: playwright-report-${{ matrix.examples }}
path: examples/dynamic_load/${{ matrix.examples }}/playwright-report/

test_csr_examples:
name: Test ${{ matrix.examples }} example
name: Test ${{ matrix.examples }} CSR example
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
[workspace]
resolver = "2"
members = ["leptos_i18n", "leptos_i18n_macro", "tests/json", "tests/common"]
members = [
"leptos_i18n",
"leptos_i18n_macro",
"tests/json",
"tests/common",
"tests/namespaces",
]
exclude = ["examples", "tests"]

[workspace.package]
Expand Down
1 change: 1 addition & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
- [Locale Resolution](./infos/01_locale_resol.md)
- [Reduce Binary Size](./reduce_size/README.md)
- [ICU4X Datagen](./reduce_size/01_datagen.md)
- [Lazy Load The Translations](./reduce_size/02_dynamic_load.md)
- [Features](./06_features.md)
- [Appendix: `i18n Ally` extension for VSC](./appendix_i18n_ally.md)
53 changes: 53 additions & 0 deletions docs/book/src/reduce_size/02_dynamic_load.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Dynamic loading of translations

## Why use it ?

By default the translations are loaded at compile time and are baked into the binary,
this has some performance advantages but comes at a cost: binary size.
This is fine when the number of keys and locales are small and the values are not long,
but when supporting a high number of locales and with a lot of keys, binary sizes start to highly increase.

The `"dynamic_load"` feature reduce this binary size increase by removing the baked translations in the binary, and lazy load them on the client.
The way it does that is by using a server function to request the translations in a given "translation unit".
What I call "translation unit" is a group of translations, they are either one unit per locale, one unit per locale per namespaces if you use them.

## How it works

When using SSR, the server will register every units used for a given request and bake only the used one in the sent HTML,
they are then parsed when the client hydrate, so no request for translations is done on page load.
When the client need access to an unloaded unit, it will request it to the server and will update the view when received.

## What changes ?

### Async accessors

For obvious reason, with the `"dynamic_load"` accessing a value is now async, `t!`, `td!` and `tu!` still return `impl Fn() -> impl IntoView`,
as the async part is handled inside of it with some optimizations, but the `*_display!` and `*_string!` variants now return a future and need to be awaited.
(You can turn them into some kind of `Signal<Option<String>>` using leptos `AsyncDerived::new_unsync`)

They are technically not needed to be async on the server, as translations are still baked in for them,
but for the API to be the same on the client and the server they return the value wrapped in an async bloc.

### Server Fn

If you use a backend that need to manually register server functions,
you can use the `ServerFn` associated type on the `Locale` trait implemented by the generated `Locale` enum:

```rust
use i18n::Locale;
use leptos_i18n::Locale as LocaleTrait;

register_server_fn::<<Locale as LocaleTrait>::ServerFn>();
```

## Final note

Other than that, this is mostly a drop in feature and do not require much from the user.

## Disclaimers

1. There is a chance that enabling this feature actually increase binary sizes if there isn't much translations,
as there is additional code being generated to request, parse and load the translations. But this is mostly a fixed cost,
so with enough translations the trade will be beneficial. So do some testing.

2. Only the raw strings are removed from the binary, the code to display each keys is still baked in it, whatever the locale or the namespace.
9 changes: 9 additions & 0 deletions examples/dynamic_load/axum_island/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Cargo.lock
target
!.vscode

node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
5 changes: 5 additions & 0 deletions examples/dynamic_load/axum_island/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"lokalise.i18n-ally",
]
}
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 examples/dynamic_load/axum_island/.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"
}
106 changes: 106 additions & 0 deletions examples/dynamic_load/axum_island/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
[package]
name = "axum_island"
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]
axum = { version = "0.7", optional = true }
leptos = { version = "0.7.0-beta", features = ["experimental-islands"] }
leptos_meta = { version = "0.7.0-beta" }
leptos_axum = { version = "0.7.0-beta", optional = true }
leptos_i18n = { path = "../../../leptos_i18n", features = [
"track_locale_files",
"experimental-islands",
"dynamic_load",
] }
serde = { version = "1", features = ["derive"] }
console_error_panic_hook = { version = "0.1", optional = true }
wasm-bindgen = { version = "0.2", optional = true }
simple_logger = "4"
tokio = { version = "1.35", features = ["rt-multi-thread"], optional = true }
log = "0.4"
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }

[features]
default = ["hydrate", "ssr"]
hydrate = [
"dep:console_error_panic_hook",
"dep:wasm-bindgen",
"leptos/hydrate",
"leptos_i18n/hydrate",
]
ssr = [
"dep:axum",
"dep:tokio",
"dep:tower",
"dep:tower-http",
"dep:leptos_axum",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_i18n/axum",
]

[package.metadata.leptos-i18n]
default = "en"
locales = ["en", "fr"]

[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "axum_island"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
# When NOT using cargo-leptos this must be updated to "." or the counters will not work. The above warning still applies if you do switch to cargo-leptos later.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
# style-file = "src/styles/tailwind.css"
# [Optional] Files in the asset-dir will be copied to the site-root directory
assets-dir = "public"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-addr = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
end2end-cmd = "npx playwright test"
end2end-dir = "e2e"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target
#
# Optional. Can be over-ridden with the command line parameter --bin-features
bin-features = ["ssr"]

# If the --no-default-features flag should be used when compiling the bin target
#
# Optional. Defaults to false.
bin-default-features = false

# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features
lib-features = ["hydrate"]

# If the --no-default-features flag should be used when compiling the lib target
#
# Optional. Defaults to false.
lib-default-features = false

# Additional files your application could depends on.
# A change to any file in those directories will trigger a rebuild.
#
# Optional.
watch-additional-files = ["locales"]

[package.metadata.cargo-all-features]
skip_feature_sets = [["hydrate", "ssr"]]
17 changes: 17 additions & 0 deletions examples/dynamic_load/axum_island/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Hello World Example

This example showcase how to use `leptos_i18n` with experimental `island` feature from Leptos.

## How to run

Simply use `cargo_leptos` to run it:

```sh
cargo leptos watch
```

and to build:

```sh
cargo leptos build --release
```
Loading

0 comments on commit e9ca44f

Please sign in to comment.