Easy to use Rust i18n library based on code generation

Overview

rosetta-i18n

Crates.io docs.rs CI

rosetta-i18n is an easy-to-use and opinionated Rust internationalization (i18n) library powered by code generation.

rosetta_i18n::include_translations!();

println!(Lang::En.hello("world"));  // Hello, world!

Features

  • No runtime errors. Translation files are parsed at build time, so your code will never fail due to translations anymore.
  • No dependencies. This crate aims to have the smallest runtime overheat compared to raw strings. There is no additional dependencies at runtime.
  • Standard JSON format. Translations are written in JSON file with a syntax used by many other i18n libraries. Therefore, most translation services support it out of the box.
  • String formatting is supported and 100% safe.

Installation

Rosetta is separated into two crates, rosetta-i18n and rosetta-build. To install both, add the following to your Cargo.toml:

[dependencies]
rosetta-i18n = "0.1"

[build-dependencies]
rosetta-build = "0.1"

Getting started

The following guide explains how to use rosetta-i18 to manage your translations. Please refer to the crate documentation for in depth explanations about the exposed types.

1- Writing translation files

First, we have to write translation files in JSON format, for example in a /locales directory. The format is similar to other translations libraries.

locales/en.json

{
    "hello": "Hello world!",
    "hello_name": "Hello {name}!"
}

In this example, we defined two keys, hello and hello_name. The first is a static string, whereas the second contains the name variable which will be replaced at runtime by the value of your choice. Note that nested key are not (yet?) supported.

Then, create a file for each language you want. It is not required that all files contain all keys. We will define a fallback language later.

2- Generate code from translation files

Code generation use a build script, which will be run each time you edit a translation file. Let's create a build.rs file at the root folder of your crate (same folder as the Cargo.toml file).

build.rs

fn main() -> Result<(), Box<dyn std::error::Error>> {
    rosetta_build::config()
        .source("en", "locales/en.json")
        .source("fr", "locales/fr.json")
        .fallback("en")
        .generate()?;

    Ok(())
}

This script will use the rosetta_build crate. In this example, we define two languages: en and fr. Languages identifiers must be in the ISO 639-1 format (two-letters code). The en language is defined as fallback, so if a key is not defined in our fr.json file, the English string will be used. The output type name can also be customized with the .name() method, as well as the output folder with the .output() method.

Then, call the .generate() method to generate the code. By default, it will be generated in a folder inside the target directory (OUT_DIR env variable).

3- Use generated type

The generated type (named Lang except if you defined another name - see the previous section) must be included in your code with the include_translations macro. A good practice is to isolate it in a dedicated module.

Each translation key is transformed into a method, and each language into an enum variant.

src/main.rs

mod translations {
    rosetta_i18n::include_translations!();
}

fn main() {
    use translations::Lang;

    println!("{}", Lang::En.hello());  // Hello world!
    println!("{}", Lang::En.hello_name("Rust"));  // Hello Rust!
}

Contributing

There is no particular contribution guidelines, feel free to open a new PR to improve the code. If you want to introduce a new feature, please create an issue before.

