GENL / matice

Use your Laravel translations in JavaScript.
MIT License
100 stars 16 forks source link

Use your Laravel translations in JavaScript

Latest Version on Packagist Latest Version on NPM Tests Total Downloads on packagist Downloads on NPM

Logo

https://github.com/genl/matice/actions/workflows/.github/workflows/tests.yml/badge.svg?event=push

Matice creates a Blade directive that you can include in your views. It will export a JavaScript object of your Laravel application's translations, keyed by their names (aliases, lang, filenames, folders name), as well as globals trans(), __() and transChoice() helper functions which you can use to access your translations in your JavaScript.

Installation

You can install the package via composer:

composer require genl/matice
  1. Include our Blade directive (@translations) somewhere in your template before your main application JavaScript is loaded—likely in the header somewhere.
  2. Publish the vendor if you want to customize config:
    php artisan vendor:publish --provider="Genl\Matice\MaticeServiceProvider"

Matice is available as an NPM matice package which exposes the trans() function for use in frontend applications that do not use Blade. You can install the NPM package with:

// With yarn
yarn add matice

With npm
npm install matice

or load it from a CDN:

<!-- Load the Matice translation object first -->
<script src="https://unpkg.com/matice@1.1.x/dist/matice.min.js" defer></script>

TypeScript support

Matice is fully written in TypeScript so, it's compatible with TypeScript projects.

Usage

Matice comes with almost the same localization concepts as Laravel. Read more about Laravel localization

This package uses the @translations directive to inject a JavaScript object containing all of your application's translations, keyed by their names. This collection is available globally on the client side in the window.Matice object. The @translations directive accepts a list of locales to be loaded under th form of an array or a comma seperated string. If no locales are given, all the translations will be loaded.

Examples

import the trans() function like follow:

@translations(['en', 'fr'])

or

@translations('en, fr')

The package also creates an optional trans() JavaScript helper which works like Laravel's PHP trans() helper to retrieve translation strings.

Examples

import the trans() function like follow:

import { trans } from "matice";

Let's assume we have this translations file:

// resources/lang/en/custom.php

return [
    'greet' => [
        'me' => 'Hello!',
        'someone' => 'Hello :name!',
        'me_more' => 'Hello Ekcel Henrich!',
        'people' =>'Hello Ekcel!|Hello everyone!',
    ],
    'balance' => "{0} You're broke|[1000, 5000] a middle man|[1000000,*] You are awesome :name, :count Million Dollars"
];
// resources/lang/fr/custom.php

return [
    'greet' => [
        'me' => 'Bonjour!'
    ]
];

trans

Retrieve a text:

let sentence = ''

sentence = trans('greet.me') // Hello!

// With parameters
sentence = trans('greet.someone', {args: {name: "Ekcel"}}) // Hello Ekcel!

Update locale

Matice exposes setLocale function to change the locale that is used by the trans function.

import { setLocale } from "matice"

// update the locale
setLocale('fr')
const sentence = trans('greet.me') // Bonjour!

Pluralization

On pluralization, the choice depends on the count argument. To activate pluralization pass the argument pluralize to true.

// Simple pluralization
sentence = trans('greet.people', {args: {count: 0}, pluralize: true}) // Hello Ekcel!
sentence = trans('greet.people', {args: {count: 2}, pluralize: true}) // Hello everyone!

// Advanced pluralization with range. Works the same way.
// [balance => '{0} You're broke|[1000, 5000] a middle man|[1000000,*] You are awesome :name, :count Million Dollars']
sentence = trans('balance', {args: {count: 0}, pluralize: true}) // You're broke
sentence = trans('balance', {args: {count: 3000}, pluralize: true}) // a middle man

Trans Choice

Matice provides a helper function for pluralization

import { transChoice } from "matice"

let sentence = transChoice('balance', 17433085, {name: 'Ekcel'}) // You are awesome Ekcel, 17433085 Million Dollars

Underscore function

transChoice() always throws an error if the key is not found. To change this behaviour, use __(key, {pluralize: true})

sentence = trans('greet.unknown') // -> throws an error with a message.
sentence = __('greet.unknown') // greet.unknown

Default values

Matice uses your current app locale app()->getLocale() as the default locale when generating the translation object with the blade directive @translations. Same applies when generating with command line.

When Matice does not find a key, it falls back to the default locale and search again. The fallback is the same you define in your config.app.fallback_locale.

// config/app.php

'locale' => 'fr',
'fallback_locale' => 'en',

Retrieve the current locale

Matice exposes the MaticeLocalizationConfig class :

import { MaticeLocalizationConfig } from "matice"

const locale = MaticeLocalizationConfig.locale // 'en'

const fallbackLocale = MaticeLocalizationConfig.fallbackLocale // 'en'

const locales = MaticeLocalizationConfig.locales // ['en', 'fr']

Matice also provides helpers to deal with locales information:

import { setLocale, getLocale, locales } from "matice"

// Update the locale
setLocale('fr') //

const locale = getLocale() // 'fr'

const locales = locales() // ['en', 'fr']

Force locale

With the version 1.1.4, it is possible to force the locale for a specific translation WITHOUT changing the global local.

setLocale('en') // Set the current locale to English.

