inertiajs / pingcrm

A demo application to illustrate how Inertia.js works.
http://demo.inertiajs.com
MIT License
2.11k stars 772 forks source link

Add demo for multi-language support #30

Closed gzhihao closed 5 years ago

gzhihao commented 5 years ago

Maybe out of this demo app's scope, but would like to see how multi-language is supported in an inertia app. It might be similar to the typical SPA way, but I see some differences, e.g. we can leverage the lang switching feature on server side, which could make frontend dumb simple. (That's the beauty of inertia).

Feel free to close this issue if it's not align with the goal of this repo.

Juhlinus commented 5 years ago

This is definitely something that I would agree should be a part of PingCRM as a showcase. Making use of the lang files in the resources directory. I am not sure if this is already relatively simple to implement, but I'll have a look when I have some free time.

reinink commented 5 years ago

Hi folks, sorry, but I don't see adding this to Ping CRM. I want to keep this project as simple as possible to help folks get up and running with Inertia.js. The idea isn't to show how to solve every possible problem with the library.

Thanks for your interest either way! 😍

Juhlinus commented 5 years ago

@gzhihao If you're still looking for a solution I suggest having a look at this npm package: https://github.com/martinlindhe/laravel-vue-i18n-generator

gzhihao commented 5 years ago

Thanks for both.

Yeah, I got my setup working, for those who are interested, I'm using following two packages

devonmather commented 4 years ago

I ended up localizing a Laravel, Vue.js app that was using Inertia.js without adding any required extra dependencies to the front or backend. I wrote about it in a pretty straight forward step by step format in case it might help anyone (myself) in the future 🤙🏻 https://devonmather.xyz/localizing-a-laravel-app-using-vue-js-and-inertia-js-without-any-dependencies/

lucasdcrk commented 4 years ago

For anyone reading later

I was also seeking for a way to use localization with Inertia, simply because it seemed logic to me.

But remember that you use a fully fledged javascript framework for your frontend! That means that most of the time you can use the localization system of your frontend framework (for vuejs it's Vue-i18n) instead of Laravel's one (you can still use Laravel's localization system for your controller logic, emails and blade views if your app is not 100% inertia based).

I think this is the cleanest and easiest way to go.

Elzakzouk commented 3 years ago

I ended up localizing a Laravel, Vue.js app that was using Inertia.js without adding any required extra dependencies to the front or backend. I wrote about it in a pretty straight forward step by step format in case it might help anyone (myself) in the future 🤙🏻 https://devonmather.xyz/localizing-a-laravel-app-using-vue-js-and-inertia-js-without-any-dependencies/

could you provide us with the Github repo please

flakerimi commented 2 years ago

I ended up localizing a Laravel, Vue.js app that was using Inertia.js without adding any required extra dependencies to the front or backend. I wrote about it in a pretty straight forward step by step format in case it might help anyone (myself) in the future 🤙🏻 https://devonmather.xyz/localizing-a-laravel-app-using-vue-js-and-inertia-js-without-any-dependencies/

Can you do a review for vue3. Its not loading base.js mixin

djpmedia commented 2 years ago

I ended up localizing a Laravel, Vue.js app that was using Inertia.js without adding any required extra dependencies to the front or backend. I wrote about it in a pretty straight forward step by step format in case it might help anyone (myself) in the future 🤙🏻 https://devonmather.xyz/localizing-a-laravel-app-using-vue-js-and-inertia-js-without-any-dependencies/

Can you do a review for vue3. Its not loading base.js mixin

You need to modify the base.js a bit:

module.exports = {
    methods: {
        __(key, replace = {}) {
            var translation = this.$page.props.language[key]
                ? this.$page.props.language[key]
                : key
            Object.keys(replace).forEach(function (key) {
                translation = translation.replace(':' + key, replace[key])
            });
            return translation
        },
    },
}

And app.js lookslike:

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => require(`./Pages/${name}.vue`),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .mixin({ methods: { route } })
            .mixin(require('./base'))
            .mount(el);
    },
});

This worked for me with Vue3

KickSchutte67 commented 2 years ago

Due to the Laravel 9 update, the AppServiceProvider.php has to be edited because the change of the "lang" folder.

Inertia::share([
    'locale' => function () {
        return app()->getLocale();
    },
    'language' => function () {
        return translations(
            app_path('../lang/'. app()->getLocale() .'.json')
        );
    }
]);

This works as from 03-03-2022.

ivand88 commented 2 years ago

Thanks for helpful topic @devonmather and everyone else!