Comments
  • Parameters order may be inconsistent between builds

    Parameters order may be inconsistent between builds

    Reported on Reddit by scott11x8.

    The parser actually store variables in a HashSet, which has not a consistent and predictable order. This may cause tricky bugs, as order parameters of generated methods may differ across build.

    This can be fixed by sorting parameters alphabetically. Furthermore, this will avoid silent bugs when changing order in source files.

    • [x] Sort parameters alphabetically.
    • [ ] Improve tests for keys with multiple variables.
    bug enhancement 
    opened by baptiste0928 0
  • Parameter order is incorrect and appears to be random.

    Parameter order is incorrect and appears to be random.

    I've found an issue when using multiple parameters, the ordering is incorrect and random.

    Rust:

    &lang.notification_post_reply_body(&person.name, &comment.content, &inbox_link)

    JSON:

    "notification_post_reply_body": "<h1>Post reply</h1><br><div>{username} - {comment_text}</div><br><a href=\"{inbox_link}\">inbox</ a>",

    Yet when I do a hover / check the order, it shows:

    pub fn notification_post_reply_body(&self, comment_text: impl ::std::fmt::Display, inbox_link: impl ::std::fmt::Display, username: impl ::std::fmt::Display) -> ::std::string::String

    Somehow the order of name, comment content, inbox link, got changed to comment content, inbox link, username during the rosetta build process.

    enhancement 
    opened by dessalines 8
  • Helper macro for retrieving translations

    Helper macro for retrieving translations

    The current syntax is quite verbose for something that gets used a lot. It would be nice if there could be a helper macro like get_string!("en", "key", "param1", "param2", ...).

    The macro could also automatically use fallback() if the requested language doesnt exist, so the whole usage would be much easier.

    enhancement 
    opened by Nutomic 2
  • Cant have translation keys which are Rust keywords

    Cant have translation keys which are Rust keywords

    For example, mod.

    --> /home/felix/workspace/lemmy/target/debug/build/lemmy_websocket-5c9ec7d2402ea26c/out/rosetta_output.rs:1:35802
    |
    1 | ...[allow (clippy :: match_single_binding)] pub fn mod (& self) -> & 'static str { match self { _ => "mod" } } # [allow (clippy :: match_...
    |                                                    ^^^ expected identifier, found keyword
    |
    help: you can escape reserved keywords to use them as identifiers
    

    Would be good if identifiers could be escaped, as suggested by the help message. Syntax is like let r#mod = 123;.

    bug 
    opened by Nutomic 1
  • Some usage questions

    Some usage questions

    Thank you for this library! There are very few options for using json localization files in Rust, and this looks like the best one. There are some things which are unclear though, and I hope you can explain them to me:

    • How can i select the language dynamically? The examples only show Lang::En.hello(), but i need something like get_string("en", "hello") in order to select the language at runtime.
    • Can errors about unused arguments be disabled? We are using the same translation files across multiple projects, so its normal that some of them are unused.
      error: named argument never used
      --> /home/felix/workspace/lemmy/target/debug/build/lemmy_websocket-5c9ec7d2402ea26c/out/rosetta_output.rs:1:1957
      |
      1 | ...tring { match self { _ => format ! ("{{count}} Abonnés" , count = count) } } # [allow (clippy :: match_single_binding)] pub fn select_...
      |                                        -------------------           ^^^^^ named argument never used
      |                                        |
      |                                        formatting specifier missing
      
    • When enabling translations for en and fr from these files, it gives another error. Maybe its because we dont follow your string formatting rules, or because the variable has a different name in both language files?
      Compiling lemmy_websocket v0.15.1 (/home/felix/workspace/lemmy/crates/websocket)
      error: failed to run custom build command for `lemmy_websocket v0.15.1 (/home/felix/workspace/lemmy/crates/websocket)`
      
      Caused by:
      process didn't exit successfully: `/home/felix/workspace/lemmy/target/debug/build/lemmy_websocket-d9289556ed2bb147/build-script-build` (exit status: 1)
      --- stdout
      cargo:rerun-if-changed=translations/translations/en.json
      
      --- stderr
      Error: Parse(InvalidType { key: "number_online_plural", expected: "string" })
      

    Thanks in advance :)

    opened by Nutomic 4
  • Provide a function to extract used preferred language from environment

    Provide a function to extract used preferred language from environment

    Detecting user preferred language from environment variable or system configuration seems to be a common use case, so it could be great to provide a ready-to-use function to do that. It will probably be hidden behind a feature to avoid unnecessary additional dependencies.

    The current_locale crate can be used to detect user preferred language.

    Suggestion from Reddit by tlgman.

    enhancement 
    opened by baptiste0928 0
  • Use IETF BCP 47 language tag instead of ISO 639-1

    Use IETF BCP 47 language tag instead of ISO 639-1

    The library actually uses ISO 693-1 languages identifiers, which are very simple languages identifiers (2-letters code), but does allow for precise identifiers. For example, en-US is not a valid ISO 693-1 identifier.

    IETF BCP 47 is a standardized code used by many standards such as HTTP and HTML. This format is compatible with ISO 693-1 identifiers. As for actual identifiers, the library will not provide a way to validate if a specific language tag exists, but will rather focus on parsing and canonization.

    enhancement 
    opened by baptiste0928 0
Releases(v0.1.2)
  • v0.1.2(Oct 25, 2021)

  • v0.1.1(Oct 24, 2021)

    • Parameters of formatted strings are now sorted alphabetically (#2).
    • New documentation website on GitHub pages (1b4aae35619f76486c0f9c0616bc63f01ce5da9f)

    Full Changelog: https://github.com/baptiste0928/rosetta/compare/v0.1.0...v0.1.1

    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Oct 17, 2021)

