Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multilingual #561

Merged
merged 79 commits into from
Mar 8, 2017
Merged

Add multilingual #561

merged 79 commits into from
Mar 8, 2017

Conversation

marktopper
Copy link
Contributor

@marktopper marktopper commented Jan 14, 2017

Progress:

  • Add translations migration
  • Add Translatable trait
  • Add method to get translated value
  • Add method to translate the whole model
  • Enable saving directly to the translator
  • Enable checking if a model is translatable
  • Create view to input translated values (Feature/multilangual #873)
  • Make method to get original model from translator
  • Make method to create translation
  • Make method to update translation
  • Make method to delete translation
  • Make method to get meta data for translation
  • ~Make method to get rows that have translations~~ (can come later as a PR)
  • ~Make method to get rows that does not have translations~~ (can come later as a PR)
  • Make tests
  • Turn this into a plugin for Voyager (Cancelled)

Setup:

In order to use this multilingual, you must set some extra languages in the configuration. It will not work where there are only the default language.

Then you must ensure that the models you wish to use the multilingual support for have the TCG\Voyager\Translatable trait.
Along with that you must define the translatable attributes in the model as so

class Page extends Model
{
    protected $translatable = ['title', 'body'];
}

Usage:

Eager load translations

// Loads all translations
$posts = Post::with('translations')->get();

// Loads all translations
$posts = Post::all();
$posts->load('translations');

// Loads all translations
$posts = Post::withTranslations()->get();

// Loads specific locales translations
$posts = Post::withTranslations(['en', 'da'])->get();

// Loads specific locale translations
$posts = Post::withTranslation('da')->get();

// Loads current locale translations
$posts = Post::withTranslation('da')->get();

Get default language value

echo $post->title;

Get translated value

echo $post->getTranslatedAttribute('title', 'locale', 'fallbackLocale');

If you do not define locale, the current application locale will be used. You can pass in your own locale as a string.
If you do not define fallbackLocale, the current application fallback locale will be used. You can pass in your own locale as a string. If you want to turn the fallback locale off, pass false.
If no values are found, null is returned.

Translate the whole model

$post = $post->translate('locale', 'fallbackLocale');
echo $post->title;
echo $post->body;

// You can also run the `translate` method on the Eloquent collection
// to translate all models in the collection.
$posts = $posts->translate('locale', 'fallbackLocale');
echo $posts[0]->title;

If you do not define locale, the current application locale will be used. You can pass in your own locale as a string.
If you do not define fallbackLocale, the current application fallback locale will be used. You can pass in your own locale as a string. If you want to turn the fallback locale off, pass false.
If no values are found for the model for a specific attribute, either for the locale or the fallback, it will set that attribute to null.

Check if model is translatable

// with string
if (Voyager::translatable(Post::class)) {
    // it's translatable
}

// with object of Model or Collection
if (Voyager::translatable($post)) {
    // it's translatable
}

Save changes

$post = $post->translate('da');
$post->title = 'foobar';
$post->save();

This will update or create the translation for title the post with the locale da.
Please note that if a modified attribute is not translatable, then it will make the changes directly to the model itself. Meaning that it will overwrite the attribute in the language set as default.

Feedback, questions and pull requests are more than welcome.

@marktopper
Copy link
Contributor Author

UPDATE: Added new ways to eager load translations.

@marktopper
Copy link
Contributor Author

UPDATE: You can now translate a Eloquent Collection like this.

$posts = Post::all()->translate('locale');
echo $posts[0]->title; // is now translated

You can also make modifications to the translated models and save them as translations like this.

$post = Post::first()->translate('locale');
$post->title = 'test';
$post->save();

Please note that if a modified attribute is not translatable, then it will make the changes directly to the model itself. Meaning that it will overwrite the attribute in the language set as default.

@marktopper
Copy link
Contributor Author

UPDATE: You can now use the Voyager facade to check if a object or classname is translatable.

// with string
if (Voyager::translatable(Post::class)) {
    // it's translatable
}

// with object of Model or Collection
if (Voyager::translatable($post)) {
    // it's translatable
}

@marktopper marktopper changed the title [WIP] Add multilangual [WIP] Add multilingual Jan 14, 2017
@akazorg
Copy link
Contributor

akazorg commented Jun 2, 2017

Hi @Steve-sy, have you setup the config voyager.php and followed the instructions from docs?
If you can't sort that out, please open a new issue and we will help you.
Thanks.

@coppee
Copy link

coppee commented Jun 13, 2017

Hello there,
Just to be clear, the final translation system choice is to use a specific table, no the json solution ?
And btw, thanks for all your great work !

@marktopper
Copy link
Contributor Author

@coppee We use the database for the BREAD translations but will use PHP files for the views-translations.

@coppee
Copy link

coppee commented Jun 13, 2017

Hi @marktopper , thanks for your reply. Sorry, I was not clear enough in my question. I was talking about the other translation eloquent solution (use json in fields) proposed by @akazorg like : https://github.com/spatie/laravel-translatable

@akazorg
Copy link
Contributor

akazorg commented Jun 13, 2017

Hi @coppee Voyager is using the extra table approach, not the JSON one.
It works great this way.

@marktopper marktopper deleted the feature/multilangual branch June 21, 2017 13:09
@vitorbrussi
Copy link

Hello, I am new not Voyager, not understood how to translate to pt_br.
They could pass me one step at a time. Thank you.

@akazorg
Copy link
Contributor

akazorg commented Aug 12, 2017

Hi @vitorbrussi, check this #1364.

@vitormicillo
Copy link
Contributor

It would be very interesting if there was the possibility of having a auto-translator when a locale is defined, and return the translated data In fields that have translation

@akazorg
Copy link
Contributor

akazorg commented Aug 23, 2017

That's a nice idea @vitormicillo, we should work on that.

@vitormicillo
Copy link
Contributor

vitormicillo commented Aug 24, 2017

I created a not very elegant method but it works to auto translate the specific words

private function translatedCollectionToArray(\TCG\Voyager\Translator\Collection $translatedCollection) {
        $dateFields = [ 'created_at', 'updated_at', 'deleted_at' ];
        $collectionArray = array();
        foreach($translatedCollection as $collectionElement)
        {
            $collectionArray[] = array_map(function($rawAttributeData) { return $rawAttributeData['value']; }, $collectionElement->getRawAttributes());
        }
        foreach($collectionArray as $i => $collectionElement)
        {
            foreach($collectionElement as $elementKey => $elementValue)
            {
                if(!in_array($elementKey, $dateFields) || $elementValue == null) { continue; }
                $collectionArray[$i][$elementKey] = strtotime($elementValue);
            }
        }
        return $collectionArray;
    }

@vitormicillo
Copy link
Contributor

vitormicillo commented Aug 24, 2017

This method recycles the use of the '\TCG\Voyager\Translator\Collection'
And stamps the dates in timestamp
I hope to help a little, to run it just send in the header your locale
and to call this methodo:
I'm using: 'Sections' => $this->translatedCollectionToArray(Section::all()->translate($language, 'fallbackLocale')),

$language = $request->header('locale');
Section::all() You can modify to Post::all() to try or Category::all()

@DODMax
Copy link
Contributor

DODMax commented Sep 1, 2017

Really great work on this multilingual feature!

I'm trying to think of a way to use it to store my Laravel translations (like in https://github.com/Waavi/translation but I'd like to avoid using a different package/ UI)

