1 | <?php |
||
2 | declare(strict_types=1); |
||
3 | |||
4 | namespace Nexendrie\Translation\Bridges\NetteDI; |
||
5 | |||
6 | use Nette\DI\CompilerExtension; |
||
7 | use Nette\DI\Definitions\FactoryDefinition; |
||
8 | use Nette\PhpGenerator\ClassType; |
||
9 | use Nexendrie\Translation\ILocaleResolver; |
||
10 | use Nexendrie\Translation\Bridges\NetteApplication\IAppRequestAwareLocaleResolver; |
||
11 | use Nexendrie\Translation\Resolvers\EnvironmentLocaleResolver; |
||
12 | use Nexendrie\Translation\Resolvers\ManualLocaleResolver; |
||
13 | use Nexendrie\Translation\Resolvers\FallbackLocaleResolver; |
||
14 | use Nexendrie\Translation\Resolvers\ChainLocaleResolver; |
||
15 | use Nexendrie\Translation\Resolvers\SessionLocaleResolver; |
||
16 | use Nexendrie\Translation\Resolvers\HeaderLocaleResolver; |
||
17 | use Nexendrie\Translation\Bridges\NetteApplication\ParamLocaleResolver; |
||
18 | use Nexendrie\Translation\Translator; |
||
19 | use Nexendrie\Translation\ILoader; |
||
20 | use Nexendrie\Translation\IFileLoader; |
||
21 | use Nexendrie\Translation\Loaders\NeonLoader; |
||
22 | use Nexendrie\Translation\Loaders\IniLoader; |
||
23 | use Nexendrie\Translation\Loaders\JsonLoader; |
||
24 | use Nexendrie\Translation\Loaders\YamlLoader; |
||
25 | use Nexendrie\Translation\Loaders\PhpLoader; |
||
26 | use Nexendrie\Translation\Loaders\MessagesCatalogue; |
||
27 | use Nexendrie\Translation\InvalidLocaleResolverException; |
||
28 | use Nexendrie\Translation\InvalidFolderException; |
||
29 | use Nexendrie\Translation\InvalidLoaderException; |
||
30 | use Nexendrie\Translation\Bridges\Tracy\TranslationPanel; |
||
31 | use Nexendrie\Translation\CatalogueCompiler; |
||
32 | use Nette\Utils\Arrays; |
||
33 | use Nette\Application\Application; |
||
34 | use Nette\Bridges\ApplicationLatte\ILatteFactory; |
||
35 | use Nexendrie\Translation\ILoaderAwareLocaleResolver; |
||
36 | use Nexendrie\Translation\IMessageSelector; |
||
37 | use Nexendrie\Translation\MessageSelector; |
||
38 | use Nexendrie\Translation\InvalidMessageSelectorException; |
||
39 | use Nette\Schema\Expect; |
||
40 | use Nette\DI\Definitions\ServiceDefinition; |
||
41 | use Nette\DI\Helpers; |
||
42 | |||
43 | /** |
||
44 | * TranslationExtension for Nette DI Container |
||
45 | * |
||
46 | * @author Jakub Konečný |
||
47 | * @method \stdClass getConfig() |
||
48 | */ |
||
49 | final class TranslationExtension extends CompilerExtension { |
||
50 | /** @internal */ |
||
51 | public const SERVICE_TRANSLATOR = "translator"; |
||
52 | /** @internal */ |
||
53 | public const SERVICE_LOADER = "loader"; |
||
54 | /** @internal */ |
||
55 | public const SERVICE_LOCALE_RESOLVER = "localeResolver"; |
||
56 | /** @internal */ |
||
57 | public const SERVICE_PANEL = "panel"; |
||
58 | /** @internal */ |
||
59 | public const SERVICE_CATALOGUE_COMPILER = "catalogueCompiler"; |
||
60 | /** @internal */ |
||
61 | public const SERVICE_ORIGINAL_LOADER = "originalLoader"; |
||
62 | /** @internal */ |
||
63 | public const SERVICE_MESSAGE_SELECTOR = "messageSelector"; |
||
64 | |||
65 | private array $resolvers = [ |
||
66 | "environment" => EnvironmentLocaleResolver::class, |
||
67 | "manual" => ManualLocaleResolver::class, |
||
68 | "fallback" => FallbackLocaleResolver::class, |
||
69 | "session" => SessionLocaleResolver::class, |
||
70 | "header" => HeaderLocaleResolver::class, |
||
71 | "param" => ParamLocaleResolver::class |
||
72 | ]; |
||
73 | |||
74 | private array $loaders = [ |
||
75 | "neon" => NeonLoader::class, |
||
76 | "ini" => IniLoader::class, |
||
77 | "json" => JsonLoader::class, |
||
78 | "yaml" => YamlLoader::class, |
||
79 | "php" => PhpLoader::class, |
||
80 | "catalogue" => MessagesCatalogue::class, |
||
81 | ]; |
||
82 | |||
83 | public function getConfigSchema(): \Nette\Schema\Schema { |
||
84 | 1 | $params = $this->getContainerBuilder()->parameters; |
|
85 | 1 | return Expect::structure([ |
|
86 | 1 | "localeResolver" => Expect::anyOf(Expect::string(), Expect::arrayOf("string"))->default(["param", "session", "header", ]), |
|
87 | 1 | "default" => Expect::string("en"), |
|
88 | 1 | "debugger" => Expect::bool(Helpers::expand("%debugMode%", $params)), |
|
89 | 1 | "loader" => Expect::structure([ |
|
90 | 1 | "name" => Expect::string("neon"), |
|
91 | 1 | "folders" => Expect::array()->default([Helpers::expand("%appDir%/lang", $params)]), |
|
92 | 1 | ])->castTo("array"), |
|
93 | 1 | "onUntranslated" => Expect::array()->default([ |
|
94 | 1 | ["@" . $this->prefix(static::SERVICE_TRANSLATOR), "logUntranslatedMessage"] |
|
95 | ]), |
||
96 | 1 | "compiler" => Expect::structure([ |
|
97 | 1 | "enabled" => Expect::bool(false), |
|
98 | 1 | "languages" => Expect::arrayOf("string")->default([]), |
|
99 | 1 | ])->castTo("array"), |
|
100 | 1 | "messageSelector" => Expect::type("class")->default(MessageSelector::class), |
|
101 | ]); |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * @return string[] |
||
106 | * @throws InvalidLocaleResolverException |
||
107 | */ |
||
108 | protected function resolveResolverClass(): array { |
||
109 | 1 | $config = $this->getConfig(); |
|
110 | 1 | $return = []; |
|
111 | 1 | $resolvers = $config->localeResolver; |
|
112 | 1 | if(!is_array($resolvers)) { |
|
113 | 1 | $resolvers = [$resolvers]; |
|
114 | } |
||
115 | 1 | foreach($resolvers as $resolverName) { |
|
116 | 1 | $resolver = Arrays::get($this->resolvers, strtolower($resolverName), ""); |
|
117 | 1 | if($resolver !== "") { |
|
118 | 1 | $return[] = $resolver; |
|
119 | 1 | } elseif(class_exists($resolverName) && is_subclass_of($resolverName, ILocaleResolver::class)) { |
|
120 | 1 | $return[] = $resolverName; |
|
121 | } else { |
||
122 | 1 | throw new InvalidLocaleResolverException("Invalid locale resolver $resolverName."); |
|
123 | } |
||
124 | } |
||
125 | 1 | return $return; |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * @throws InvalidLoaderException |
||
130 | */ |
||
131 | protected function resolveLoaderClass(): string { |
||
132 | 1 | $config = $this->getConfig(); |
|
133 | /** @var string $loaderName */ |
||
134 | 1 | $loaderName = $config->loader["name"]; |
|
135 | 1 | $loader = Arrays::get($this->loaders, strtolower($loaderName), ""); |
|
136 | 1 | if($loader !== "") { |
|
137 | 1 | return $loader; |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
138 | 1 | } elseif(class_exists($loaderName) && is_subclass_of($loaderName, ILoader::class)) { |
|
139 | 1 | return $loaderName; |
|
140 | } |
||
141 | 1 | throw new InvalidLoaderException("Invalid translation loader $loaderName."); |
|
142 | } |
||
143 | |||
144 | /** |
||
145 | * @return string[] |
||
146 | * @throws InvalidFolderException |
||
147 | */ |
||
148 | protected function getFolders(): array { |
||
149 | 1 | $config = $this->getConfig(); |
|
150 | 1 | $folders = $config->loader["folders"]; |
|
151 | /** @var ITranslationProvider $extension */ |
||
152 | 1 | foreach($this->compiler->getExtensions(ITranslationProvider::class) as $extension) { |
|
153 | 1 | $folders = array_merge($folders, array_values($extension->getTranslationResources())); |
|
154 | } |
||
155 | 1 | foreach($folders as $folder) { |
|
156 | 1 | if(!is_dir($folder)) { |
|
157 | 1 | throw new InvalidFolderException("Folder $folder does not exist."); |
|
158 | } |
||
159 | } |
||
160 | 1 | return $folders; |
|
161 | } |
||
162 | |||
163 | /** |
||
164 | * @throws InvalidMessageSelectorException |
||
165 | */ |
||
166 | protected function resolveMessageSelector(): string { |
||
167 | 1 | $config = $this->getConfig(); |
|
168 | /** @var string $messageSelector */ |
||
169 | 1 | $messageSelector = $config->messageSelector; |
|
170 | 1 | if(class_exists($messageSelector) && is_subclass_of($messageSelector, IMessageSelector::class)) { |
|
171 | 1 | return $messageSelector; |
|
172 | } |
||
173 | 1 | throw new InvalidMessageSelectorException("Invalid message selector $messageSelector."); |
|
174 | } |
||
175 | |||
176 | /** |
||
177 | * @throws InvalidLocaleResolverException |
||
178 | * @throws InvalidLoaderException |
||
179 | * @throws InvalidMessageSelectorException |
||
180 | */ |
||
181 | public function loadConfiguration(): void { |
||
182 | 1 | $builder = $this->getContainerBuilder(); |
|
183 | 1 | $config = $this->getConfig(); |
|
184 | 1 | $resolvers = $this->resolveResolverClass(); |
|
185 | 1 | $loader = $this->resolveLoaderClass(); |
|
186 | 1 | $builder->addDefinition($this->prefix(static::SERVICE_TRANSLATOR)) |
|
187 | 1 | ->setType(Translator::class); |
|
188 | 1 | $builder->addDefinition($this->prefix(static::SERVICE_LOADER)) |
|
189 | 1 | ->setType($loader) |
|
190 | 1 | ->addSetup("setDefaultLang", [$config->default]); |
|
191 | 1 | $messageSelector = $this->resolveMessageSelector(); |
|
192 | 1 | $builder->addDefinition($this->prefix(static::SERVICE_MESSAGE_SELECTOR)) |
|
193 | 1 | ->setType($messageSelector); |
|
194 | 1 | if(count($resolvers) === 1) { |
|
195 | 1 | $builder->addDefinition($this->prefix(static::SERVICE_LOCALE_RESOLVER)) |
|
196 | 1 | ->setType($resolvers[0]); |
|
197 | } else { |
||
198 | 1 | $chainResolver = $builder->addDefinition($this->prefix(static::SERVICE_LOCALE_RESOLVER)) |
|
199 | 1 | ->setType(ChainLocaleResolver::class); |
|
200 | 1 | foreach($resolvers as $index => $resolver) { |
|
201 | 1 | $resolverService = $builder->addDefinition($this->prefix("resolver.$index")) |
|
202 | 1 | ->setType($resolver) |
|
203 | 1 | ->setAutowired(false); |
|
204 | 1 | $chainResolver->addSetup('$service[] = ?', [$resolverService]); |
|
205 | } |
||
206 | } |
||
207 | 1 | if($config->debugger && interface_exists(\Tracy\IBarPanel::class)) { |
|
208 | 1 | $builder->addDefinition($this->prefix(static::SERVICE_PANEL)) |
|
209 | 1 | ->setType(TranslationPanel::class); |
|
210 | /** @var ServiceDefinition $tracy */ |
||
211 | 1 | $tracy = $builder->getDefinition("tracy.bar"); |
|
212 | 1 | $tracy->addSetup("addPanel", ["@" . $this->prefix(static::SERVICE_PANEL), "translation"]); |
|
213 | } |
||
214 | 1 | } |
|
215 | |||
216 | /** |
||
217 | * @throws InvalidFolderException |
||
218 | */ |
||
219 | public function beforeCompile(): void { |
||
220 | 1 | $builder = $this->getContainerBuilder(); |
|
221 | 1 | $config = $this->getConfig(); |
|
222 | /** @var ServiceDefinition $loader */ |
||
223 | 1 | $loader = $builder->getDefinition($this->prefix(static::SERVICE_LOADER)); |
|
224 | 1 | if(in_array(IFileLoader::class, class_implements((string) $loader->class), true)) { |
|
225 | 1 | $folders = $this->getFolders(); |
|
226 | 1 | $loader->addSetup("setFolders", [$folders]); |
|
227 | 1 | foreach($folders as $folder) { |
|
228 | 1 | $builder->addDependency($folder); |
|
229 | } |
||
230 | } |
||
231 | /** @var ServiceDefinition $resolver */ |
||
232 | 1 | $resolver = $builder->getDefinition($this->prefix(static::SERVICE_LOCALE_RESOLVER)); |
|
233 | 1 | if(in_array(IAppRequestAwareLocaleResolver::class, class_implements((string) $resolver->class), true)) { |
|
234 | 1 | $applicationService = $builder->getByType(Application::class) ?? "application"; |
|
235 | 1 | if($builder->hasDefinition($applicationService)) { |
|
236 | /** @var ServiceDefinition $application */ |
||
237 | 1 | $application = $builder->getDefinition($applicationService); |
|
238 | 1 | $application->addSetup('$service->onRequest[] = ?', [[$resolver, "onRequest"]]); |
|
239 | } |
||
240 | } |
||
241 | 1 | if($config->compiler["enabled"]) { |
|
242 | 1 | $serviceName = $this->prefix(static::SERVICE_LOADER); |
|
243 | /** @var ServiceDefinition $loader */ |
||
244 | 1 | $loader = $builder->getDefinition($serviceName); |
|
245 | 1 | $builder->addDefinition($this->prefix(static::SERVICE_ORIGINAL_LOADER)) |
|
246 | 1 | ->setFactory((string) $loader->class, [new ManualLocaleResolver(), $config->loader["folders"]]) |
|
247 | 1 | ->addSetup("setDefaultLang", [$config->default]) |
|
248 | 1 | ->setAutowired(false); |
|
249 | 1 | $folder = Helpers::expand("%tempDir%/catalogues", $builder->parameters); |
|
250 | 1 | $loader->setFactory(MessagesCatalogue::class); |
|
251 | 1 | $loader->setType(MessagesCatalogue::class); |
|
252 | 1 | $loader->addSetup("setFolders", [[$folder]]); |
|
253 | 1 | $builder->addDefinition($this->prefix(static::SERVICE_CATALOGUE_COMPILER)) |
|
254 | 1 | ->setFactory(CatalogueCompiler::class, [$loader, $folder, $config->compiler["languages"]]); |
|
255 | } |
||
256 | 1 | $latteFactoryService = $builder->getByType(ILatteFactory::class) ?? "latte.latteFactory"; |
|
257 | 1 | if($builder->hasDefinition($latteFactoryService)) { |
|
258 | /** @var FactoryDefinition $latteFactory */ |
||
259 | 1 | $latteFactory = $builder->getDefinition($latteFactoryService); |
|
260 | 1 | $latteFactory->getResultDefinition()->addSetup("addFilter", ["translate", ["@" . $this->prefix(static::SERVICE_TRANSLATOR), "translate"]]); |
|
261 | 1 | $latteFactory->getResultDefinition()->addSetup("addProvider", ["translator", "@" . $this->prefix(static::SERVICE_TRANSLATOR)]); |
|
262 | } |
||
263 | 1 | } |
|
264 | |||
265 | public function afterCompile(ClassType $class): void { |
||
266 | 1 | $config = $this->getConfig(); |
|
267 | 1 | $initialize = $this->initialization; |
|
268 | 1 | $initialize->addBody('$translator = $this->getService(?);', [$this->prefix(static::SERVICE_TRANSLATOR)]); |
|
269 | 1 | foreach($config->onUntranslated as &$task) { |
|
270 | 1 | if(!is_array($task)) { |
|
271 | 1 | $task = explode("::", $task); |
|
272 | 1 | } elseif(substr($task[0], 0, 1) === "@") { |
|
273 | 1 | $initialize->addBody('$translator->onUntranslated[] = [$this->getService(?), ?];', [substr($task[0], 1), $task[1]]); |
|
274 | 1 | continue; |
|
275 | } |
||
276 | 1 | $initialize->addBody('$translator->onUntranslated[] = [?, ?];', [$task[0], $task[1]]); |
|
277 | } |
||
278 | 1 | $initialize->addBody('$resolvers = $this->findByType(?); |
|
279 | 1 | foreach($resolvers as $resolver) $this->getService($resolver)->setLoader($this->getService(?));', [ILoaderAwareLocaleResolver::class, $this->prefix(static::SERVICE_LOADER)]); |
|
280 | 1 | if($config->compiler["enabled"]) { |
|
281 | 1 | $initialize->addBody('$this->getService(?)->compile();', [$this->prefix(static::SERVICE_CATALOGUE_COMPILER)]); |
|
282 | } |
||
283 | 1 | } |
|
284 | } |
||
285 | ?> |