Owner
Student. Rust enthusiast.
null
A cargo subcommand that extends cargo's capabilities when it comes to code generation.

cargo-px Cargo Power eXtensions Check out the announcement post to learn more about cargo-px and the problems it solves with respect to code generatio

Luca Palmieri 33 May 7, 2023
A Rust library for random number generation.

A Rust library for random number generation.

null 1.3k Jan 6, 2023
Czkawka is a simple, fast and easy to use app to remove unnecessary files from your computer.

Multi functional app to find duplicates, empty folders, similar images etc.

Rafał Mikrut 9.2k Jan 4, 2023
Manage self-hosted Supabase instances with an easy to use API & Web Portal (soon)

SupaManager A project by Harry Bairstow; Manage self-hosted Supabase instances with an easy to use API & Web Portal (soon) ⚠️ Note: The project is in

Harry Bairstow 11 Sep 15, 2022
Arkworks bindings to Circom's R1CS, for Groth16 Proof and Witness generation in Rust.

ark-circom Arkworks bindings to Circom's R1CS, for Groth16 Proof and Witness generation in Rust.

Georgios Konstantopoulos 138 Dec 25, 2022
Fegeya Gretea (aka green tea), new generation programming language.

Fegeya Gretea Gretea (aka green tea), new generation programming language. A taste of Gretea's syntax: import tea.green.fmt module hello { fn hel

Ferhat Geçdoğan 13 Sep 28, 2022
Generate bindings to use Rust code in Qt and QML

Rust Qt Binding Generator This code generator gets you started quickly to use Rust code from Qt and QML. In other words, it helps to create a Qt based

KDE GitHub Mirror 768 Dec 24, 2022
ffizz is a library of utilities for exporting Rust libs for use in other languages

ffizz ffizz is a library of utilities for exporting Rust libs for use in other languages. FFI generally requires a lot of unsafe code, which in turn r

Dustin J. Mitchell 2 Aug 29, 2022
How to use an Arduino library in a Rust project?

Example of an Arduino library usage in a Rust project The project tested with Arduino UNO on Fedora 35. It demonstrates the usage of LiquidCrystal_I2C

Konstantin 6 Dec 27, 2022
A simple quote-based code generator for Rust

flexgen A flexible, yet simple quote-based code generator for creating beautiful Rust code Why? Rust has two types of macros, and they are both very p

Scott Meeuwsen 11 Oct 13, 2022
bevy_blender is a Bevy library that allows you to use assets created in Blender directly from the .blend file

bevy_blender bevy_blender is a Bevy library that allows you to use assets created in Blender directly from the .blend file.

Jerald Thomas 45 Jan 4, 2023
Easy switch between AWS Profiles and Regions

AWSP - CLI To Manage your AWS Profiles! AWSP provides an interactive terminal to interact with your AWS Profiles. The aim of this project is to make i

KubeOps Skills 14 Dec 25, 2022
Implementation of "Complete and Easy Bidirectional Typechecking for Higher-Rank Polymorphism"

Implementation of "Complete and Easy Bidirectional Typechecking for Higher-Rank Polymorphism" See arXiv:1306.6032 This implementation focusses on read

Jakob Demler 95 Dec 20, 2022
A simple, fast, easy README generator

Welcome to Readme Generator A simple, fast, easy README generator. Never worry about READMEs again! What it does: Run the command in your project's di

Dhravya Shah 41 Nov 13, 2022
Give folders & files keywords for easy lookup

Give folders & files keywords for easy lookup

char* 16 Dec 27, 2022
Build your service-server fast, easy (and without hosting!)

service-io is a library to build servers that offering services with really little effort. Choose an input connector. Choose an output connector. Choo

Luis Enrique Muñoz Martín 34 Jan 4, 2023
A stupidly simple and easy to self-host, personal server for file hosting on the web

Grasswave CDN A stupidly simple and easy to self-host, personal server for file hosting on the web. Written in Rust. Thanks, @Maciejowski, for the sty

Rafał Baran 3 Jan 3, 2023
A library for generating TypeScript definitions from rust code.

Tsify is a library for generating TypeScript definitions from rust code. Using this with wasm-bindgen will automatically output the types to .d.

Madono Haru 60 Dec 30, 2022
Rust library to scan files and expand multi-file crates source code as a single tree

syn-file-expand This library allows you to load full source code of multi-file crates into a single syn::File. Features: Based on syn crate. Handling

Vitaly Shukela 11 Jul 27, 2022