1 | <?php |
||
2 | |||
3 | namespace Koded\I18n; |
||
4 | |||
5 | use Koded\Stdlib\Configuration; |
||
6 | use Throwable; |
||
7 | use function array_key_exists; |
||
8 | use function error_log; |
||
9 | use function getcwd; |
||
10 | use function rtrim; |
||
11 | use function str_replace; |
||
12 | use function strtolower; |
||
13 | |||
14 | abstract class I18nCatalog |
||
15 | { |
||
16 | protected string $locale; |
||
17 | protected string $directory; |
||
18 | protected I18nFormatter $formatter; |
||
19 | |||
20 | 26 | private function __construct( |
|
21 | I18nFormatter $formatter, |
||
22 | string $directory, |
||
23 | string $locale) |
||
24 | { |
||
25 | 26 | $this->formatter = $formatter; |
|
26 | 26 | $this->directory = rtrim($directory, '/'); |
|
27 | 26 | $this->locale = $this->initialize($locale); |
|
28 | } |
||
29 | |||
30 | 26 | public static function new(Configuration $conf): I18nCatalog |
|
31 | { |
||
32 | 26 | $catalog = $conf->get('translation.catalog', ArrayCatalog::class); |
|
33 | 26 | $formatter = $conf->get('translation.formatter', DefaultFormatter::class); |
|
34 | 26 | $instance = new $catalog( |
|
35 | 26 | new $formatter, |
|
36 | 26 | $directory = $conf->get('translation.dir', getcwd() . '/locale'), |
|
37 | 26 | $locale = self::normalizeLocale($conf->get('translation.locale', I18n::DEFAULT_LOCALE)) |
|
38 | 26 | ); |
|
39 | 26 | if ($instance->supports($locale)) { |
|
40 | 17 | return $instance; |
|
41 | } |
||
42 | 16 | if ($catalog !== ArrayCatalog::class) { |
|
43 | 2 | error_log(" > ($locale) gettext not supported, trying ArrayCatalog ..."); |
|
44 | 2 | $conf->set('translation.catalog', ArrayCatalog::class); |
|
45 | 2 | return static::new($conf); |
|
46 | } |
||
47 | // Last resort, passthru |
||
48 | 15 | return new NoCatalog(new $formatter, $directory, $locale); |
|
49 | } |
||
50 | |||
51 | 26 | public static function normalizeLocale(string $locale): string |
|
52 | { |
||
53 | 26 | $locale = explode('_', str_replace('.', '_', $locale)); |
|
54 | 26 | return "$locale[0]_$locale[1]"; |
|
55 | } |
||
56 | |||
57 | 15 | public function translate( |
|
58 | string $domain, |
||
59 | string $key, |
||
60 | array $arguments = [], |
||
61 | int $n = 0): string |
||
62 | { |
||
63 | 15 | return $this->formatter->format( |
|
64 | 15 | $this->message($domain, $key, $n), |
|
65 | 15 | $arguments |
|
66 | 15 | ); |
|
67 | } |
||
68 | |||
69 | 26 | public function locale(): string |
|
70 | { |
||
71 | 26 | return $this->locale; |
|
72 | } |
||
73 | |||
74 | 25 | public function directory(): string |
|
75 | { |
||
76 | 25 | return $this->directory; |
|
77 | } |
||
78 | |||
79 | 26 | public function formatter(): I18nFormatter |
|
80 | { |
||
81 | 26 | return $this->formatter; |
|
82 | } |
||
83 | |||
84 | 2 | public function urlized(): string |
|
85 | { |
||
86 | 2 | return str_replace('_', '-', strtolower($this->locale)); |
|
87 | } |
||
88 | |||
89 | /** |
||
90 | * Returns the localized display name for catalog's language. |
||
91 | * Defaults to locale value if language value is not available. |
||
92 | * |
||
93 | * @return string |
||
94 | */ |
||
95 | abstract public function language(): string; |
||
96 | |||
97 | /** |
||
98 | * Translates the message. |
||
99 | * |
||
100 | * @param string $domain |
||
101 | * @param string $string |
||
102 | * @param int $n |
||
103 | * @return string |
||
104 | */ |
||
105 | abstract protected function message( |
||
106 | string $domain, |
||
107 | string $string, |
||
108 | int $n): string; |
||
109 | |||
110 | /** |
||
111 | * Checks if the locale is supported for this catalog, |
||
112 | * or other specific requirements. |
||
113 | * |
||
114 | * @param string $locale |
||
115 | * @return bool |
||
116 | */ |
||
117 | abstract protected function supports(string $locale): bool; |
||
118 | |||
119 | /** |
||
120 | * Initialize the catalog object. This method is |
||
121 | * called before supports(). |
||
122 | * |
||
123 | * @param string $locale Desired locale to be initialized |
||
124 | * @return string|false Returns the set locale, |
||
125 | * or FALSE if initialization fails. |
||
126 | */ |
||
127 | abstract protected function initialize(string $locale): string|false; |
||
128 | } |
||
129 | |||
130 | class NoCatalog extends I18nCatalog |
||
131 | { |
||
132 | 2 | public function language(): string |
|
133 | { |
||
134 | 2 | return $this->locale ?: ''; |
|
135 | } |
||
136 | |||
137 | 10 | protected function message(string $domain, string $string, int $n): string |
|
138 | { |
||
139 | 10 | return $string; |
|
140 | } |
||
141 | |||
142 | // @codeCoverageIgnoreStart |
||
143 | protected function supports(string $locale): bool |
||
144 | { |
||
145 | return true; |
||
146 | } |
||
147 | // @codeCoverageIgnoreEnd |
||
148 | |||
149 | 16 | protected function initialize(string $locale): string|false |
|
150 | { |
||
151 | 16 | return $locale; |
|
152 | } |
||
153 | } |
||
154 | |||
155 | class ArrayCatalog extends I18nCatalog |
||
156 | { |
||
157 | private array $data = []; |
||
158 | |||
159 | 2 | public function language(): string |
|
160 | { |
||
161 | 2 | return $this->data['language'] ?? $this->locale; |
|
162 | } |
||
163 | |||
164 | 4 | protected function message(string $domain, string $string, int $n): string |
|
165 | { |
||
166 | 4 | return $this->data[$domain][$string] ?? $string; |
|
167 | } |
||
168 | |||
169 | 24 | protected function supports(string $locale): bool |
|
170 | { |
||
171 | 24 | return $this->locale === $locale; |
|
172 | } |
||
173 | |||
174 | 24 | protected function initialize(string $locale): string|false |
|
175 | { |
||
176 | try { |
||
177 | /** @noinspection PhpIncludeInspection */ |
||
178 | 24 | $this->data = require($catalog = "$this->directory/$locale.php"); |
|
179 | 14 | if (false === array_key_exists('messages', $this->data)) { |
|
180 | 1 | error_log("ERROR : i18n catalog $catalog is missing the messages array"); |
|
181 | 1 | return false; |
|
182 | } |
||
183 | 14 | return $locale; |
|
184 | 14 | } catch (Throwable $e) { |
|
185 | 14 | error_log($e->getMessage()); |
|
186 | 14 | return false; |
|
187 | } |
||
188 | } |
||
189 | } |
||
190 |