Giter Club home page Giter Club logo

frenchkiss.js's Introduction

Logo

๐Ÿ’‹ FrenchKiss.js

File size License: MIT

Description

FrenchKiss.js is a blazing fast lightweight i18n library written in JavaScript, working both in the browser and NodeJS environments. It provides a simple and really fast solution for handling internationalization.

FrenchKiss is by now, the fastest i18n JS package out there, working 5 to 1000 times faster than any others by JIT compiling the translations, try it by running the benchmarks !

Minimum requirements:

Node 0.10 IE 9

โš ๏ธ Frenchkiss is internaly using new Function() to create optimized functions. Therefore it can conflict when using CSP (Content Security Policy) rules. You can bypass it by disabling it by using the CSP keyword unsafe-eval, but it is generally not recommended as it would weaken the protections offered by CSP. We will eventually work on a new version offering pre-compilation.

๐Ÿ“– Table of content

๐Ÿš€ Installation

Install with yarn:

$ yarn add frenchkiss

Or install using npm:

$ npm i frenchkiss

๐Ÿ–ฅ๏ธ How to use

Minimal code

Tell FrenchKiss what to return by simply giving it a table object, where the key is the search reference and the value is the already-translated string.

import frenchkiss from 'frenchkiss';

// Define the locale language
frenchkiss.locale('en');

// Add translations in each languages
frenchkiss.set('en', {
  hello: 'Hello {name} !',
  fruits: {
    apple: 'apples',
  },
  // and other sentences...
});

frenchkiss.t('hello', {
  name: 'John',
}); // => 'Hello John !'

frenchkiss.t('fruits.apple'); // => 'apples'

frenchkiss.locale(language?: string): string

Get or set the locale, it will define what table FrenchKiss have to work with.

Note: If you are working with NodeJS and concurrent requests, you can use the third parameter (language) of t() to avoid language collision.

import frenchkiss from 'frenchkiss';

frenchkiss.locale('fr_FR'); // => 'fr_FR'
frenchkiss.locale(); // => 'fr_FR'
frenchkiss.locale('en_GB'); // => 'en_GB'

frenchkiss.set(language: string, table: object)

Define the translation table for the language. Any call to the specified language erase all the previously stored data.

import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  hello: 'Hi, ',
  howareyou: 'How are you ?',
  // ...
});

frenchkiss.t(key: string, params?: object, lang?: string): string

The most used method to returns translation. It's built with performance in mind. Here is what you should know about it :

  • โœ… It does support multiple interpolation variable
  • โœ… It supports interpolation.
  • โœ… It supports PLURAL.
  • โœ… It supports SELECT.
  • โœ… It supports nested PLURAL, SELECT and variables.
  • โœ… It supports nested keys (using dots in keys).
  • โŒ It does not support date, number, currency formatting (maybe check for Intl.NumberFormat and Intl.DateTimeFormat).
import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  hello: 'Hello {name} !',
});

frenchkiss.t('hello'); // => 'Hello  !'
frenchkiss.t('hello', { name: 'John' }); // => 'Hello John !'
frenchkiss.t('hello', { name: 'Anna' }); // => 'Hello Anna !'

Note: By default, if no parameters are given it will be interpreted as an empty string.

If you are working with concurrent connections it's also possible to use the third parameter lang to force the language to use. Doing a generator that forces the language use and pass it to your function can be what you are looking for.

import frenchkiss from 'frenchkiss';

frenchkiss.locale('fr');
frenchkiss.set('en', {
  hello: 'Hello {name} !',
});

// Helper
const generateLanguageTranslator = (lang) => {
  return (key, params) => frenchkiss.t(key, params, lang);
};

// Generate t that force language
const t = generateLanguageTranslator('en');

// Force result in english
t('hello'); // => 'Hello  !'
t('hello', { name: 'John' }); // => 'Hello John !'
t('hello', { name: 'Anna' }); // => 'Hello Anna !'

frenchkiss.extend(language: string, table: object)

Extend the translation table for the language. In contrary of set(), the previously stored data will be kept.