trans('greet.me') // "Hello!"
trans('greet.me', { locale: 'fr' }) // "Bonjour!"
trans('greet.me', { args: {}, locale: 'fr' }) // "Bonjour!"

__('greet.me', { locale: 'fr' }) // "Bonjour!"

transChoice('greet.me', 1, undefined, 'fr') // "Bonjour!"
transChoice('greet.me', 1, {}, 'fr') // "Bonjour!"

Filtering translations

Matice supports filtering the translations it makes available to your JavaScript, which is great to control the size of your data your translations become too large with thousands of lines.

Filtering namespaces

To set up namespace translations filtering, update in your config file either an only or except setting as an array of translations folders or files. Note: Setting the same namespace both 'only' and 'except' will result to 'except'. But in the other cases, 'only' takes precedence over 'except'

    // config/matice.php

    return [
        // Export only 
        'only' => [
            'fr/', // Take all the 'fr' directory with his children
            'es', // Take all the 'es' directory with his children
            'en/auth', // With or without the file extension
            'en/pagination.php',
            'en/validations',
        ],

        // Or... export everything except
        'except' => [
            'en/passwords',
            'en\\validations',
        ],
    ]; 

The base directory is the lang_directory defined in the config file: config('matice.lang_directory').

Use with SPA

Matice registers an Artisan console command to generate a matice_translations.js translations file, which can be used (or not) as part of an asset pipeline such as Laravel Mix.

You can run php artisan matice:generate --no-export in your project to generate a static translations file without the export statement in resources/assets/js/matice_translations.js. You can customize the generation path in the config/matice.php file.

php artisan matice:generate --no-export

An example of matice_translations.js, where auth translations exist in resources/lang/en/auth.php:

// resources/lang/en/auth.php

return [
    'failed' => 'These credentials do not match our records.',
    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];
// matice_translations.js

const Matice = {
    locale: 'en',
    fallbackLocale: 'en',
    translations: {
      en: {
        auth: {
          failed: 'These credentials do not match our records.',
          throttle: 'Too many login attempts. Please try again in :seconds seconds.'
        }
      }
    }
};

At this point you can use in javascript this translations file like usual, paste in your html as well.

This is useful if your laravel and js app are separated like with SPA or PWA. So you can link the generated translations file with your JS App. If you're not in the case of SPA, WPA... you might never have to generate the translations manually because @translations directive already does it for you when the app environment is 'production' to improve performance.

<!-- Manually include the generated translations in your HTML file. -->

<html>
<head>
    <title></title>

    <!-- The matice package -->
    <script src="https://unpkg.com/matice@1.1.x/dist/matice.min.js" defer></script>

    <!-- "link to the generated translations file" -->
    <script src="https://your-awesomeapp-server.co/matice_translations.js"></script>
</head>

<body>
    😃
</body>
</html>

Whenever your translation messages change, run php artisan matice:generate again. Remember to disable browser cache, it may have cached the old translations file.

Using with Vue Components

Basically, Matice can be integrated to any Javascript projects. Event with some big framework like Vue.js React.js or Angular. Some frameworks like Vue re-renders the UI dynamically. In this section we show you how to bind Matice with Vue 2. Based on this example we assume you can take inspiration to do the same with the framework you use for your project. For example, with React, you should re-render the whole app after setLocale() is called for the changes to be visible.

Add this to your app.js file:

// app.js

import {__, trans, setLocale, getLocale, transChoice, MaticeLocalizationConfig, locales} from "matice"

Vue.mixin({
    methods: {
        $trans: trans,
        $__: __,
        $transChoice: transChoice,
        $setLocale(locale: string) {
            setLocale(locale);
            this.$forceUpdate() // Refresh the vue instance(The whole app in case of SPA) after the locale changes.
        },
        // The current locale
        $locale() {
            return getLocale()
        },
        // A listing of the available locales
        $locales() {
            return locales()
        }
    },
})

Then you can use the methods in your Vue components like so:

<p>{{ $trans('greet.me') }}</p>

Dive Deeper

Matice extends the Laravel Translator class. Use Translator::list() to return an array representation of all of your app translations.

If you want to load only translations of a specific locale, use the matice facade:

use GENL\Matice\Facades\Matice;

// Loads all the translations
$translations = Matice::translations();

// Or loads translations for a specific locale.
$translations = Matice::translations($locale);

Environment-based loading of minified matice helper file

When using the @translations Blade directive, Matice detects the current environment and minify the output if APP_ENV is production.

In development, @translations loops into the lang directory to generate the matice object each time the page reloads, and doesn't generatematice_translations.js file. In production, @translations generate the matice_translations.js file for you when your app is open for the first time then the generated file is used every time the page reloads. The Matice object is not generated every time, so it has no effect on performances in production. This behavior can be disabled in the config/matice.php file, set use_generated_translations_file_in_prod to false.

Note: Matice supports json translation files as well as php files.,

Testing

// --  laravel test --
composer test

// -- js test --

// With yarn
yarn test

// With npm
npm run test

Changelog

Please see CHANGELOG for more information what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email bigcodole@gmail.com instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Laravel Package Boilerplate

This package was generated using the Laravel Package Boilerplate.