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