I might be wrong and missed the info in original article that explained this process, so just wanted to share. If you're ever in need to pass parameters, same way you use it regularly in Larval app, you can always do something like:

image

Then in template:

image

And finally in the end ( en.json ):

image

maelga commented 2 years ago

Vuejs has its own localization system (Vue-i18n) that's easy to use, so you don't need to reinvent the wheel. I came across this excellent package that bridges Laravel language files and Vue for that purpose: https://github.com/xiCO2k/laravel-vue-i18n

serdarcevher commented 2 years ago

@djpmedia's modified solution from @devonmather works in the template part. However, I couldn't find an easy way to use it in the script block.

I tried to use the provide/inject method but it didn't work since the "$this.page" part in the module doesn't work with the composition API.

Anyone has a practical solution to deal with this?

Edit: I ended up by creating a Composable and copying the function there, then replacing the $this.page part with usePage(). But still looking for an ideal solution without duplicating the translation function.

bytescrum commented 2 years ago
'language' => function () {
        return translations(
            resource_path('lang/'. app()->getLocale() .'.json')
        );
    },
function translations($json)
{
    if(!file_exists($json)) {
    return [];
    }
    return json_decode(file_get_contents($json), true);
}

i vijay kanaujia update this things to get data from multiple files

'language' => translations(),

function translations()
{
    $path = resource_path('lang/'. app()->getLocale());
    $files = array_diff(scandir($path), array('.', '..'));
    $languages = [];
    if(count($files)){
        foreach($files as $file){
            $ext = pathinfo($file);
            $languages[$ext['filename']] = Lang::get($ext['filename']);
        }
    }
    return $languages;
}
hcivelek commented 2 years ago

I ended up localizing a Laravel, Vue.js app that was using Inertia.js without adding any required extra dependencies to the front or backend. I wrote about it in a pretty straight forward step by step format in case it might help anyone (myself) in the future 🤙🏻 https://devonmather.xyz/localizing-a-laravel-app-using-vue-js-and-inertia-js-without-any-dependencies/

Can you do a review for vue3. Its not loading base.js mixin

You need to modify the base.js a bit:

module.exports = {
    methods: {
        __(key, replace = {}) {
            var translation = this.$page.props.language[key]
                ? this.$page.props.language[key]
                : key
            Object.keys(replace).forEach(function (key) {
                translation = translation.replace(':' + key, replace[key])
            });
            return translation
        },
    },
}

And app.js lookslike:

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => require(`./Pages/${name}.vue`),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .mixin({ methods: { route } })
            .mixin(require('./base'))
            .mount(el);
    },
});

This worked for me with Vue3

When I used this, I got Uncaught ReferenceError: require is not defined. Why did it happen?

robertspektor commented 2 years ago

I have the same. The problem is, that Laravel is switched to Vite. Vite hasn't a require method. I tried

.mixin(import('./base'))

But I get following error:

app.cc8c8888.js:31 TypeError: m.__ is not a function

KickSchutte67 commented 2 years ago

I have the same. The problem is, that Laravel is switched to Vite. Vite hasn't a require method. I tried

.mixin(import('./base'))

But I get following error:

app.cc8c8888.js:31 TypeError: m.__ is not a function

Did you fix this issue when switching to Vite? And if so how'd you fix this issue?

alex0107 commented 1 year ago

I have the same. The problem is, that Laravel is switched to Vite. Vite hasn't a require method. I tried .mixin(import('./base')) But I get following error: app.cc8c8888.js:31 TypeError: m.__ is not a function

Did you fix this issue when switching to Vite? And if so how'd you fix this issue?

@KickSchutte67

I fixed it with

base.js:

export default {
    methods: {
        __(key, replace = {}) {
            var translation = this.$page.props.language[key]
                ? this.$page.props.language[key]
                : key
            Object.keys(replace).forEach(function (key) {
                translation = translation.replace(':' + key, replace[key])
            });
            return translation
        },
    },
}

and app.js:

import translation from './Helper/Translation';

 return createApp({render: () => h(app, props)})
            ....
            .mixin(translation)
            .....
SnoxiK commented 1 year ago

Hello,

I just put a new laravel 9 version with inertiajs and I had some small problems here is the solution.

in app.js

...
import base from './base';
...

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, app, props, plugin }) {
        return createApp({ render: () => h(app, props) })
            .use(plugin)
            .use(ZiggyVue, Ziggy)
            .mixin(base) //add this line.
            .mount(el);
    },
});

in base.js change module.exports = to export default

show code:

export default {
    methods: {
        /**
         * Translate the given key.
         */
        __(key, replace = {}) {
            var translation = this.$page.props.language[key]
                ? this.$page.props.language[key]
                : key

            Object.keys(replace).forEach(function (key) {
                translation = translation.replace(':' + key, replace[key])
            });

            return translation
        },

        /**
        * Translate the given key with basic pluralization.
        */
        __n(key, number, replace = {}) {
            var options = key.split('|');

            key = options[1];
            if(number == 1) {
                key = options[0];
            }

            return tt(key, replace);
        },
    },
}
GankCC commented 1 year ago

I have created an example for a multi-language website using Laravel, Vue, and InertiaJS. Take a look https://github.com/GankCC/multilang

curiousteam commented 1 year ago

In Laravel 10:

class HandleInertiaRequests extends Middleware
{
    // ...
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [

            // ...

            'language' => function () {
                $langFile = lang_path(app()->getLocale() . ".json");

                if (!File::exists($langFile)) return [];

                return File::json($langFile);
            },

            // ...
        ]);
    }
}

Don't forget to import use Illuminate\Support\Facades\File; facade at the top.

SayerAljohani commented 10 months ago

Dears, After reviewing various suggestions and solutions for localizing Laravel with Inertia React, I have distilled my goals My aim is to adopt a solution that:

  1. Ensure a solution that is easy to implement and doesn't consume excessive time during changes.
  2. Seek an approach that works seamlessly on both Laravel and React sides.
  3. Minimize redundancy in the implementation.
  4. Prioritize ease of deployment and updates.
  5. Opt for a clear and straightforward implementation without unnecessary complexity.
  6. Ensure the solution does not adversely impact the website's performance.

I attempted to share the language as JSON using 'inertia::share.' However, in my opinion, this is not a recommended practice because:

use Illuminate\Support\Facades\Inertia;

public function boot(): void
{
    // Check your condition here
    if (/* your condition */) {
        Inertia::share([
            'lang' => function () {
                // Your logic to fetch and return the data
                return $this->mergeLangFiles();
            },
        ]);
    }
}
  1. Effect performance during scaling your project.
  2. Need a custom solution in front-end that does not consider many things such as shipping with many localization frameworks
  3. difficult to manage multi-language

I have come across several solutions, and most of them involve custom server-side implementations.

My solution is :

  1. Create a new Larvel project

  2. Use starter-kits quick setup front-end react

  3. publish Defulat framework Lang files via the lang:publish Artisan command. The result should be a new folder named "Lang" and nested folder "EN" contains four .php files: image

  4. Create a simple Artisan command to merge these files into a single JSON file based on the language namespace [EN, FR, AR, ...] and save them in the Public folder under the same structure.

    private function mergeLanguageFiles($languagesPath, $outputPath)
    {
        $mergedTranslations = [];
        // Get all language files
        $files = File::allFiles($languagesPath);
    
        foreach ($files as $file) {
            $langArray = include $file;
            $locale = pathinfo($file, PATHINFO_FILENAME);
                $mergedTranslations[$locale] = $langArray;
            }
        }
    
        // Save the merged translations to separate JSON files
        File::put($outputPath . '/shared.json', json_encode($sharedTranslations, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
    
    }
  5. To ensure these files remain up-to-date, run the following command each time you boot the project. Apply this step only in development mode

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        if ($this->app->environment('local')) {
            // Run the command only in the local environment
            Artisan::call('make:lang-merge');
        }
    }

    Result :

image

  1. Install follwing pakeges in react : "dependencies": { "i18next": "^23.7.6", "i18next-http-backend": "^2.4.1", "react-i18next": "^13.5.0" }
    1. Create new file under "/JS/ as following : image

You can get this code from official documentation

import i18n from 'i18next';
import HttpApi from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';

i18n
  .use(initReactI18next)
  .use(HttpApi)
  .init({
    lng: 'ar', // en
    fallbackLng: 'ar', // en
    debug: true,
    interpolation: {
      escapeValue: false,
    },
    backend:{
        loadPath: '/lang/{{lng}}/{{ns}}.json',
    },
    load: 'languageOnly', // Load only the language part of the locale
    ns: ['messages'],
    defaultNS: 'shared', // Set the default namespace
  });

export default i18n;
  1. In app.tsx import the previous file : import './i18n'; // Import the i18n configuration.

  2. Now, it's ready for transitioning content, as shown in the screenshot below image

This solution is very simple, robust, and easy to scale. Additionally, it allows you to leverage the features of the 'i18next' library. In the backend, you can continue translating content as follows: __('auth.password')