import frenchkiss from 'frenchkiss';

frenchkiss.extend('en', {
  // The next two lines have already been set
  // hello: 'Hi, ',
  // howareyou: 'How are you ?',
  greatandyou: 'Great and you ?',
  // ...
});

frenchkiss.unset(language: string)

If you need to clean the data of a stored language for memory optimizations, unset is all you need.

import frenchkiss from 'frenchkiss';

frenchkiss.unset('en_GB');

frenchkiss.fallback(language?: string): string

Get or set the fallback. Define what table FrenchKiss will use to fallback in case the locale table doesn't have the required translation.

import frenchkiss from 'frenchkiss';

frenchkiss.set('fr', {
  hello: 'Bonjour, ',
});

frenchkiss.set('en', {
  hello: 'Hi, ',
  howareyou: 'How are you ?',
});

frenchkiss.locale('fr');
frenchkiss.fallback('en');

frenchkiss.t('hello'); // => 'Bonjour, ' <- from 'fr' locale
frenchkiss.t('howareyou'); // => 'How are you ?' <- from 'en' fallback

frenchkiss.onMissingKey(fn: Function)

When the client requests a missing key, frenchKiss will returns the key as result. It's possible to handle it and return what you want or just send an event to your error reporting system.

import frenchkiss from 'frenchkiss';

frenchkiss.t('missingkey'); // => 'missingkey'

frenchkiss.onMissingKey((key, params, locale) => {
  // Send error to your server
  sendReport(`Missing the key "${key}" in ${frenchkiss.locale()} language.`);

  // Returns the text you want
  return `An error happened (${key})`;
});

frenchkiss.t('missingkey'); // => 'An error happened (missingkey)'

frenchkiss.onMissingVariable(fn: Function)

It's possible to handle missing variables, sending errors to your monitoring server or handle it directly by returning something to replace with.

import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  hello: 'Hello {name} !',
});
frenchkiss.locale('en');

frenchkiss.t('hello'); // => 'Hello  !'

frenchkiss.onMissingVariable((variable, key, language) => {
  // Send error to your server
  sendReport(`Missing the variable "${variable}" in ${language}->${key}.`);

  // Returns the text you want
  return `[missing:${variable}]`;
});

frenchkiss.t('hello'); // => 'Hello [missing:name] !'

Nested keys

Under the hood, frenchkiss allows you to handle nested keys, by using '.' inside key names.

import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  fruits: {
    apple: 'An apple',
    banana: 'A banana',
  },
  vegetables: {
    carrot: 'A carrot',
    daikon: 'A daikon',
  },
});

frenchkiss.t('fruits.apple'); // => 'An apple'

Accessing an object directly will result on the onMissingKey method to be called:

import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  fruits: {
    apple: 'An apple',
    banana: 'A banana',
  },
});

frenchkiss.onMissingKey((key) => `[notfound:${key}]`);
frenchkiss.t('fruits'); // => '[notfound:fruits]'

In case of duplicate names on key and objects, do not expect the result to be uniform (in fact, just don't do it).

import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  'fruits.apple.green': 1,
  'fruits.apple': {
    'green': 2
  },
  'fruits': {
    'apple.green': 3
    'apple': {
      'green': 4
    }
  }
});

frenchkiss.t('fruits.apple.green'); // => '1' or '2' or '3' or '4'

SELECT expression

If you need to display different text messages depending on the value of a variable, you need to translate all of those text messages... or you can handle this with a select ICU expression.

import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  your_pet:
    'You own {pet, select, dog{a good boy} cat{an evil cat} other{a {pet} ! What is that?}}!',
});

frenchkiss.t('your_pet', { pet: 'dog' }); // => 'You own a good boy!'
frenchkiss.t('your_pet', { pet: 'cat' }); // => 'You own an evil cat!'
frenchkiss.t('your_pet', { pet: 'rat' }); // => 'You own a rat ! What is that?!'
  • The first parameter is the variable you want to check (pet).
  • The second parameter identifies this as a select expression type.
  • The third parameter is a pattern consisting of keys and their matching values.

