-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathMessagesFileLoader.php
More file actions
213 lines (192 loc) · 6.69 KB
/
MessagesFileLoader.php
File metadata and controls
213 lines (192 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\I18n;
use Cake\Core\App;
use Cake\Core\Exception\CakeException;
use Cake\Core\Plugin;
use Cake\Utility\Inflector;
use Locale;
use function Cake\Core\pluginSplit;
/**
* A generic translations package factory that will load translations files
* based on the file extension and the package name.
*
* This class is a callable, so it can be used as a package loader argument.
*/
class MessagesFileLoader
{
/**
* The package (domain) name.
*
* @var string
*/
protected string $_name;
/**
* The package (domain) plugin
*
* @var string|null
*/
protected ?string $_plugin = null;
/**
* The locale to load for the given package.
*
* @var string
*/
protected string $_locale;
/**
* The extension name.
*
* @var string
*/
protected string $_extension;
/**
* Creates a translation file loader. The file to be loaded corresponds to
* the following rules:
*
* - The locale is a folder under the `resources/locales/` directory, a fallback will be
* used if the folder is not found.
* - The $name corresponds to the file name to load
* - If there is a loaded plugin with the underscored version of $name, the
* translation file will be loaded from such plugin.
*
* ### Examples:
*
* Load and parse resources/locales/fr/validation.po
*
* ```
* $loader = new MessagesFileLoader('validation', 'fr_FR', 'po');
* $package = $loader();
* ```
*
* Load and parse resources/locales/fr_FR/validation.mo
*
* ```
* $loader = new MessagesFileLoader('validation', 'fr_FR', 'mo');
* $package = $loader();
* ```
*
* Load the plugins/MyPlugin/resources/locales/fr/my_plugin.po file:
*
* ```
* $loader = new MessagesFileLoader('my_plugin', 'fr_FR', 'mo');
* $package = $loader();
*
* Vendor prefixed plugins are expected to use `my_prefix_my_plugin` syntax.
* ```
*
* @param string $name The name (domain) of the translations package.
* @param string $locale The locale to load, this will be mapped to a folder
* in the system.
* @param string $extension The file extension to use. This will also be mapped
* to a messages parser class.
*/
public function __construct(string $name, string $locale, string $extension = 'po')
{
$this->_name = $name;
// If space is not added after slash, the character after it remains lowercased
$pluginName = Inflector::camelize(str_replace('/', '/ ', $this->_name));
if (strpos($this->_name, '.')) {
[$this->_plugin, $this->_name] = pluginSplit($pluginName);
} elseif (Plugin::isLoaded($pluginName)) {
$this->_plugin = $pluginName;
}
$this->_locale = $locale;
$this->_extension = $extension;
}
/**
* Loads the translation file and parses it. Returns an instance of a translations
* package containing the messages loaded from the file.
*
* @return \Cake\I18n\Package|false
* @throws \Cake\Core\Exception\CakeException if no file parser class could be found for the specified
* file extension.
*/
public function __invoke(): Package|false
{
$folders = $this->translationsFolders();
$file = $this->translationFile($folders, $this->_name, $this->_extension);
if (!$file) {
return false;
}
$name = ucfirst($this->_extension);
$class = App::className($name, 'I18n\Parser', 'FileParser');
if (!$class) {
throw new CakeException(sprintf('Could not find class `%s`.', "{$name}FileParser"));
}
/** @var \Cake\I18n\Parser\MoFileParser|\Cake\I18n\Parser\PoFileParser $object */
$object = new $class();
$messages = $object->parse($file);
$package = new Package('default');
$package->setMessages($messages);
return $package;
}
/**
* Returns the folders where the file should be looked for according to the locale
* and package name.
*
* @return array<string> The list of folders where the translation file should be looked for
*/
public function translationsFolders(): array
{
$locale = Locale::parseLocale($this->_locale) + ['region' => null];
$folders = [
$locale['language'],
// gettext compatible paths, see https://www.php.net/manual/en/function.gettext.php
$locale['language'] . DIRECTORY_SEPARATOR . 'LC_MESSAGES',
];
if ($locale['region']) {
$languageRegion = implode('_', [$locale['language'], $locale['region']]);
$folders[] = $languageRegion;
// gettext compatible paths, see https://www.php.net/manual/en/function.gettext.php
$folders[] = $languageRegion . DIRECTORY_SEPARATOR . 'LC_MESSAGES';
}
$searchPaths = [];
$localePaths = App::path('locales');
if (!$localePaths && defined('ROOT')) {
$localePaths[] = ROOT . DIRECTORY_SEPARATOR
. 'resources' . DIRECTORY_SEPARATOR
. 'locales' . DIRECTORY_SEPARATOR;
}
if ($this->_plugin && Plugin::isLoaded($this->_plugin)) {
$localePaths[] = App::path('locales', $this->_plugin)[0];
}
foreach ($localePaths as $path) {
foreach ($folders as $folder) {
$searchPaths[] = $path . $folder . DIRECTORY_SEPARATOR;
}
}
return $searchPaths;
}
/**
* @param array<string> $folders Folders
* @param string $name File name
* @param string $ext File extension
* @return string|null File if found
*/
protected function translationFile(array $folders, string $name, string $ext): ?string
{
$file = null;
$name = str_replace('/', '_', $name);
foreach ($folders as $folder) {
$path = "{$folder}{$name}.{$ext}";
if (is_file($path)) {
$file = $path;
break;
}
}
return $file;
}
}