1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 1.2.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\I18n;
16:
17: use Aura\Intl\FormatterLocator;
18: use Aura\Intl\PackageLocator;
19: use Cake\Cache\Cache;
20: use Cake\I18n\Formatter\IcuFormatter;
21: use Cake\I18n\Formatter\SprintfFormatter;
22: use Locale;
23:
24: /**
25: * I18n handles translation of Text and time format strings.
26: */
27: class I18n
28: {
29: /**
30: * Default locale
31: *
32: * @var string
33: */
34: const DEFAULT_LOCALE = 'en_US';
35:
36: /**
37: * The translators collection
38: *
39: * @var \Cake\I18n\TranslatorRegistry|null
40: */
41: protected static $_collection;
42:
43: /**
44: * The environment default locale
45: *
46: * @var string
47: */
48: protected static $_defaultLocale;
49:
50: /**
51: * Returns the translators collection instance. It can be used
52: * for getting specific translators based of their name and locale
53: * or to configure some aspect of future translations that are not yet constructed.
54: *
55: * @return \Cake\I18n\TranslatorRegistry The translators collection.
56: */
57: public static function translators()
58: {
59: if (static::$_collection !== null) {
60: return static::$_collection;
61: }
62:
63: static::$_collection = new TranslatorRegistry(
64: new PackageLocator(),
65: new FormatterLocator([
66: 'sprintf' => function () {
67: return new SprintfFormatter();
68: },
69: 'default' => function () {
70: return new IcuFormatter();
71: },
72: ]),
73: new TranslatorFactory(),
74: static::getLocale()
75: );
76:
77: if (class_exists('Cake\Cache\Cache')) {
78: static::$_collection->setCacher(Cache::engine('_cake_core_'));
79: }
80:
81: return static::$_collection;
82: }
83:
84: /**
85: * Returns an instance of a translator that was configured for the name and passed
86: * locale. If no locale is passed then it takes the value returned by the `getLocale()` method.
87: *
88: * This method can be used to configure future translators, this is achieved by passing a callable
89: * as the last argument of this function.
90: *
91: * ### Example:
92: *
93: * ```
94: * I18n::setTranslator('default', function () {
95: * $package = new \Aura\Intl\Package();
96: * $package->setMessages([
97: * 'Cake' => 'Gâteau'
98: * ]);
99: * return $package;
100: * }, 'fr_FR');
101: *
102: * $translator = I18n::translator('default', 'fr_FR');
103: * echo $translator->translate('Cake');
104: * ```
105: *
106: * You can also use the `Cake\I18n\MessagesFileLoader` class to load a specific
107: * file from a folder. For example for loading a `my_translations.po` file from
108: * the `src/Locale/custom` folder, you would do:
109: *
110: * ```
111: * I18n::translator(
112: * 'default',
113: * 'fr_FR',
114: * new MessagesFileLoader('my_translations', 'custom', 'po');
115: * );
116: * ```
117: *
118: * @deprecated 3.5 Use getTranslator() and setTranslator()
119: * @param string $name The domain of the translation messages.
120: * @param string|null $locale The locale for the translator.
121: * @param callable|null $loader A callback function or callable class responsible for
122: * constructing a translations package instance.
123: * @return \Aura\Intl\TranslatorInterface|null The configured translator.
124: * @throws \Aura\Intl\Exception
125: */
126: public static function translator($name = 'default', $locale = null, callable $loader = null)
127: {
128: deprecationWarning(
129: 'I18n::translator() is deprecated. ' .
130: 'Use I18n::setTranslator()/getTranslator() instead.'
131: );
132: if ($loader !== null) {
133: static::setTranslator($name, $loader, $locale);
134:
135: return null;
136: }
137:
138: return self::getTranslator($name, $locale);
139: }
140:
141: /**
142: * Sets a translator.
143: *
144: * Configures future translators, this is achieved by passing a callable
145: * as the last argument of this function.
146: *
147: * ### Example:
148: *
149: * ```
150: * I18n::setTranslator('default', function () {
151: * $package = new \Aura\Intl\Package();
152: * $package->setMessages([
153: * 'Cake' => 'Gâteau'
154: * ]);
155: * return $package;
156: * }, 'fr_FR');
157: *
158: * $translator = I18n::getTranslator('default', 'fr_FR');
159: * echo $translator->translate('Cake');
160: * ```
161: *
162: * You can also use the `Cake\I18n\MessagesFileLoader` class to load a specific
163: * file from a folder. For example for loading a `my_translations.po` file from
164: * the `src/Locale/custom` folder, you would do:
165: *
166: * ```
167: * I18n::setTranslator(
168: * 'default',
169: * new MessagesFileLoader('my_translations', 'custom', 'po'),
170: * 'fr_FR'
171: * );
172: * ```
173: *
174: * @param string $name The domain of the translation messages.
175: * @param callable $loader A callback function or callable class responsible for
176: * constructing a translations package instance.
177: * @param string|null $locale The locale for the translator.
178: * @return void
179: */
180: public static function setTranslator($name, callable $loader, $locale = null)
181: {
182: $locale = $locale ?: static::getLocale();
183:
184: $translators = static::translators();
185: $loader = $translators->setLoaderFallback($name, $loader);
186: $packages = $translators->getPackages();
187: $packages->set($name, $locale, $loader);
188: }
189:
190: /**
191: * Returns an instance of a translator that was configured for the name and locale.
192: *
193: * If no locale is passed then it takes the value returned by the `getLocale()` method.
194: *
195: * @param string $name The domain of the translation messages.
196: * @param string|null $locale The locale for the translator.
197: * @return \Aura\Intl\TranslatorInterface The configured translator.
198: * @throws \Aura\Intl\Exception
199: */
200: public static function getTranslator($name = 'default', $locale = null)
201: {
202: $translators = static::translators();
203:
204: if ($locale) {
205: $currentLocale = $translators->getLocale();
206: $translators->setLocale($locale);
207: }
208:
209: $translator = $translators->get($name);
210:
211: if (isset($currentLocale)) {
212: $translators->setLocale($currentLocale);
213: }
214:
215: return $translator;
216: }
217:
218: /**
219: * Registers a callable object that can be used for creating new translator
220: * instances for the same translations domain. Loaders will be invoked whenever
221: * a translator object is requested for a domain that has not been configured or
222: * loaded already.
223: *
224: * Registering loaders is useful when you need to lazily use translations in multiple
225: * different locales for the same domain, and don't want to use the built-in
226: * translation service based of `gettext` files.
227: *
228: * Loader objects will receive two arguments: The domain name that needs to be
229: * built, and the locale that is requested. These objects can assemble the messages
230: * from any source, but must return an `Aura\Intl\Package` object.
231: *
232: * ### Example:
233: *
234: * ```
235: * use Cake\I18n\MessagesFileLoader;
236: * I18n::config('my_domain', function ($name, $locale) {
237: * // Load src/Locale/$locale/filename.po
238: * $fileLoader = new MessagesFileLoader('filename', $locale, 'po');
239: * return $fileLoader();
240: * });
241: * ```
242: *
243: * You can also assemble the package object yourself:
244: *
245: * ```
246: * use Aura\Intl\Package;
247: * I18n::config('my_domain', function ($name, $locale) {
248: * $package = new Package('default');
249: * $messages = (...); // Fetch messages for locale from external service.
250: * $package->setMessages($message);
251: * $package->setFallback('default');
252: * return $package;
253: * });
254: * ```
255: *
256: * @param string $name The name of the translator to create a loader for
257: * @param callable $loader A callable object that should return a Package
258: * instance to be used for assembling a new translator.
259: * @return void
260: */
261: public static function config($name, callable $loader)
262: {
263: static::translators()->registerLoader($name, $loader);
264: }
265:
266: /**
267: * Sets the default locale to use for future translator instances.
268: * This also affects the `intl.default_locale` PHP setting.
269: *
270: * When called with no arguments it will return the currently configure
271: * locale as stored in the `intl.default_locale` PHP setting.
272: *
273: * @deprecated 3.5 Use setLocale() and getLocale().
274: * @param string|null $locale The name of the locale to set as default.
275: * @return string|null The name of the default locale.
276: */
277: public static function locale($locale = null)
278: {
279: deprecationWarning(
280: 'I18n::locale() is deprecated. ' .
281: 'Use I18n::setLocale()/getLocale() instead.'
282: );
283: if (!empty($locale)) {
284: static::setLocale($locale);
285:
286: return null;
287: }
288:
289: return self::getLocale();
290: }
291:
292: /**
293: * Sets the default locale to use for future translator instances.
294: * This also affects the `intl.default_locale` PHP setting.
295: *
296: * @param string $locale The name of the locale to set as default.
297: * @return void
298: */
299: public static function setLocale($locale)
300: {
301: static::getDefaultLocale();
302: Locale::setDefault($locale);
303: if (isset(static::$_collection)) {
304: static::translators()->setLocale($locale);
305: }
306: }
307:
308: /**
309: * Will return the currently configure locale as stored in the
310: * `intl.default_locale` PHP setting.
311: *
312: * @return string The name of the default locale.
313: */
314: public static function getLocale()
315: {
316: static::getDefaultLocale();
317: $current = Locale::getDefault();
318: if ($current === '') {
319: $current = static::DEFAULT_LOCALE;
320: Locale::setDefault($current);
321: }
322:
323: return $current;
324: }
325:
326: /**
327: * This returns the default locale before any modifications, i.e.
328: * the value as stored in the `intl.default_locale` PHP setting before
329: * any manipulation by this class.
330: *
331: * @deprecated 3.5 Use getDefaultLocale()
332: * @return string
333: */
334: public static function defaultLocale()
335: {
336: deprecationWarning('I18n::defaultLocale() is deprecated. Use I18n::getDefaultLocale() instead.');
337:
338: return static::getDefaultLocale();
339: }
340:
341: /**
342: * Returns the default locale.
343: *
344: * This returns the default locale before any modifications, i.e.
345: * the value as stored in the `intl.default_locale` PHP setting before
346: * any manipulation by this class.
347: *
348: * @return string
349: */
350: public static function getDefaultLocale()
351: {
352: if (static::$_defaultLocale === null) {
353: static::$_defaultLocale = Locale::getDefault() ?: static::DEFAULT_LOCALE;
354: }
355:
356: return static::$_defaultLocale;
357: }
358:
359: /**
360: * Sets the name of the default messages formatter to use for future
361: * translator instances.
362: *
363: * By default the `default` and `sprintf` formatters are available.
364: *
365: * If called with no arguments, it will return the currently configured value.
366: *
367: * @deprecated 3.5 Use getDefaultFormatter() and setDefaultFormatter().
368: * @param string|null $name The name of the formatter to use.
369: * @return string The name of the formatter.
370: */
371: public static function defaultFormatter($name = null)
372: {
373: deprecationWarning(
374: 'I18n::defaultFormatter() is deprecated. ' .
375: 'Use I18n::setDefaultFormatter()/getDefaultFormatter() instead.'
376: );
377:
378: return static::translators()->defaultFormatter($name);
379: }
380:
381: /**
382: * Returns the currently configured default formatter.
383: *
384: * @return string The name of the formatter.
385: */
386: public static function getDefaultFormatter()
387: {
388: return static::translators()->defaultFormatter();
389: }
390:
391: /**
392: * Sets the name of the default messages formatter to use for future
393: * translator instances. By default the `default` and `sprintf` formatters
394: * are available.
395: *
396: * @param string $name The name of the formatter to use.
397: * @return void
398: */
399: public static function setDefaultFormatter($name)
400: {
401: static::translators()->defaultFormatter($name);
402: }
403:
404: /**
405: * Set if the domain fallback is used.
406: *
407: * @param bool $enable flag to enable or disable fallback
408: * @return void
409: */
410: public static function useFallback($enable = true)
411: {
412: static::translators()->useFallback($enable);
413: }
414:
415: /**
416: * Destroys all translator instances and creates a new empty translations
417: * collection.
418: *
419: * @return void
420: */
421: public static function clear()
422: {
423: static::$_collection = null;
424: }
425: }
426: