I18N   B
last analyzed

Coupling/Cohesion

Components 2
Dependencies 5

Complexity

Total Complexity 54

Size/Duplication

Total Lines 476
Duplicated Lines 0 %

Test Coverage

Coverage 39.08%

Importance

Changes 8
Bugs 1 Features 0
Metric Value
c 8
b 1
f 0
dl 0
loc 476
ccs 68
cts 174
cp 0.3908
rs 7.0642
wmc 54
lcom 2
cbo 5

18 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 30 2
C getDefaultLanguage() 0 39 8
A setCurrentLanguage() 0 12 2
A loadLanguage() 0 17 3
A getCurrent() 0 4 1
A getCurrentId() 0 4 1
B setLanguage() 0 25 6
A isLoaded() 0 4 1
A isDefault() 0 8 3
A isAvailable() 0 4 1
B languages() 0 20 5
A languagesForSelect() 0 9 2
A translateGetString() 0 13 2
A translateInsertString() 0 18 1
B translate() 0 35 6
A getRawStrings() 0 4 1
B getContext() 0 23 5
B generate() 0 23 4

How to fix   Complexity   

Complex Class

Complex classes like I18N often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use I18N, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Translation management
5
 */
6
namespace Rocket\Translation;
7
8
use Exception;
9
use Illuminate\Foundation\Application as IlluminateApplication;
10
use Request;
11
use Rocket\Translation\Model\Language;
12
use Rocket\Translation\Model\StringModel;
13
use Rocket\Translation\Model\Translation;
14
15
/**
16
 * Class I18N
17
 */
