Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: xiCO2k/laravel-vue-i18n
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.4.2
Choose a base ref
...
head repository: xiCO2k/laravel-vue-i18n
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Loading
Showing with 11,130 additions and 19,029 deletions.
  1. +1 −1 .github/workflows/tests.yaml
  2. +1 −0 .gitignore
  3. +1 −0 .npmignore
  4. +192 −26 README.md
  5. +1 −1 babel.config.js
  6. +2 −1 jest.config.js
  7. +0 −1 mix.js
  8. +9,547 −18,744 package-lock.json
  9. +50 −15 package.json
  10. +420 −117 src/index.ts
  11. +3 −0 src/interfaces/options.ts
  12. +7 −0 src/interfaces/parsed-lang-file.ts
  13. +13 −0 src/interfaces/plugin-options.ts
  14. +149 −27 src/loader.ts
  15. +15 −6 src/mix.ts
  16. +2 −1 src/pluralization.ts
  17. +0 −35 src/utils/get-plural-index.ts
  18. +16 −0 src/utils/has-php-translations.ts
  19. +76 −0 src/vite.ts
  20. +104 −0 test/class.test.ts
  21. +4 −0 test/fixtures/lang/de.json
  22. +4 −1 test/fixtures/lang/en.json
  23. +7 −1 test/fixtures/lang/en/auth.php
  24. +11 −0 test/fixtures/lang/en/classnames.php
  25. +12 −0 test/fixtures/lang/en/domain/car.php
  26. +7 −0 test/fixtures/lang/en/domain/user.php
  27. +16 −0 test/fixtures/lang/en/enums.php
  28. +6 −0 test/fixtures/lang/en/ignore.php
  29. +10 −0 test/fixtures/lang/en/nested/cars/car.php
  30. +7 −1 test/fixtures/lang/pt.json
  31. +10 −0 test/fixtures/lang/pt/nested/cars/car.php
  32. +17 −0 test/fixtures/lang/vendor/package-example/en/messages.php
  33. +17 −0 test/fixtures/lang/vendor/package-example/pt/messages.php
  34. +7 −0 test/fixtures/lang/wrongfolder/random.php
  35. +6 −0 test/fixtures/locales/en/auth.php
  36. +5 −0 test/fixtures/locales/en/domain/user.php
  37. +0 −1 test/fixtures/mix-manifest.json
  38. +0 −10 test/fixtures/webpack.mix.js
  39. +27 −0 test/folderIsolationUtil.ts
  40. +127 −13 test/loader.test.ts
  41. +71 −3 test/plurization.test.ts
  42. +8 −4 test/setup.ts
  43. +130 −4 test/translate.test.ts
  44. +14 −0 tsc-multi.json
  45. +7 −16 tsconfig.json
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2

