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