Skip to content
This repository has been archived by the owner on Dec 9, 2019. It is now read-only.

Needs rewrite to work with new translation system in Laravel 5.4 #17

Open
peterjaap opened this issue Feb 5, 2017 · 3 comments
Open

Comments

@peterjaap
Copy link

https://laravel-news.com/json-based-translations

I'm willing to do it, just need to find some time.

@peterjaap
Copy link
Author

peterjaap commented Feb 11, 2017

So I've found some time to get it working for my installation. I've attached the diff here, if somebody has the time to clean it up, put it into the general extension and update the tests, then it would truly be a team effort.

Be aware, this is a diff for my already extended custom models for this package (see #16) that I've placed in app/Commands/OneSky.

I've decided that there is now one 'source file' in Laravel. This is named after your mapped base locale name (so en, with the mapped OneSky locale being en_GB), which is thus found in resources/lang/en.json and will thus be called en.json in OneSky. This is a little weird when downloading into your other locales, because you'll basically download en.json into your own locale (for example resources/lang/nl.json). Maybe we could just call it something more generic likestrings.json when uploading it to OneSky.

You'll also need to update the file_format index in config/onesky.php from PHP_SHORT_ARRAY to HIERARCHICAL_JSON since we'll be dealing with JSON files instead of PHP files with arrays.

My course of action was to remove all translations in OneSky (backup first) and then push the base language strings from the Laravel app. Then I imported my translations into OneSky manually and pulled them into Laravel again.

My next course of action will be to create an artisan command that can extract translatable strings (basically everything within __()) from the Laravel codebase, like I've done for Magento or @barryvdh has done in his translation manager package. Done, see comment below.

Good luck!

commit 5cee15a9296e1e575584d86534779cfd65c568eb
Author: peterjaap <peterjaap@elgentos.nl>
Date:   Sat Feb 11 12:04:45 2017 +0100

    Updated OneSky overwritten models with updates for 5.4 translation system (Branch: master)

diff --git a/app/Commands/OneSky/Pull.php b/app/Commands/OneSky/Pull.php
index cb8bfb4..d303333 100644
--- a/app/Commands/OneSky/Pull.php
+++ b/app/Commands/OneSky/Pull.php
@@ -6,28 +6,51 @@ use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
 
 class Pull extends \Ageras\LaravelOneSky\Commands\Pull
 {
+    /**
+     * If you just use the base locale to put strings in OneSky and will never translate them,
+     *  it is safer to set this to true.
+     *
+     * @var bool
+     */
+    protected $excludeBaseLocaleFromPulling = false;
 
     public function handle()
     {
         $baseLocale = $this->baseLocale();
         $locales = $this->locales();
-        $translationsPath = $this->translationsPath() . DIRECTORY_SEPARATOR . $baseLocale;
+        $translationsPath = $this->translationsPath();
 
-        $locales = array_diff($locales, [$baseLocale]);
+        /**
+         * Exclude the base locale when downloading translations from OneSky
+         */
+        if ($this->excludeBaseLocaleFromPulling) {
+            $locales = array_diff($locales, [$baseLocale]);
+        }
 
+        /**
+         * If LaravelLocalization is used, map the Laravel locale code (such as 'en') to the
+         * OneSky locale code (a fully qualified one, such as 'en_US')
+         */
         $supportedLocales = LaravelLocalization::getSupportedLocales();
         $supportedLocales = array_combine(array_keys($supportedLocales), array_column($supportedLocales, 'regional'));
         $oneskyLocales = array_map(function ($locale) use ($supportedLocales) {
             return $supportedLocales[$locale];
         }, $locales);
 
-        $files = $this->scanDir($translationsPath);
+
+        /**
+         * We assume here that the name of the file uploaded is the base locale with the extension 'json'.
+         */
+        $file = $translationsPath . DIRECTORY_SEPARATOR . $baseLocale . '.json';
+        if (!file_exists($file)) {
+            throw new \Exception(sprintf('The translation file for the %s locale could not be found. If you are populating your Laravel environment with strings from OneSky, please create an empty translation JSON file for the base locale first.', $baseLocale));
+        }
 
         $this->downloadTranslations(
             $this->client(),
             $this->project(),
             $oneskyLocales,
-            $files
+            [basename($file)]
         );
 
         switch($this->result) {
@@ -44,7 +67,7 @@ class Pull extends \Ageras\LaravelOneSky\Commands\Pull
         foreach ((array)$locales as $locale) {
             foreach ((array)$files as $file) {
                 if ($result = $this->downloadTranslation($client, $project, $locale, $file)) {
-                    echo sprintf('Downloaded %s/%s from project %d', $locale, $file, $project) . PHP_EOL;
+                    $this->info(sprintf('Downloaded translations for locale %s from project %d', $locale, $project));
                 }
             }
         }
@@ -52,11 +75,12 @@ class Pull extends \Ageras\LaravelOneSky\Commands\Pull
 
     public function downloadTranslation($client, $project, $locale, $file)
     {
         $data = $this->prepareTranslationData($project, $locale, $file);
 
         $response = $client->translations('export', $data);
 
-        if(!is_null(json_decode($response))) {
+        if(is_null(json_decode($response))) {
             $this->result = static::UNKNOWN_ERROR;
             $this->invalidResponse($locale, $file, $response);
             return false;
@@ -72,7 +96,7 @@ class Pull extends \Ageras\LaravelOneSky\Commands\Pull
             $locale = $localeMapping[$locale];
         }
 
-        $filePath = $this->translationsPath() . DIRECTORY_SEPARATOR . $locale . DIRECTORY_SEPARATOR . $file;
+        $filePath = $this->translationsPath() . DIRECTORY_SEPARATOR . $locale . '.json';
 
         return file_put_contents($filePath, $response);
     }
diff --git a/app/Commands/OneSky/Push.php b/app/Commands/OneSky/Push.php
index dbf84ba..b3b7650 100644
--- a/app/Commands/OneSky/Push.php
+++ b/app/Commands/OneSky/Push.php
@@ -10,8 +10,12 @@ class Push extends \Ageras\LaravelOneSky\Commands\Push
     public function handle()
     {
         $locale = $this->baseLocale();
-        $translationsPath = $this->translationsPath() . DIRECTORY_SEPARATOR . $locale;
+        $translationsPath = $this->translationsPath();
 
+        /**
+         * If LaravelLocalization is used, map the Laravel locale code (such as 'en') to the
+         * OneSky locale code (a fully qualified one, such as 'en_US')
+         */
         $supportedLocales = LaravelLocalization::getSupportedLocales();
         if (array_key_exists($locale, $supportedLocales)) {
             $oneskyLocale = $supportedLocales[$locale]['regional'];
@@ -19,17 +23,49 @@ class Push extends \Ageras\LaravelOneSky\Commands\Push
             $oneskyLocale = $locale;
         }
 
+        /**
+         * Scan all translations to check whether there is a base locale language file available.
+         * Since 5.4 this is not a requirement anymore but is needed for the OneSky integration
+         * to be able to know which strings are available to be translated.
+         *
+         * @TODO create an artisan command that scans the codebase for translatable strings
+         *
+         */
         $files = $this->scanDir($translationsPath);
+        if (!in_array($locale, $files)) {
+            throw new \Exception(sprintf('You should create a JSON translation file for your base locale %s too.', $locale));
+        }
+
+        $file = $translationsPath . DIRECTORY_SEPARATOR . $locale . '.json';
 
-        $files = array_map(function ($fileName) use (&$locale, &$translationsPath) {
-            return $translationsPath . DIRECTORY_SEPARATOR . $fileName;
-        }, $files);
+        if (!file_exists($file)) {
+            throw new \Exception(sprintf('The translation file for the %s locale could not be found.', $locale));
+        }
 
         $this->uploadFiles(
             $this->client(),
             $this->project(),
             $oneskyLocale,
-            $files
+            [$file]
         );

         $this->info('Files were uploaded successfully!');
     }

+    public function prepareUploadData($project, $locale, array $files)
+    {
+        $data = [];
+        foreach ($files as $file) {
+            $data[] = [
+                'project_id'  => $project,
+                'file'        => $file,
+                'file_format' => 'HIERARCHICAL_JSON',
+                'locale'      => $locale,
+            ];
+        }
+
+        return $data;
+    }

     /**
      * @param \OneSky\Api\Client $client
@@ -47,7 +64,7 @@ class Push extends \Ageras\LaravelOneSky\Commands\Push
 
         foreach ($data as $d) {
             if ($responseStatus = $this->uploadFile($client, $d)) {
-                echo sprintf('Uploaded %s/%s to project %d (status %s)', $d['locale'], basename($d['file']), $d['project_id'], $responseStatus) . PHP_EOL;
+                $this->info(sprintf('Uploaded %s to project %d (status %s)', basename($d['file']), $d['project_id'], $responseStatus));
             }
         }
     }

@peterjaap
Copy link
Author

peterjaap commented Feb 11, 2017

Actually had time to also create the artisan command for finding translation strings! Thanks @barryvdh for the great starting point.

app/Commands/OneSky/Find.php

<?php

namespace App\Commands\OneSky;

use Symfony\Component\Finder\Finder;

class Find extends \Ageras\LaravelOneSky\Commands\BaseCommand
{
    protected $signature = 'onesky:find';

    protected $description = 'Find translatable strings in templates';

    public function handle()
    {
        $keys = [];
        $functions =  ['__'];
        $pattern =                              // See http://regexr.com/392hu
            "[^\w|>]".                          // Must not have an alphanum or _ or > before real method
            "(".implode('|', $functions) .")".  // Must start with one of the functions
            "\(".                               // Match opening parenthese
            "[\'\"]".                           // Match " or '
            "(".                                // Start a new group to match:
            "(.*)".                             // Anything
            //"([.][^\1)]+)+".                  // Be followed by one or more items/keys, uncommented for 5.4; groups aren't used anymore
            ")".                                // Close group
            "[\'\"]".                           // Closing quote
            "[\),]";                            // Close parentheses or new parameter

        // Find all PHP + Twig files in the app folder, except for storage
        $finder = new Finder();
        $finder->in(base_path())->exclude('storage')->name('*.php')->name('*.twig')->files();

        /** @var \Symfony\Component\Finder\SplFileInfo $file */
        foreach ($finder as $file) {
            // Search the current file for the pattern
            if(preg_match_all("/$pattern/siU", $file->getContents(), $matches)) {
                // Get all matches
                foreach ($matches[2] as $key) {
                    $this->info('Found <fg=white>' . $key . '</> in <fg=cyan>' . $file->getFilename() . '</>');
                    $keys[] = $key;
                }
            }
        }

        // Remove duplicates
        $keys = array_unique($keys);

        // Create combined array to easily overwrite with existing translations
        $keys = array_combine($keys, $keys);

        $baseLocaleJson = $this->translationsPath() . DIRECTORY_SEPARATOR . $this->baseLocale() . '.json';
        if (file_exists($baseLocaleJson)) {
            $baseLocaleArray = json_decode(file_get_contents($baseLocaleJson), true);
            $baseLocaleArray = array_merge($keys, $baseLocaleArray);
        } else {
            $baseLocaleArray = $keys;
        }

        if ($file = $this->ask('To which file do you want to write the found strings?', 'resources/lang/' . $this->baseLocale() . '.json')) {
            file_put_contents($file, json_encode($baseLocaleArray, JSON_PRETTY_PRINT));
            $this->info('Found strings have successfully been written to ' . $file);
        }
    }
}

app/Providers/OneSkyServiceProvider.php

<?php

namespace App\Providers;

use App\Commands\OneSky as Commands;

class OneSkyServiceProvider extends \Ageras\LaravelOneSky\ServiceProvider
{

    public function registerCommands()
    {
        $this->app->bindIf('command.onesky', function () {
            return new \Ageras\LaravelOneSky\Commands\OneSky();
        });
        $this->app->bindIf('command.onesky.pull', function () {
            return new Commands\Pull();
        });
        $this->app->bindIf('command.onesky.push', function () {
            return new Commands\Push();
        });
        $this->app->bindIf('command.onesky.find', function () {
            return new Commands\Find();
        });

        $this->commands(
            'command.onesky',
            'command.onesky.pull',
            'command.onesky.push',
            'command.onesky.find'
        );
    }

}

Running it will give you;

➜  testproject git:(master) ✗ php artisan onesky:find
Found Translate me! in index.blade.php

 To which file do you want to write the found strings? [resources/lang/en.json]:
 >

Found strings have successfully been written to resources/lang/en.json

➜  testproject git:(master) ✗ cat resources/lang/en.json
{"Translate me!", "Translate me!"}

At which point you'll run php artisan onesky:push, have it translated in OneSky and run php artisan onesky:pull. Happy campers!

@springerin
Copy link

great job 👍 i'll give it a try asap :)

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

No branches or pull requests

2 participants