- name: Cache npm dependencies
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: node_modules
key: npm-${{ hashFiles('package-lock.json') }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
/node_modules
/coverage
/dist
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
coverage
dist
test
218 changes: 192 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -3,55 +3,141 @@
</h1>

<p align="center">
<a href="https://github.com/xiCO2k/laravel-vue-i18n/actions"><img alt="GitHub Workflow Status (master)" src="https://img.shields.io/github/workflow/status/xiCO2k/laravel-vue-i18n/Tests/main"></a>
<a href="https://github.com/xiCO2k/laravel-vue-i18n/actions"><img alt="GitHub Workflow Status (master)" src="https://github.com/xiCO2k/laravel-vue-i18n/actions/workflows/tests.yaml/badge.svg"></a>
<a href="https://www.npmjs.com/package/laravel-vue-i18n"><img alt="License" src="https://img.shields.io/npm/l/laravel-vue-i18n.svg?sanitize=true"></a>
<a href="https://www.npmjs.com/package/laravel-vue-i18n"><img alt="Version" src="https://img.shields.io/npm/v/laravel-vue-i18n.svg"></a>
<a href="https://www.npmjs.com/package/laravel-vue-i18n"><img alt="Total Downloads" src="https://img.shields.io/npm/dt/laravel-vue-i18n.svg"></a>
</p>

<p align="center">
<b>laravel-vue-i18n</b> is a <b>Vue3</b> plugin that allows to connect your <b>Laravel</b> Framework <b>JSON</b> translation
<b>laravel-vue-i18n</b> is a <b>Vue3</b> plugin that allows to connect your <b>Laravel</b> Framework translation
files with <b>Vue</b>. It uses the same logic used on <a href="https://laravel.com/docs/8.x/localization">Laravel Localization</a>.
</p>

## Installation

With [npm](https://www.npmjs.com):
```sh
$ npm i laravel-vue-i18n
npm i laravel-vue-i18n
```

or with [yarn](https://yarnpkg.com):
```sh
$ yarn add laravel-vue-i18n
yarn add laravel-vue-i18n
```

## Setup

> If you want to see a screencast on how to setup check out this video: [How to use Laravel Vue i18n plugin](https://www.youtube.com/watch?v=ONRo8-i5Qsk).
### With Vite

```js
import { createApp } from 'vue'
import { i18nVue } from 'laravel-vue-i18n'

createApp()
.use(i18nVue, {
resolve: lang => import(`../../lang/${lang}.json`),
resolve: async lang => {
const langs = import.meta.glob('../../lang/*.json');
return await langs[`../../lang/${lang}.json`]();
}
})
.mount('#app');
```

### With `vite`
#### SSR (Server Side Rendering)

The `resolve` method will need to be:
For Server Side Rendering the resolve method should not receive a `Promise` and instead take advantage of the `eager` param like this:

```js
resolve: async lang => {
const langs = import.meta.glob('../../lang/*.json');
return await langs[`../../lang/${lang}.json`]();
}
````
.use(i18nVue, {
lang: 'pt',
resolve: lang => {
const langs = import.meta.glob('../../lang/*.json', { eager: true });
return langs[`../../lang/${lang}.json`].default;
},
})
```

### With SSR
#### PHP Translations Available on Vue

The `resolve` method can receive a `require` instead of a `Promise`:
In order to load `php` translations, you can use this `Vite` plugin.

```js
// vite.config.js
import i18n from 'laravel-vue-i18n/vite';

export default defineConfig({
plugins: [
laravel([
'resources/css/app.css'
'resources/js/app.js',
]),
vue(),

// Laravel >= 9
i18n(),

// Laravel < 9, since the lang folder is inside the resources folder
// you will need to pass as parameter:
// i18n('resources/lang'),
],
});
```

#### Vite plugin options

In addition to that, you can use this `Vite` plugin with additional paths to load from, this is usefull when you are using a package that let's you override your translations, or in case you are getting your application's lang files from different paths.

Note that if one key found in two paths, priority will be given to the last given path between these two (In this example translation key will be loaded from `public/locales`)

```js
// vite.config.js
import i18n from 'laravel-vue-i18n/vite';

export default defineConfig({
plugins: [
laravel([
'resources/css/app.css'
'resources/js/app.js',
]),
vue(),

i18n({
// you can also change your langPath here
// langPath: 'locales'
additionalLangPaths: [
'public/locales' // Load translations from this path too!
]
}),
],
});
```

> During the `npm run dev` execution time, the plugin will create some files like this `php_{lang}.json` on your lang folder.
> And to avoid that to be commited to your code base, I suggest to your `.gitignore` this like:
```bash
lang/php_*.json
```

### With Webpack / Laravel Mix

```js
import { createApp } from 'vue'
import { i18nVue } from 'laravel-vue-i18n'

createApp()
.use(i18nVue, {
resolve: lang => import(`../../lang/${lang}.json`),
})
.mount('#app');
```

#### SSR (Server Side Rendering)

For Server Side Rendering the resolve method should receive a `require` instead of a `Promise`:

```js
.use(i18nVue, {
@@ -60,7 +146,7 @@ The `resolve` method can receive a `require` instead of a `Promise`:
})
````

### Laravel Mix Plugin
#### PHP Translations Available on Vue

In order to load `php` translations, you can use this `Mix` plugin.

@@ -72,25 +158,30 @@ require('laravel-vue-i18n/mix');
mix.i18n();
// Laravel < 9, since the lang folder is inside the resources folder
// you will need to pass as parameter.
mix.i18n('resouces/lang');
// you will need to pass as parameter:
// mix.i18n('resources/lang');
```

### Usage

```html
<template>
<div>
<h1>{{ $t('Welcome :name!', { name: 'Francisco' }) }}. </h1>
<h1>{{ $t('Welcome, :name!', { name: 'Francisco' }) }}. </h1>
<div>Logged in {{ $tChoice('{1} :count minute ago|[2,*] :count minutes ago', 10) }}</div>
</div>
</template>
```

### Plugin Options

- `lang` *(optional)*: if not provided it will try to find from the `<html lang="pt">` tag, if is not available it will default to `en`.
- `lang` *(optional)*: If not provided it will try to find from the `<html lang="pt">` tag.
- `fallbackLang` *(optional)*: If the `lang` was not provided or is invalid, it will try reach for this `fallbackLang` instead, default is: `en`.
- `fallbackMissingTranslations` *(optional)*: If the `lang` is provided, but the translation key does not exist in that language it will fallback to the `fallbackLang` instead.
- `resolve` *(required)*: The way to reach your language files.
- `shared` *(optional)*: Whether to [share the same `I18n` instance](#using-multiple-instances) between different Vue apps, default is: `true`.
- `onLoad` *(optional)*: It's called everytime a language is loaded.
```js
createApp().use(i18nVue, {
@@ -107,7 +198,7 @@ The `trans()` method can translate a given message.
// lang/pt.json
{
"Welcome!": "Bem-vindo!",
"Welcome, :name!": "Bem-vindo, :name!",
"Welcome, :name!": "Bem-vindo, :name!"
}
import { trans } from 'laravel-vue-i18n';
@@ -125,7 +216,7 @@ use it instead of `trans()` to watch any changes (language changes or lang files
// lang/pt.json
{
"Welcome!": "Bem-vindo!",
"Welcome, :name!": "Bem-vindo, :name!",
"Welcome, :name!": "Bem-vindo, :name!"
}
import { wTrans } from 'laravel-vue-i18n';
@@ -134,7 +225,7 @@ setup() {
return {
welcomeLabel: wTrans('Welcome!'),
welcomeFrancisco: wTrans('Welcome, :name!', { name: 'Francisco' })
}
}
}
<template>
@@ -153,7 +244,7 @@ there is also available an `trans_choice` alias, and a mixin called `$tChoice()`
{
"There is one apple|There are many apples": "Existe uma maça|Existe muitas maças",
"{0} There are none|[1,19] There are some|[20,*] There are many": "Não tem|Tem algumas|Tem muitas",
"{1} :count minute ago|[2,*] :count minutes ago": "{1} há :count minuto|[2,*] há :count minutos",
"{1} :count minute ago|[2,*] :count minutes ago": "{1} há :count minuto|[2,*] há :count minutos"
}
import { transChoice } from 'laravel-vue-i18n';
@@ -175,7 +266,7 @@ use it instead of `transChoice()` to watch any changes (language changes or lang
{
"There is one apple|There are many apples": "Existe uma maça|Existe muitas maças",
"{0} There are none|[1,19] There are some|[20,*] There are many": "Não tem|Tem algumas|Tem muitas",
"{1} :count minute ago|[2,*] :count minutes ago": "{1} há :count minuto|[2,*] há :count minutos",
"{1} :count minute ago|[2,*] :count minutes ago": "{1} há :count minuto|[2,*] há :count minutos"
}
import { wTransChoice } from 'laravel-vue-i18n';
@@ -184,7 +275,7 @@ setup() {
return {
oneAppleLabel: wTransChoice('There is one apple|There are many apples', 1),
multipleApplesLabel: wTransChoice('{0} There are none|[1,19] There are some|[20,*] There are many', 19)
}
}
}
<template>
@@ -208,7 +299,7 @@ import { loadLanguageAsync } from 'laravel-vue-i18n';
### `getActiveLanguage()`
The `getActiveLanguage()` returns the language that is current beign used.
The `getActiveLanguage()` returns the language that is currently being used.
```jsx
@@ -228,3 +319,78 @@ import { isLoaded } from 'laravel-vue-i18n';
const loaded = isLoaded(); // true
const loaded = isLoaded('fr'); // false
```
## Using multiple instances
Under the hood, the Vue plugin is using a `I18n` class which encapsulates all the translation logic and the currently active language. This means that it's possible to create multiple class instances, each with different options and active language. This can be useful for scenarios where part of the app needs to be translated to a language different from the main UI.

Note that loaded languages are still shared between different instances. This avoids loading the same set of translations multiple times. The main difference between different instances will be the currently active language.

```js
import { I18n } from 'laravel-vue-i18n'
const resolver = lang => import(`./fixtures/lang/${lang}.json`)
const i18nEn = new I18n({
lang: 'en',
resolve: resolver
})
const i18nPt = new I18n({
lang: 'pt',
resolve: resolver
})
i18nEn.trans('Welcome!') // will output "Welcome!"
i18nPt.trans('Welcome!') // will output "Bem-vindo!"
```

By default, installing the the `i18nVue` plugin will create a shared instance. This instance is accessed when importing the translation functions, such as `trans`, directly. When using multiple Vue app instances, it's possible to either share the `I18n` instance between them, or have each app create its own instance.
**Shared usage (default)** - all Vue app instances will use the same `I18n` class and currently active language:
```js
import { i18nVue } from 'laravel-vue-i18n'
const appA = createApp()
.use(i18nVue, { lang: 'pt' })
.mount('#app-1');
const appB = createApp()
.use(i18nVue)
.mount('#app-2');
// elsewhere
import { trans } from 'laravel-vue-i18n'
trans('Welcome!') // will output "Bem-vindo!"
```
**Non-shared usage** - each Vue app will have its own `I18n` instance & currently active language.
```js
import { i18nVue } from 'laravel-vue-i18n'
const appA = createApp()
.use(i18nVue, {
lang: 'es'
shared: false, // don't use the shared instance
})
.mount('#app-1');

const appB = createApp()
.use(i18nVue, {
lang: 'pt'
shared: false, // don't use the shared instance
})
.mount('#app-2');
```

### Accessing the shared instance
It's possible to access the shared instance via code as well:

```js
import { I18n } from 'laravel-vue-i18n'

I18n.getSharedInstance()
```
### Caveats

It is possible to import a translation function before installing the `i18nVue` plugin. When calling the translation function, ie `trans()`, and the plugin has not been installed, a shared `I18n` instance will be created with default options. This ensures that it's possible to import and call these functions without any fatal errors. However, this may yield undesired results. Therefore, it is advisable to never call any translation methods before the plugin is installed.
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
plugins: ['@vue/babel-plugin-jsx'],
plugins: ['babel-plugin-transform-vite-meta-env', '@vue/babel-plugin-jsx'],
presets: [
['@babel/preset-env',
{
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,8 @@ module.exports = {
preset: 'ts-jest',
globals: {
'ts-jest': {
babelConfig: true
tsconfig: './tsconfig.json',
babelConfig: './babel.config.js',
}
},
testEnvironment: 'jsdom',
1 change: 0 additions & 1 deletion mix.js

This file was deleted.

Loading