18
class I18N
19
{
20
    /**
21
     * An array of the loaded languages
22
     * @var array
23
     */
24
    protected $languagesLoaded = [];
25
26
    /**
27
     * An array of existing languages by ISO
28
     * @var array
29
     */
30
    protected $languagesIso = [];
31
32
    /**
33
     * An array of existing languages by ID
34
     * @var array
35
     */
36
    protected $languagesId = [];
37
38
    /**
39
     * Language currently in use
40
     * @var string
41
     */
42
    protected $currentLanguage;
43
44
    /**
45
     * Language currently in use (ID)
46
     * @var string
47
     */
48
    protected $currentLanguageId;
49
50
    /**
51
     * All the translation strings
52
     * @var array
53
     */
54
    protected $strings = [];
55
56
    /**
57
     * Path to the language files
58
     * @var string
59
     */
60
    protected $filePath;
61
62
    /**
63
     * Context of the current page
64
     * @var string
65
     */
66
    protected $pageContext;
67
68
    /**
69
     * Strings cache
70
     * @var array
71
     */
72
    protected $stringsRaw = [];
73
74
    protected $cache;
75
    protected $session;
76
    protected $log;
77
78
    /**
79
     * Prepare the translation service
80
     *
81
     * @param IlluminateApplication $app
82
     */
83 177
    public function __construct(IlluminateApplication $app)
84
    {
85 177
        $this->filePath = $app['path.storage'] . '/languages/';
86 177
        $this->cache = $app['cache'];
87 177
        $this->session = $app['session'];
88 177
        $this->log = $app['log'];
89
90 177
        $lang = $this->cache->remember(
91 177
            'Lang::List',
92 177
            60 * 24,
93 177
            function () {
94 177
                return Language::all();
95
            }
96 177
        );
97
98 177
        foreach ($lang as $l) {
99 177
            $this->languagesIso[$l->iso] = $this->languagesId[$l->id] = [
100 177
                'id' => $l->id,
101 177
                'name' => $l->title,
102 177
                'iso' => $l->iso,
103
            ];
104 177
        }
105
106 177
        $locale = $app['config']['app.locale'];
107 177
        $fallback = $app['config']['app.fallback_locale'];
108
109
        //current default language
110 177
        $language = $this->getDefaultLanguage($locale, $fallback);
111 177
        $this->setLanguage($language);
112 177
    }
113
114
    /**
115
     * Detects the default languages in the following order :
116
     *
117
     * 1. Is a user session var defined ?
118
     * 2. Can we take it from the browser ?
119
     * 3. Take the site default
120
     *
121
     * @param $locale string
122
     * @param $fallback string
123
     * @throws Exception if a default language cannot be found
124
     * @return string
125
     */
126 177
    public function getDefaultLanguage($locale, $fallback)
127
    {
128
        //1. detect user session
129 177
        $session_lang = $this->session->get('language');
130
131 177
        if (!empty($session_lang)) {
132
            return $session_lang;
133
        }
134
135
        //TODO :: move languages to subdomains
136
        //Special workaroud : only french for the moment
137 177
        if (defined('F_LANGUAGES') && !F_LANGUAGES) {
138
            return 'fr';
139
        }
140
141
        //2. detect browser language
142 177
        $browser_languages = Request::getLanguages();
143
144
        //is one of them available ?
145 177
        foreach ($browser_languages as $lang) {
146 177
            if ($this->isAvailable($lang)) {
147 177
                $this->session->put('language', $lang);
148
149 177
                return $lang;
150
            }
151 177
        }
152
153
        //3. Site default
154
        if ($this->isAvailable($locale)) {
155
            return $locale;
156
        }
157
158
        //4. Site fallback
159
        if ($this->isAvailable($fallback)) {
160
            return $fallback;
161
        }
162
163
        throw new \Exception('Cannot find an adapted language');
164
    }
165
166
    /**
167
     * Set the current language
168
     *
169
     * @param  string $language
170
     * @return bool
171
     */
172
    public function setCurrentLanguage($language)
173
    {
174
        if (!$this->isAvailable($language)) {
175
            return false;
176
        }
177
178
        $this->session->put('language', $language);
179
180
        $this->setLanguage($language);
181
182
        return true;
183
    }
184
185
    /**
186
     * Load a language file
187
     *
188
     * @param  string $language
189
     * @return bool
190
     */
191 177
    public function loadLanguage($language)
192
    {
193 177
        if ($this->isLoaded($language)) {
194
            return;
195
        }
196
197 177
        $langfile = $language . '.php';
198
199 177
        $this->strings[$language] = [];
200
201
        // Determine where the language file is and load it
202 177
        if (file_exists($this->filePath . $langfile)) {
203
            $this->strings[$language] = include $this->filePath . $langfile;
204
        }
205
206 177
        $this->languagesLoaded[] = $language;
207 177
    }
208
209
    /**
210
     * Get the current language
211
     * @return string
212
     */
213 27
    public function getCurrent()
214
    {
215 27
        return $this->currentLanguage;
216
    }
217
218
    /**
219
     * Get the current language id
220
     * @return int
221
     */
222 150
    public function getCurrentId()
223
    {
224 150
        return $this->currentLanguageId;
225
    }
226
227
    /**
228
     * Set the language to use
229
     *
230
     * @param  string $language
231
     * @return bool
232
     */
233 177
    public function setLanguage($language)
234
    {
235 177
        if ($language == $this->currentLanguage) {
236 156
            return;
237
        }
238
239 177
        if (!$this->isLoaded($language)) {
240 177
            $this->loadLanguage($language);
241 177
        }
242
243
        switch ($language) {
244 177
            case 'fr':
245 21
                setlocale(LC_ALL, 'fr_FR.utf8', 'fr_FR.UTF-8', '[email protected]', 'fr_FR', 'french');
246 21
                break;
247 177
            case 'en':
248 177
                setlocale(LC_ALL, 'en_US.utf8', 'en_US.UTF-8', 'en_US');
249 177
                break;
250
            case 'de':
251
                setlocale(LC_ALL, 'de_DE.utf8', 'de_DE.UTF-8', '[email protected]', 'de_DE', 'deutsch');
252
                break;
253
        }
254
255 177
        $this->currentLanguage = $language;
256 177
        $this->currentLanguageId = $this->languagesIso[$language]['id'];
257 177
    }
258
259
    /**
260
     * Checks if a language is loaded or not
261
     *
262
     * @param  string $language
263
     * @return bool
264
     */
265 177
    protected function isLoaded($language)
266
    {
267 177
        return in_array($language, $this->languagesLoaded);
268
    }
269
270
    /**
271
     * Checks if a language is the default one
272
     *
273
     * @param  string $language
274
     * @return bool
275
     */
276
    protected function isDefault($language)
277
    {
278
        if ($language == 'default' or $this->currentLanguage == $language) {
279
            return true;
280
        }
281
282
        return false;
283
    }
284
285
    /**
286
     * Checks if the language is availavble
287
     *
288
     * @param  string $language
289
     * @return bool
290
     */
291 177
    protected function isAvailable($language)
292
    {
293 177
        return array_key_exists($language, $this->languagesIso);
294
    }
295
296
    /**
297
     * Retrieve languages.
298
     *
299
     * this is a hybrid method.
300
     *
301
     *
302
     *     I18N::languages();
303
     *     returns ['fr' => ['id' => 1, 'name' => 'francais', 'iso' => 'fr'], 'en' => ...]
304
     *
305
     *
306
     *     I18N::languages('fr');
307
     *     returns ['id' => 1, 'name' => 'francais', 'iso' => 'fr']
308
     *
309
     *
310
     *     I18N::languages(1);
311
     *     returns ['id' => 1, 'name' => 'francais', 'iso' => 'fr']
312
     *
313
     *
314
     *     I18N::languages('fr', 'id');
315
     *     returns 1
316
     *
317
     * @param int|string $key
318
     * @param string $subkey
319
     * @return array
320
     */
321 36
    public function languages($key = null, $subkey = null)
322
    {
323 36
        if ($key === null) {
324 33
            return $this->languagesIso;
325
        }
326
327 3
        if (is_int($key)) {
328
            if (is_null($subkey)) {
329
                return $this->languagesId[$key];
330
            }
331
332
            return $this->languagesId[$key][$subkey];
333
        }
334
335 3
        if (is_null($subkey)) {
336
            return $this->languagesIso[$key];
337
        }
338
339 3
        return $this->languagesIso[$key][$subkey];
340
    }
341
342
    public function languagesForSelect()
343
    {
344
        $languages = [];
345
        foreach (static::languages() as $lang) {
346
            $languages[$lang['id']] = t($lang['name'], [], 'languages');
347
        }
348
349
        return $languages;
350
    }
351
352
    protected function translateGetString($context, $language, $string_id)
353
    {
354
        $row = Translation::select('text')
355
            ->where('string_id', $string_id)
356
            ->where('language_id', $this->languagesIso[$language]['id'])
357
            ->first();
358
359
        if ($row !== null) {
360
            return $row->text;
361
        }
362
363
        return false;
364
    }
365
366
    protected function translateInsertString($context, $text)
367
    {
368
        $string = new StringModel();
369
        $string->date_creation = mysql_datetime();
370
        $string->context = $context;
371
        $string->string = $text;
372
        $string->save();
373
374
        //insertion de la traduction par défaut.
375
        $translation = new Translation();
376
        $translation->string_id = $string->id;
377
        $translation->language_id = $this->languagesIso[app('config')['app.locale']]['id'];
378
        $translation->date_edition = mysql_datetime();
379
        $translation->text = $text;
380
        $translation->save();
381
382
        return $translation;
383
    }
384
385
    /**
386
     * Retreive a string to translate
387
     *
388
     * if it doesn't find it, put it in the database
389
     *
390
     * @param  string $string
391
     * @param  string $context
392
     * @param  string $language
393
     * @return string
394
     */
395
    public function translate($string, $context = 'default', $language = 'default')
396
    {
397
        if ($this->isDefault($language)) {
398
            $language = $this->currentLanguage;
399
        } else {
400
            $this->setLanguage($language);
401
        }
402
403
        //get string from cache
404
        if (array_key_exists($context, $this->strings[$language]) &&
405
            array_key_exists($string, $this->strings[$language][$context])) {
406
            return $this->strings[$language][$context][$string];
407
        }
408
409
        //check in db
410
        $db_string = StringModel::select('id', 'date_creation')
411
            ->where('string', $string)
412
            ->where('context', $context)
413
            ->first();
414
415
        if (!$db_string) {
416
            $this->translateInsertString($context, $string);
417
418
            return $string;
419
        }
420
421
        $text = $this->translateGetString($context, $language, $db_string->id);
422
        if ($text) {
423
            $this->strings[$language][$context][$string] = $text;
424
425
            return $text;
426
        }
427
428
        return $string;
429
    }
430
431
    /**
432
     * Get the cached strings
433
     *
434
     * @return array
435
     */
436
    public function getRawStrings()
437
    {
438
        return $this->stringsRaw;
439
    }
440
441
    /**
442
     * Get the page's context
443
     *
444
     * @return string
445
     */
446
    public function getContext()
447
    {
448
        if ($this->pageContext) {
449
            return $this->pageContext;
450
        }
451
452
        $current = \Route::getCurrentRoute();
453
454
        if (!$current) {
455
            return 'default';
456
        }
457
458
        if ($current->getName()) {
459
            return $this->pageContext = $current->getName();
460
        }
461
462
        $action = $current->getAction();
463
        if (array_key_exists('controller', $action)) {
464
            return $this->pageContext = $action['controller'];
465
        }
466
467
        return $this->pageContext = implode('/', array_map(['Str', 'slug'], explode('/', $current->getUri())));
468
    }
469
470
    public function generate()
471
    {
472
        $filePath = app('path.storage') . '/languages/';
473
474
        if (!is_dir($filePath)) {
475
            mkdir($filePath, 0755, true);
476
            chmod($filePath, 0755);
477
        }
478
479
        foreach ($this->languages() as $lang => $d) {
480
            $strings = StringModel::select('string', 'text', 'context')
481
                ->where('language_id', $d['id'])
482
                ->join((new Translation)->getTable(), 'strings.id', '=', 'string_id', 'left')
483
                ->get();
484
485
            $final_strings = [];
486
            foreach ($strings as $s) {
487
                $final_strings[$s->context][$s->string] = $s->text;
488
            }
489
490
            file_put_contents("{$filePath}{$lang}.php", '<?php return ' . var_export($final_strings, true) . ';');
491
        }
492
    }
493
}
494