So basically I'm with my translation files that look something like this

'date' => 'Payment date: :date',
'amount' => 'Amount: :amount :currency',
'plan' => 'Plan: :plan',
'from' => 'From: :date',
'to' => 'To: :date',

And I'm thinking of adding a labels table with my key and label columns, making it translatable then having a class a bit like this:

class VoyagerTranslator extends Illuminate\Translation\Translator
{
	private $labels = null;

	public function get($key, array $replace = [], $locale = null, $fallback = true)
	{
		$lang = $this->getLabel($key);

		if ($lang && ($line = $lang->getTranslatedAttribute('label', $locale, false))) {
			return $this->makeReplacements($line, $replace);
		} else {
			return parent::get($key, $replace, $locale, $fallback);
		}
	}

	protected function getLabel($key)
	{
		if ($this->labels == null) {
			$this->labels = \App\Label::with('translations')->get();
		}

		return $this->labels->where('key', $key)->first();
	}
}

Any thoughts? Thanks!

EDIT: Ok after some edits this seems to work well: this will use Voyager's translation from DB if present otherwise fall back to Laravel language files (once registered with the proper service provider of course).

@vitormicillo
Copy link
Contributor

I created a method to use the translations

private function translatedCollectionToArray(\TCG\Voyager\Translator\Collection $translatedCollection) {
    $collectionArray = array();
    foreach($translatedCollection as $collectionElement)
    {
        $collectionArray[] = array_map(function($rawAttributeData) { return $rawAttributeData['value']; }, $collectionElement->getRawAttributes());
    }
    return $collectionArray;
}

and to use:

Ex.

$language = $request->header('locale');
if($language == null || $language !== "it"){$language = "en";}
$section = $this->translatedCollectionToArray(Posts::all()->translate($language, 'fallbackLocale'));

works fine for me to use in translating api, I hope it helps many

@sergbbb
Copy link

sergbbb commented Oct 26, 2017

Please include this guide in Documentation. It is hard to find it. Thank you.

@yarokov
Copy link

yarokov commented Feb 4, 2018

Hi! How can I hide the not translated posts?

@fadighattas100
Copy link

fadighattas100 commented Feb 23, 2018

Hi, any video tutorial for how to do this like in the gif's images? how to translate the post's content , thanks in advance.

@PointfullDevelopers
Copy link

I tried to implement this, but no luck.

  1. When I add ['en' => 'English', 'sv' => 'Swedish'], to the 'locale' on the config it gives error.
  2. There is no Model for Posts and Pages so, where do I make those changes?

Please update your documentation

@amrittb
Copy link

amrittb commented Apr 25, 2018

It seems that I can't translate the entire model and other loaded models using Eloquent relationships using the above methods. Any chance of support on this?

@emptynick
Copy link
Collaborator

Certainly. But you have to give more informations.
And please ask questions in our Slack group!

@sten
Copy link

sten commented May 3, 2018

Hiu @marktopper, you mentioned about a year ago that you wanted to make the translation library in voyager configurable: "About the extra table, I just wanted to let it be flexible. However I can make it extendable so that people can easily make it into JSON instead and remove the translations table."

Is the current implementation configurable? Could you point me to how you would replace the current translation implementation with for instance the one from Spatie: spatie/laravel-translatable

Thanks!

@AlexanderWM
Copy link

@marktopper Any ideas how to fix #3089 ?

@thedevdojo thedevdojo locked as resolved and limited conversation to collaborators May 21, 2018
@fletch3555
Copy link
Collaborator

@AlexanderWM, this PR has been merged. 3089 would be a much better place to ask that question

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet