1 | <?php declare(strict_types=1); |
||
2 | |||
3 | namespace Koded\I18n; |
||
4 | |||
5 | use Koded\Stdlib\Config; |
||
6 | use Throwable; |
||
7 | use function error_log; |
||
8 | use function ini_set; |
||
9 | use function strtr; |
||
10 | use function substr_count; |
||
11 | use function vsprintf; |
||
12 | |||
13 | interface I18nFormatter |
||
14 | { |
||
15 | /** |
||
16 | * Message formatter for argument replacement in the message. |
||
17 | * |
||
18 | * @param string $string |
||
19 | * @param array $arguments |
||
20 | * @return string The message with applied arguments (if any) |
||
21 | */ |
||
22 | public function format(string $string, array $arguments): string; |
||
23 | } |
||
24 | |||
25 | final class StrtrFormatter implements I18nFormatter |
||
26 | { |
||
27 | 1 | public function format(string $string, array $arguments): string |
|
28 | { |
||
29 | 1 | return $arguments ? strtr($string, $arguments) : $string; |
|
30 | } |
||
31 | } |
||
32 | |||
33 | final class DefaultFormatter implements I18nFormatter |
||
34 | { |
||
35 | 8 | public function format(string $string, array $arguments): string |
|
36 | { |
||
37 | 8 | return $arguments ? vsprintf($string, $arguments) : $string; |
|
38 | } |
||
39 | } |
||
40 | |||
41 | class I18n |
||
42 | { |
||
43 | public const DEFAULT_LOCALE = 'en_US'; |
||
44 | |||
45 | /* |
||
46 | * Default configuration parameters for all catalogs. |
||
47 | * Values are taken by the explicitly set default locale, |
||
48 | * or the first loaded catalog instance. |
||
49 | */ |
||
50 | private static ?string $catalog = null; |
||
51 | private static ?string $formatter = null; |
||
52 | private static ?string $directory = null; |
||
53 | private static ?string $locale = null; |
||
54 | |||
55 | /** @var array<string, I18nCatalog> */ |
||
56 | private static array $catalogs = []; |
||
57 | |||
58 | // @codeCoverageIgnoreStart |
||
59 | private function __construct() {} |
||
60 | // @codeCoverageIgnoreEnd |
||
61 | |||
62 | 9 | public static function translate( |
|
63 | string $string, |
||
64 | array $arguments = [], |
||
65 | string $locale = null): string |
||
66 | { |
||
67 | try { |
||
68 | 9 | return static::$catalogs[$locale]->translate('messages', $string, $arguments); |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
69 | 9 | } catch (Throwable) { |
|
70 | 9 | static::registerCatalog($locale ??= static::locale()); |
|
71 | 9 | return static::$catalogs[$locale]->translate('messages', $string, $arguments); |
|
72 | } |
||
73 | } |
||
74 | |||
75 | 6 | public static function locale(): string |
|
76 | { |
||
77 | 6 | return static::$locale ??= static::normalizeLocale(\Locale::getDefault()); |
|
0 ignored issues
–
show
|
|||
78 | } |
||
79 | |||
80 | /** |
||
81 | * @return array{string, I18nCatalog} |
||
1 ignored issue
–
show
|
|||
82 | */ |
||
83 | 9 | public static function catalogs(): array |
|
84 | { |
||
85 | 9 | return static::$catalogs; |
|
0 ignored issues
–
show
|
|||
86 | } |
||
87 | |||
88 | /** |
||
89 | * @return array{locale:string, catalogs:<string, array{class: string, formatter:string, dir:string, locale:string}>} |
||
1 ignored issue
–
show
|
|||
90 | */ |
||
91 | 1 | public static function info(): array |
|
92 | { |
||
93 | 1 | $catalogs = []; |
|
94 | 1 | foreach (static::$catalogs as $locale => $instance) { |
|
0 ignored issues
–
show
|
|||
95 | 1 | $catalogs[$locale] = [ |
|
96 | 1 | 'class' => $instance::class, |
|
97 | 1 | 'formatter' => $instance->formatter()::class, |
|
98 | 1 | 'dir' => $instance->directory(), |
|
99 | 1 | 'locale' => $instance->locale(), |
|
100 | ]; |
||
101 | } |
||
102 | return [ |
||
103 | 1 | 'locale' => static::$locale, |
|
0 ignored issues
–
show
|
|||
104 | 1 | 'catalogs' => $catalogs, |
|
105 | ]; |
||
106 | } |
||
107 | |||
108 | 15 | public static function register( |
|
109 | I18nCatalog $catalog, |
||
110 | bool $asDefault = false): void |
||
111 | { |
||
112 | 15 | $locale = $catalog->locale(); |
|
113 | 15 | if ($asDefault || empty(static::$catalogs)) { |
|
0 ignored issues
–
show
|
|||
114 | 15 | static::setDefaultLocale($locale); |
|
115 | 15 | static::$directory = $catalog->directory(); |
|
0 ignored issues
–
show
|
|||
116 | 15 | static::$formatter = $catalog->formatter()::class; |
|
0 ignored issues
–
show
|
|||
117 | 15 | static::$catalog = $catalog::class; |
|
0 ignored issues
–
show
|
|||
118 | } |
||
119 | 15 | static::$catalogs[$locale] = $catalog; |
|
120 | 15 | } |
|
121 | |||
122 | 15 | public static function flush(): void |
|
123 | { |
||
124 | 15 | static::$catalogs = []; |
|
0 ignored issues
–
show
|
|||
125 | 15 | static::$directory = null; |
|
0 ignored issues
–
show
|
|||
126 | 15 | static::$formatter = null; |
|
0 ignored issues
–
show
|
|||
127 | 15 | static::$catalog = null; |
|
0 ignored issues
–
show
|
|||
128 | 15 | static::$locale = null; |
|
0 ignored issues
–
show
|
|||
129 | 15 | ini_set('intl.default_locale', ''); |
|
130 | 15 | \Locale::setDefault(''); |
|
131 | 15 | } |
|
132 | |||
133 | 9 | private static function registerCatalog(string $locale): void |
|
134 | { |
||
135 | 9 | if (isset(static::$catalogs[$locale])) { |
|
0 ignored issues
–
show
|
|||
136 | 5 | return; |
|
137 | } |
||
138 | 5 | static::$catalogs[$locale] = I18nCatalog::new((new Config) |
|
139 | 5 | ->set('translation.locale', $locale) |
|
140 | 5 | ->set('translation.dir', static::$directory) |
|
0 ignored issues
–
show
|
|||
141 | 5 | ->set('translation.formatter', static::$formatter) |
|
0 ignored issues
–
show
|
|||
142 | 5 | ->set('translation.catalog', static::$catalog) |
|
0 ignored issues
–
show
|
|||
143 | ); |
||
144 | 5 | } |
|
145 | |||
146 | 15 | private static function setDefaultLocale(string $locale): void |
|
147 | { |
||
148 | 15 | static::$locale = $locale; |
|
0 ignored issues
–
show
|
|||
149 | 15 | ini_set('intl.default_locale', $locale); |
|
150 | 15 | \Locale::setDefault($locale); |
|
151 | 15 | } |
|
152 | |||
153 | private static function normalizeLocale(string $locale): string |
||
154 | { |
||
155 | if (substr_count($locale, '_') > 1) { |
||
156 | $locale = explode('_', $locale); |
||
157 | $locale = "$locale[0]_$locale[1]"; |
||
158 | } |
||
159 | return $locale; |
||
160 | } |
||
161 | } |
||
162 |