Completed
Push — fix-2494 ( 3153ee...40d9bb )
by Sam
13:43 queued 06:38
created

i18nTest::setUp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\i18n\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Core\Convert;
8
use SilverStripe\Core\Injector\Injector;
9
use SilverStripe\Dev\SapphireTest;
10
use SilverStripe\i18n\i18n;
11
use SilverStripe\i18n\Messages\MessageProvider;
12
use SilverStripe\i18n\Messages\Symfony\SymfonyMessageProvider;
13
use SilverStripe\View\ArrayData;
14
use SilverStripe\View\SSViewer;
15
16
class i18nTest extends SapphireTest
17
{
18
    use i18nTestManifest;
19
20
    protected function setUp()
21
    {
22
        parent::setUp();
23
        $this->setupManifest();
24
    }
25
26
    protected function tearDown()
27
    {
28
        $this->tearDownManifest();
29
        parent::tearDown();
30
    }
31
32
    public function testGetExistingTranslations()
33
    {
34
        $translations = i18n::getSources()->getKnownLocales();
35
        $this->assertTrue(isset($translations['en_US']), 'Checking for en translation');
36
        $this->assertEquals($translations['en_US'], 'English (United States)');
37
        $this->assertTrue(isset($translations['de_DE']), 'Checking for de_DE translation');
38
    }
39
40
    public function testGetClosestTranslation()
41
    {
42
        // Validate necessary assumptions for this test
43
        // As per set of locales loaded from _fakewebroot
44
        $translations = i18n::getSources()->getKnownLocales();
45
        $this->assertEquals(
46
            [
47
                'en_GB',
48
                'en_US',
49
                'fr_FR',
50
                'de_AT',
51
                'de_DE',
52
                'ja_JP',
53
                'mi_NZ',
54
                'pl_PL',
55
                'es_AR',
56
                'es_ES',
57
            ],
58
            array_keys($translations)
59
        );
60
61
        // Test indeterminate locales
62
        $this->assertEmpty(i18n::get_closest_translation('zz_ZZ'));
63
64
        // Test english fallback
65
        $this->assertEquals('en_US', i18n::get_closest_translation('en_US'));
66
        $this->assertEquals('en_GB', i18n::get_closest_translation('en_GB'));
67
        $this->assertEquals('en_US', i18n::get_closest_translation('en_ZZ'));
68
69
        // Test spanish fallbacks
70
        $this->assertEquals('es_AR', i18n::get_closest_translation('es_AR'));
71
        $this->assertEquals('es_ES', i18n::get_closest_translation('es_ES'));
72
        $this->assertEquals('es_ES', i18n::get_closest_translation('es_XX'));
73
    }
74
75
    public function testDataObjectFieldLabels()
76
    {
77
        i18n::set_locale('de_DE');
78
79
        // Load into the translator as a literal array data source
80
        /** @var SymfonyMessageProvider $provider */
81
        $provider = Injector::inst()->get(MessageProvider::class);
82
        $provider->getTranslator()->addResource(
83
            'array',
84
            [ i18nTest\TestDataObject::class.'.MyProperty' => 'MyProperty' ],
85
            'en_US'
86
        );
87
        $provider->getTranslator()->addResource(
88
            'array',
89
            [ i18nTest\TestDataObject::class.'.MyProperty' => 'Mein Attribut' ],
90
            'de_DE'
91
        );
92
        $provider->getTranslator()->addResource(
93
            'array',
94
            [ i18nTest\TestDataObject::class.'.MyUntranslatedProperty' => 'Mein Attribut' ],
95
            'en_US'
96
        );
97
98
        // Test field labels
99
        $obj = new i18nTest\TestDataObject();
100
        $this->assertEquals(
101
            'Mein Attribut',
102
            $obj->fieldLabel('MyProperty')
103
        );
104
        $this->assertEquals(
105
            'My Untranslated Property',
106
            $obj->fieldLabel('MyUntranslatedProperty')
107
        );
108
    }
109
110
    public function testProvideI18nEntities()
111
    {
112
        /** @var SymfonyMessageProvider $provider */
113
        $provider = Injector::inst()->get(MessageProvider::class);
114
        $provider->getTranslator()->addResource(
115
            'array',
116
            [ i18nTest\TestObject::class.'.MyProperty' => 'Untranslated' ],
117
            'en_US'
118
        );
119
        $provider->getTranslator()->addResource(
120
            'array',
121
            [ i18nTest\TestObject::class.'.my_translatable_property' => 'Übersetzt' ],
122
            'de_DE'
123
        );
124
125
        $this->assertEquals(
126
            i18nTest\TestObject::$my_translatable_property,
0 ignored issues
show
Bug introduced by
The property my_translatable_property cannot be accessed from this context as it is declared private in class SilverStripe\i18n\Tests\i18nTest\TestObject.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
127
            'Untranslated'
128
        );
129
        $this->assertEquals(
130
            i18nTest\TestObject::my_translatable_property(),
131
            'Untranslated'
132
        );
133
134
        i18n::set_locale('en_US');
135
        $this->assertEquals(
136
            i18nTest\TestObject::my_translatable_property(),
137
            'Untranslated',
138
            'Getter returns original static value when called in default locale'
139
        );
140
141
        i18n::set_locale('de_DE');
142
        $this->assertEquals(
143
            i18nTest\TestObject::my_translatable_property(),
144
            'Übersetzt',
145
            'Getter returns translated value when called in another locale'
146
        );
147
    }
148
149
    public function testTemplateTranslation()
150
    {
151
        $oldLocale = i18n::get_locale();
152
        i18n::config()->update('missing_default_warning', false);
153
154
        /** @var SymfonyMessageProvider $provider */
155
        $provider = Injector::inst()->get(MessageProvider::class);
156
        $provider->getTranslator()->addResource(
157
            'array',
158
            [
159
                'i18nTestModule.MAINTEMPLATE' => 'Main Template',
160
                'i18nTestModule.ss.SPRINTFNONAMESPACE' => 'My replacement no namespace: %s',
161
                'i18nTestModule.LAYOUTTEMPLATE' => 'Layout Template',
162
                'i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE' => 'Layout Template no namespace',
163
                'i18nTestModule.SPRINTFNAMESPACE' => 'My replacement: %s',
164
                'i18nTestModule.WITHNAMESPACE' => 'Include Entity with Namespace',
165
                'i18nTestModuleInclude.ss.NONAMESPACE' => 'Include Entity without Namespace',
166
                'i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE' => 'My include replacement: %s',
167
                'i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE' => 'My include replacement no namespace: %s'
168
            ],
169
            'en_US'
170
        );
171
172
        $viewer = new SSViewer('i18nTestModule');
173
        $parsedHtml = Convert::nl2os($viewer->process(new ArrayData([
174
            'TestProperty' => 'TestPropertyValue'
175
        ])));
176
        $this->assertContains(
177
            Convert::nl2os("Layout Template\n"),
178
            $parsedHtml
179
        );
180
        $this->assertContains(
181
            Convert::nl2os("Layout Template no namespace\n"),
182
            $parsedHtml
183
        );
184
185
        $provider->getTranslator()->addResource(
186
            'array',
187
            [
188
                'i18nTestModule.MAINTEMPLATE' => 'TRANS Main Template',
189
                'i18nTestModule.ss.SPRINTFNONAMESPACE' => 'TRANS My replacement no namespace: %s',
190
                'i18nTestModule.LAYOUTTEMPLATE' => 'TRANS Layout Template',
191
                'i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE' => 'TRANS Layout Template no namespace',
192
                'i18nTestModule.SPRINTFNAMESPACE' => 'TRANS My replacement: %s',
193
                'i18nTestModule.WITHNAMESPACE' => 'TRANS Include Entity with Namespace',
194
                'i18nTestModuleInclude.ss.NONAMESPACE' => 'TRANS Include Entity without Namespace',
195
                'i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE' => 'TRANS My include replacement: %s',
196
                'i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE' => 'TRANS My include replacement no namespace: %s',
197
                'i18nTestModule.PLURALS' => 'An item|{count} items',
198
            ],
199
            'de_DE'
200
        );
201
202
        i18n::set_locale('de_DE');
203
        $viewer = new SSViewer('i18nTestModule');
204
        $parsedHtml = Convert::nl2os($viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue'))));
205
        $this->assertContains(
206
            Convert::nl2os("TRANS Main Template\n"),
207
            $parsedHtml
208
        );
209
        $this->assertContains(
210
            Convert::nl2os("TRANS Layout Template\n"),
211
            $parsedHtml
212
        );
213
        $this->assertContains(
214
            Convert::nl2os("TRANS Layout Template no namespace\n"),
215
            $parsedHtml
216
        );
217
        $this->assertContains(
218
            Convert::nl2os("TRANS My replacement: TestPropertyValue\n"),
219
            $parsedHtml
220
        );
221
        $this->assertContains(
222
            Convert::nl2os("TRANS Include Entity with Namespace\n"),
223
            $parsedHtml
224
        );
225
        $this->assertContains(
226
            Convert::nl2os("TRANS Include Entity without Namespace\n"),
227
            $parsedHtml
228
        );
229
        $this->assertContains(
230
            Convert::nl2os("TRANS My include replacement: TestPropertyValue\n"),
231
            $parsedHtml
232
        );
233
        $this->assertContains(
234
            Convert::nl2os("TRANS My include replacement no namespace: TestPropertyValue\n"),
235
            $parsedHtml
236
        );
237
        // Check plurals
238
        $this->assertContains('Single: An item', $parsedHtml);
239
        $this->assertContains('Multiple: 4 items', $parsedHtml);
240
        $this->assertContains('None: 0 items', $parsedHtml);
241
242
        i18n::set_locale($oldLocale);
243
    }
244
245
    public function testNewTMethodSignature()
246
    {
247
        /** @var SymfonyMessageProvider $provider */
248
        $provider = Injector::inst()->get(MessageProvider::class);
249
        $provider->getTranslator()->addResource(
250
            'array',
251
            [
252
                'i18nTestModule.NEWMETHODSIG' => 'TRANS New _t method signature test',
253
                'i18nTestModule.INJECTIONS' => 'TRANS Hello {name} {greeting}. But it is late, {goodbye}',
254
                'i18nTestModule.INJECTIONSLEGACY' => 'TRANS Hello %s %s. But it is late, %s',
255
            ],
256
            'en_US'
257
        );
258
259
        $entity = "i18nTestModule.INJECTIONS";
260
        $default = "Hello {name} {greeting}. But it is late, {goodbye}";
261
        $entityLegacy = 'i18nTestModule.INJECTIONSLEGACY';
262
        $defaultLegacy = 'TRANS Hello %s %s. But it is late, %s';
263
264
        // Test missing entity key
265
        $translated = i18n::_t(
266
            $entity.'_DOES_NOT_EXIST',
267
            $default,
268
            array("name"=>"Mark", "greeting"=>"welcome", "goodbye"=>"bye")
269
        );
270
        $this->assertContains(
271
            "Hello Mark welcome. But it is late, bye",
272
            $translated,
273
            "Testing fallback to the translation default (but using the injection array)"
274
        );
275
276
        // Test standard injection
277
        $translated = i18n::_t(
278
            $entity,
279
            $default,
280
            ["name"=>"Paul", "greeting"=>"good you are here", "goodbye"=>"see you"]
281
        );
282
        $this->assertContains(
283
            "TRANS Hello Paul good you are here. But it is late, see you",
284
            $translated,
285
            "Testing entity, default string and injection array"
286
        );
287
288
        // @deprecated 5.0 Passing in context
289
        $translated = i18n::_t(
290
            $entity,
291
            $default,
292
            "New context (this should be ignored)",
293
            ["name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen"]
294
        );
295
        $this->assertContains(
296
            "TRANS Hello Steffen willkommen. But it is late, wiedersehen",
297
            $translated,
298
            "Full test of translation, using default, context and injection array"
299
        );
300
301
        // @deprecated 5.0 Passing in % placeholders (detected in default value)
302
        // Note: Missing-placeholder substitution no longer functions
303
        $translated = i18n::_t(
304
            $entityLegacy, // has %s placeholders
305
            $defaultLegacy,
306
            ["name"=>"Cat", "greeting2"=>"meow", "goodbye"=>"meow"]
307
        );
308
        $this->assertContains(
309
            "TRANS Hello Cat meow. But it is late, meow",
310
            $translated,
311
            "Testing sprintf placeholders with named injections"
312
        );
313
314
        // Passing in non-associative arrays for placeholders is now an error
315
        $this->setExpectedException(InvalidArgumentException::class, 'Injection must be an associative array');
316
        i18n::_t(
317
            $entity, // has {name} placeholders
318
            $default,
319
            ["Cat", "meow", "meow"]
320
        );
321
    }
322
323
    /**
324
     * See @i18nTestModule.ss for the template that is being used for this test
325
     * */
326
    public function testNewTemplateTranslation()
327
    {
328
        i18n::config()->update('missing_default_warning', false);
329
330
        /** @var SymfonyMessageProvider $provider */
331
        $provider = Injector::inst()->get(MessageProvider::class);
332
        $provider->getTranslator()->addResource(
333
            'array',
334
            [
335
                'i18nTestModule.NEWMETHODSIG' => 'TRANS New _t method signature test',
336
                'i18nTestModule.INJECTIONS' => 'TRANS Hello {name} {greeting}. But it is late, {goodbye}'
337
            ],
338
            'en_US'
339
        );
340
341
        $viewer = new SSViewer('i18nTestModule');
342
        $parsedHtml = Convert::nl2os($viewer->process(new ArrayData(['TestProperty' => 'TestPropertyValue'])));
343
        $this->assertContains(
344
            Convert::nl2os("Hello Mark welcome. But it is late, bye\n"),
345
            $parsedHtml,
346
            "Testing fallback to the translation default (but using the injection array)"
347
        );
348
349
        $this->assertContains(
350
            Convert::nl2os("TRANS Hello Paul good you are here. But it is late, see you\n"),
351
            $parsedHtml,
352
            "Testing entity, default string and injection array"
353
        );
354
355
        //test injected calls
356
        $this->assertContains(
357
            Convert::nl2os(
358
                "TRANS Hello ".Director::absoluteBaseURL()." ".i18n::get_locale().". But it is late, global calls\n"
359
            ),
360
            $parsedHtml,
361
            "Testing a translation with just entity and injection array, but with global variables injected in"
362
        );
363
    }
364
365
    public function testGetLocaleFromLang()
366
    {
367
        $this->assertEquals('en_US', i18n::getData()->localeFromLang('en'));
368
        $this->assertEquals('de_DE', i18n::getData()->localeFromLang('de_DE'));
369
        $this->assertEquals('xy_XY', i18n::getData()->localeFromLang('xy'));
370
    }
371
372
    public function testValidateLocale()
373
    {
374
        $this->assertTrue(i18n::getData()->validate('en_US'), 'Known locale in underscore format is valid');
375
        $this->assertTrue(i18n::getData()->validate('en-US'), 'Known locale in dash format is valid');
376
        $this->assertFalse(i18n::getData()->validate('en'), 'Short lang format is not valid');
377
        $this->assertFalse(i18n::getData()->validate('xx_XX'), 'Unknown locale in correct format is not valid');
378
        $this->assertFalse(i18n::getData()->validate(''), 'Empty string is not valid');
379
        $this->assertTrue(i18n::getData()->validate('de_DE'), 'Known locale where language is same as region');
380
        $this->assertTrue(i18n::getData()->validate('fr-FR'), 'Known locale where language is same as region');
381
        $this->assertTrue(i18n::getData()->validate('zh_cmn'), 'Known locale with all lowercase letters');
382
    }
383
384
    public function testTranslate()
385
    {
386
        /** @var SymfonyMessageProvider $provider */
387
        $provider = Injector::inst()->get(MessageProvider::class);
388
        $provider->getTranslator()->addResource(
389
            'array',
390
            [ 'i18nTestModule.ENTITY' => 'Entity with "Double Quotes"' ],
391
            'en_US'
392
        );
393
        $provider->getTranslator()->addResource(
394
            'array',
395
            [
396
                'i18nTestModule.ENTITY' => 'Entity with "Double Quotes" (de)',
397
                'i18nTestModule.ADDITION' => 'Addition (de)',
398
            ],
399
            'de'
400
        );
401
        $provider->getTranslator()->addResource(
402
            'array',
403
            [
404
                'i18nTestModule.ENTITY' => 'Entity with "Double Quotes" (de_AT)',
405
            ],
406
            'de_AT'
407
        );
408
409
410
        $this->assertEquals(
411
            'Entity with "Double Quotes"',
412
            i18n::_t('i18nTestModule.ENTITY', 'Ignored default'),
413
            'Returns translation in default language'
414
        );
415
416
        i18n::set_locale('de');
417
        $this->assertEquals(
418
            'Entity with "Double Quotes" (de)',
419
            i18n::_t('i18nTestModule.ENTITY', 'Entity with "Double Quotes"'),
420
            'Returns translation according to current locale'
421
        );
422
423
        i18n::set_locale('de_AT');
424
        $this->assertEquals(
425
            'Entity with "Double Quotes" (de_AT)',
426
            i18n::_t('i18nTestModule.ENTITY', 'Entity with "Double Quotes"'),
427
            'Returns specific regional translation if available'
428
        );
429
        $this->assertEquals(
430
            'Addition (de)',
431
            i18n::_t('i18nTestModule.ADDITION', 'Addition'),
432
            'Returns fallback non-regional translation if regional is not available'
433
        );
434
435
        i18n::set_locale('fr');
436
        $this->assertEquals(
437
            'Entity with "Double Quotes" (fr)',
438
            i18n::_t('i18nTestModule.ENTITY', 'Entity with "Double Quotes"'),
439
            'Non-specific locales fall back to language-only localisations'
440
        );
441
    }
442
443
    public function pluralisationDataProvider()
444
    {
445
        return [
446
            // English - 2 plural forms
447
            ['en_NZ', 0, '0 months'],
448
            ['en_NZ', 1, 'A month'],
449
            ['en_NZ', 2, '2 months'],
450
            ['en_NZ', 5, '5 months'],
451
            ['en_NZ', 10, '10 months'],
452
            // Polish - 4 plural forms
453
            ['pl_PL', 0, '0 miesięcy'],
454
            ['pl_PL', 1, '1 miesiąc'],
455
            ['pl_PL', 2, '2 miesiące'],
456
            ['pl_PL', 5, '5 miesięcy'],
457
            ['pl_PL', 10, '10 miesięcy'],
458
            // Japanese - 1 plural form
459
            ['ja_JP', 0, '0日'],
460
            ['ja_JP', 1, '1日'],
461
            ['ja_JP', 2, '2日'],
462
            ['ja_JP', 5, '5日'],
463
            ['ja_JP', 10, '10日'],
464
        ];
465
    }
466
467
    /**
468
     * @dataProvider pluralisationDataProvider()
469
     * @param string $locale
470
     * @param int $count
471
     * @param string $expected
472
     */
473
    public function testPluralisation($locale, $count, $expected)
474
    {
475
        i18n::set_locale($locale);
476
        $this->assertEquals(
477
            $expected,
478
            _t('Month.PLURALS', 'A month|{count} months', ['count' => $count]),
479
            "Plural form in locale $locale with count $count should be $expected"
480
        );
481
    }
482
483
    public function testGetLanguageName()
484
    {
485
        i18n::config()->update(
486
            'common_languages',
487
            array('de_CGN' => array('name' => 'German (Cologne)', 'native' => 'K&ouml;lsch'))
488
        );
489
        $this->assertEquals('German', i18n::getData()->languageName('de_CGN'));
490
        $this->assertEquals('Deutsch', i18n::with_locale('de_CGN', function () {
491
            return i18n::getData()->languageName('de_CGN');
492
        }));
493
    }
494
}
495