Phrases support select expression, based on ICU FormatMessage.


PLURAL expression

It's basically the same as select, except you have to use the "=" symbol for direct checking.

import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  bought_apple:
    'I {count, plural, =0{bought no apples} =1{bought one apple} other{bought {count} apples}}!',
});

frenchkiss.t('bought_apple', { count: 0 }); // => 'I bought no apples!'
frenchkiss.t('bought_apple', { count: 1 }); // => 'I bought one apple!'
frenchkiss.t('bought_apple', { count: 5 }); // => 'I bought 5 apples!'
  • The first parameter is the variable you want to check.
  • The second parameter identifies this as a plural expression type.
  • The third parameter is a pattern consisting of keys and their matching values.

โš ๏ธ Like the select expression, the plural is a lightweight version of ICU FormatMessage (offset:1 and # are not integrated).


Plural Category

It's also possible to work with plural category. Multiple languages have multiple pluralization rules. You'll have to write a function returning the type to check. The functions are not included by default in the package (not needed in most cases). But you can get some of them from PLURAL.md file.

import frenchkiss from 'frenchkiss';

frenchkiss.set('en', {
  takemymoney:
    'Take {N} dollar{N, plural, one{} =5{s! Take it} other{s}} please.',
});
frenchkiss.set('fr', {
  takemymoney:
    "Prenez {N} dollar{N, plural, one{} =5{s! Prenez le} other{s}} s'il vous plait.",
});

// Set here your plural category function
frenchkiss.plural('en', (n) => {
  const i = Math.floor(Math.abs(n));
  const v = n.toString().replace(/^[^.]*\.?/, '').length;
  return i === 1 && v === 0 ? 'one' : 'other';
});

frenchkiss.plural('fr', (n) => {
  const i = Math.floor(Math.abs(n));
  return i === 0 || i === 1 ? 'one' : 'other';
});
// etc.

frenchkiss.locale('en'); // rules to locale = 'en'
frenchkiss.t('takemymoney', { N: 0 }); // => "Take 0 dollars please."
frenchkiss.t('takemymoney', { N: 1 }); // => "Take 1 dollar please."
frenchkiss.t('takemymoney', { N: 2 }); // => "Take 2 dollars please."
frenchkiss.t('takemymoney', { N: 5 }); // => "Take 5 dollars! Take it please."

frenchkiss.locale('fr'); // rules to locale = 'fr'
frenchkiss.t('takemymoney', { N: 0 }); // => "Prenez 0 dollar s'il vous plait."
frenchkiss.t('takemymoney', { N: 1 }); // => "Prenez 1 dollar s'il vous plait."
frenchkiss.t('takemymoney', { N: 2 }); // => "Prenez 2 dollars s'il vous plait."
frenchkiss.t('takemymoney', { N: 5 }); // => "Prenez 5 dollars! Prenez le s'il vous plait."

Nested expressions

For advanced usage, it's also possible to do nested select, plural and interpolations.

import frenchkiss from 'frenchkiss';

frenchkiss.set('fr', {
  timeago: `Updated: {minutes, plural,
    =0 {just now}
    =1 {one minute ago}
    other {
      {minutes} minutes ago by {gender, select,
        male {male}
        female {female}
        other {other}
      }
    }
  }`,
});

frenchkiss.t('timeago', { minutes: 0, gender: 'male' }); // => 'Updated: just now'
frenchkiss.t('timeago', { minutes: 1, gender: 'male' }); // => 'Updated: one minute ago'
frenchkiss.t('timeago', { minutes: 5, gender: 'male' }); // => 'Updated: 5 minutes ago by male'

โš™๏ธ Running the benchmarks

$ cd benchmark
$ yarn
$ yarn start
$ open ./result.html

i18n benchmark

โณ How to test

$ npm test

๐Ÿค How to contribute

  • Fork the project

  • Create a branch from main/master like that

    $ contribution/fix/your-github-identity
    

    OR

    $ contribution/improvment/your-github-identity
    
  • Push several (if needed) clear commits

  • Add tests following the way of the other ones have been wrote

  • Make sure that all test runs

  • Push your code

๐Ÿ“ฆ List of our other package

โ›ต Join us

May you want to share more than a pull request check our jobs opportunity

๐Ÿ”— Related projects

  • i18next-scanner: Scan your code, extract translation keys/values, and merge them into i18n resource files.
  • i18n-extract: Manage localization with static analysis. (report unused/missing/duplicated key, extract them).

License

Copyright (c) 2023 Koala-Interactive

This project is MIT licensed.

frenchkiss.js's People

Contributors

dependabot[bot] avatar eduardoltorres avatar floriansimon1 avatar ftonato avatar lucbarbier-tech avatar muzishell avatar stephanhoyer avatar vthibault avatar zoontek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

frenchkiss.js's Issues

[Suggestion] Add support for nested objects

For organization purposes it is nice to have a definition file with nested objects. Just a random example:

frenchkiss.set('en', {
  fruits: {
    apple: 'An apple',
    banana: 'A banana'
  },
  vegetables: {
    carrot: 'A carrot',
    daikon: 'A daikon'
  }
})

I would then use some lodash syntax and access them by t('fruits.banana') => 'A banana'.

Would this be out of scope for frenchkiss or something that could be added? Right now I'm converting our definition file to follow this dot syntax but to have it supported by frenchkiss would be really nice.

Can the key be considered as a fallback to a language

My application is a .Net core app which supports multi-language. At the server the way we call to get a translation (based on the client's culture) is something like below.

LanguageStore["Hello there"]

The point here is that fall back value is the key and it's the English locale (So we don't have a separate english resource file).

I know in your library, we can have a fall back store, but is it possible to do something like above? So the fall back is basically the key itself which is English.

[Suggestion] Change the order of parameters

Hello,

I think would be interesting to change the order of parameters in the set function, in this way we can assume that the language parameter will receive the default _locate, avoiding having to repeat code.

See my suggested code below:
set-function

Let me know, what do you think about it.

Code generation from strings disallowed for this context

EvalError: Code generation from strings disallowed for this context
    at Function (<anonymous>)
    at o (/Users/v0/Projects/remix-i18n/documents/node_modules/frenchkiss/dist/esm/frenchkiss.js:6:706)
    at g (/Users/v0/Projects/remix-i18n/documents/node_modules/frenchkiss/dist/esm/frenchkiss.js:6:1389)
    at Object.b (/Users/v0/Projects/remix-i18n/documents/node_modules/frenchkiss/dist/esm/frenchkiss.js:6:1439)
    at Index (/Users/v0/Projects/remix-i18n/documents/app/routes/$.tsx:63:18)
    at processChild (/Users/v0/Projects/remix-i18n/documents/node_modules/react-dom/cjs/react-dom-server.browser.development.js:3352:14)
    at resolve (/Users/v0/Projects/remix-i18n/documents/node_modules/react-dom/cjs/react-dom-server.browser.development.js:3269:5)
    at ReactDOMServerRenderer2.render (/Users/v0/Projects/remix-i18n/documents/node_modules/react-dom/cjs/react-dom-server.browser.development.js:3752:22)
    at ReactDOMServerRenderer2.read (/Users/v0/Projects/remix-i18n/documents/node_modules/react-dom/cjs/react-dom-server.browser.development.js:3689:29)
    at renderToString2 (/Users/v0/Projects/remix-i18n/documents/node_modules/react-dom/cjs/react-dom-server.browser.development.js:4297:27)

react ssr

CSP compatibly

I get with frenchkiss a unsafe-eval error, because of a new function.

With the current implementation frenchkiss is not CSP compatible.

Extraction Tool

Nice library! Just wondering whether there's a way to extract the texts to external file such as JSON for further translation?

Ordinal suffix configuration

Hi,

How would one go about configuring the ordinal suffix usage for a language like English, for example?

E.g.

  • 1 -> 1st
  • 2 -> 2nd
  • 3 -> 3rd
  • 4 - 4th
  • 11 - 11th
  • 21 - 21st
  • 22 - 22nd
  • etc

Thank you for the great library!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.