├── Changelog.md
├── LICENSE
├── README.md
├── composer.json
├── src
├── Command
│ ├── DownloadCommand.php
│ ├── SynchronizeCommand.php
│ └── UploadCommand.php
├── Controller
│ └── ProfilerController.php
├── DependencyInjection
│ ├── Configuration.php
│ └── HappyrTranslationExtension.php
├── Exception
│ ├── HappyrTranslationException.php
│ └── HttpException.php
├── HappyrTranslationBundle.php
├── Http
│ └── RequestManager.php
├── Model
│ └── Message.php
├── Resources
│ ├── config
│ │ ├── autoAdd.yml
│ │ ├── routing_dev.yml
│ │ └── services.yml
│ ├── doc
│ │ └── images
│ │ │ ├── edit-flag-sync-example.gif
│ │ │ ├── missing-translation-example.gif
│ │ │ └── toolbar-example.png
│ └── views
│ │ └── Profiler
│ │ ├── edit.html.twig
│ │ ├── javascripts.html.twig
│ │ └── translation.html.twig
├── Service
│ ├── Blackhole.php
│ ├── Filesystem.php
│ ├── Loco.php
│ └── TranslationServiceInterface.php
└── Translation
│ ├── AutoAdder.php
│ └── FilesystemUpdater.php
└── tests
└── Functional
├── AppKernel.php
├── BaseTestCase.php
├── BundleInitializationTest.php
└── config
├── default.yml
├── framework.yml
└── routing.yml
/Changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### 0.4 to master
4 |
5 | ### 0.3 to 0.4
6 |
7 | * The command `happyr:translation:sync` does not crash when translation files do not exist. ([#26](https://github.com/Happyr/TranslationBundle/pull/26))
8 | * Added command for uploading all translations. (`happyr:translation:upload`) ([#29](https://github.com/Happyr/TranslationBundle/pull/31))
9 | * The value `yaml` for the configuration key `file_extension` has been renamed to `yml`. ([#31](https://github.com/Happyr/TranslationBundle/pull/31))
10 | * Added option to not handle empty translations. ([#31](https://github.com/Happyr/TranslationBundle/pull/31))
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Happyr
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Happyr Translation Bundle
2 |
3 | [](https://github.com/Happyr/TranslationBundle/releases)
4 | [](https://travis-ci.org/Happyr/TranslationBundle)
5 | [](https://scrutinizer-ci.com/g/Happyr/TranslationBundle)
6 | [](https://scrutinizer-ci.com/g/Happyr/TranslationBundle)
7 | [](https://packagist.org/packages/happyr/translation-bundle)
8 |
9 |
10 | # DEPRECATED: Use php-translation/symfony-bundle
11 |
12 | This bundle has been deprecated in favor of [php-tranlation/symfony-bundle](https://packagist.org/packages/php-translation/symfony-bundle). We took all features form this bundle and put them (and many more) at php-translation.
13 |
14 | The bundle will still live here forever but no new features or bugfixes will be merged. Forking or moving this repo to new maintainers will not make any sense since that is pretty much what we already done in php-translation.
15 |
16 | -------
17 |
18 | This bundle helps you to integrate with a third party translation service. The bundle has been focused to integrate to
19 | the [Loco](https://localise.biz) service. If you want to know how Happyr work with this bundle you should check out
20 | [this blog post](http://developer.happyr.com/how-happyr-work-with-symfony-translations).
21 |
22 | The key features of this bundle is:
23 |
24 | * Easy to download all translations from
25 | * Support for multiple projects
26 | * Create new translation assets by the Symfony WebProfiler
27 | * Edit, flag and synchronize the translation via the Symfony WebProfiler
28 | * Auto upload missing translations to SaaS
29 |
30 | ## Usage
31 |
32 | To download all translations from Loco, simply run:
33 | ``` bash
34 | php app/console happyr:translation:download
35 | ```
36 |
37 | When you have added new translations you may submit these to your translation SaaS by the WebProfiler toolbar.
38 |
39 | 
40 |
41 | You may also change translations and flag them from the same WebProfiler page.
42 |
43 | 
44 |
45 | When you want to fetch new translations from your SaaS you should run the synchronize command. This command will
46 | keep your current placeholders from missing translations.
47 |
48 | ``` bash
49 | php app/console happyr:translation:sync
50 | ```
51 |
52 | ## Install
53 |
54 | Install the bundle with `composer require happyr/translation-bundle`
55 |
56 | You do also need to choose what library to use when you are sending http messages. Consult the [php-http/client-implementation](https://packagist.org/providers/php-http/client-implementation) virtual package to find adapters to use. For more information about virtual packages please refer to [Httplug](http://docs.httplug.io/en/latest/virtual-package/). Example:
57 | ```bash
58 | composer require php-http/guzzle6-adapter
59 | ```
60 | Enable the bundle in your kernel:
61 |
62 | ```
63 | setName('happyr:translation:download')
21 | ->setDescription('Replace your local files with the latest from your translation SaaS.');
22 | }
23 | /**
24 | * {@inheritdoc}
25 | */
26 | protected function execute(InputInterface $input, OutputInterface $output)
27 | {
28 | $this->getContainer()->get('happyr.translation')->downloadAllTranslations();
29 | $output->writeln('Download complete');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Command/SynchronizeCommand.php:
--------------------------------------------------------------------------------
1 | setName('happyr:translation:sync')
22 | ->setDescription('Sync all your translations with SaaS. Leave place holders for missing translations.');
23 | }
24 | /**
25 | * {@inheritdoc}
26 | */
27 | protected function execute(InputInterface $input, OutputInterface $output)
28 | {
29 | $this->getContainer()->get('happyr.translation')->synchronizeAllTranslations();
30 | $output->writeln('Synchronization complete');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Command/UploadCommand.php:
--------------------------------------------------------------------------------
1 | setName('happyr:translation:upload')
24 | ->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action')
25 | ->setDescription('Upload your translations into your translator service.');
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | protected function execute(InputInterface $input, OutputInterface $output)
32 | {
33 | if ($input->getOption('force')) {
34 | $this->getContainer()->get('happyr.translation')->uploadAllTranslations();
35 | $output->writeln('Upload complete');
36 | } else {
37 | $output->writeln('ATTENTION: This operation should not be executed in a production environment.');
38 | $output->writeln('');
39 | $output->writeln(sprintf('This is going to replace every translations on your translator service. '));
40 | $output->writeln('Please run the operation with --force to execute');
41 | return self::RETURN_CODE_NO_FORCE;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Controller/ProfilerController.php:
--------------------------------------------------------------------------------
1 | getParameter('translation.toolbar.allow_edit')) {
30 | return new Response('You are not allowed to edit the translations.');
31 | }
32 |
33 | if (!$request->isXmlHttpRequest()) {
34 | return $this->redirectToRoute('_profiler', ['token' => $token]);
35 | }
36 |
37 | $message = $this->getMessage($request, $token);
38 | $trans = $this->get('happyr.translation');
39 |
40 | if ($request->isMethod('GET')) {
41 | $trans->fetchTranslation($message);
42 |
43 | return $this->render('HappyrTranslationBundle:Profiler:edit.html.twig', [
44 | 'message' => $message,
45 | 'key' => $request->query->get('message_id'),
46 | ]);
47 | }
48 |
49 | //Assert: This is a POST request
50 | $message->setTranslation($request->request->get('translation'));
51 | $trans->updateTranslation($message);
52 |
53 | return new Response($message->getTranslation());
54 | }
55 |
56 | /**
57 | * @param Request $request
58 | * @param string $token
59 | *
60 | * @Route("/{token}/translation/flag", name="_profiler_translations_flag")
61 | * @Method("POST")
62 | *
63 | * @return Response
64 | */
65 | public function flagAction(Request $request, $token)
66 | {
67 | if (!$request->isXmlHttpRequest()) {
68 | return $this->redirectToRoute('_profiler', ['token' => $token]);
69 | }
70 |
71 | $message = $this->getMessage($request, $token);
72 |
73 | $saved = $this->get('happyr.translation')->flagTranslation($message);
74 |
75 | return new Response($saved ? 'OK' : 'ERROR');
76 | }
77 |
78 | /**
79 | * @param Request $request
80 | * @param string $token
81 | *
82 | * @Route("/{token}/translation/sync", name="_profiler_translations_sync")
83 | * @Method("POST")
84 | *
85 | * @return Response
86 | */
87 | public function syncAction(Request $request, $token)
88 | {
89 | if (!$request->isXmlHttpRequest()) {
90 | return $this->redirectToRoute('_profiler', ['token' => $token]);
91 | }
92 |
93 | $message = $this->getMessage($request, $token);
94 | $translation = $this->get('happyr.translation')->fetchTranslation($message, true);
95 |
96 | if ($translation !== null) {
97 | return new Response($translation);
98 | }
99 |
100 | return new Response('Asset not found', 404);
101 | }
102 |
103 | /**
104 | * @param Request $request
105 | * @param $token
106 | *
107 | * @Route("/{token}/translation/sync-all", name="_profiler_translations_sync_all")
108 | *
109 | * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
110 | */
111 | public function syncAllAction(Request $request, $token)
112 | {
113 | if (!$request->isXmlHttpRequest()) {
114 | return $this->redirectToRoute('_profiler', ['token' => $token]);
115 | }
116 |
117 | $this->get('happyr.translation')->synchronizeAllTranslations();
118 |
119 | return new Response('Started synchronization of all translations');
120 | }
121 |
122 | /**
123 | * Save the selected translation to resources.
124 | *
125 | * @author Damien Alexandre (damienalexandre)
126 | *
127 | * @param Request $request
128 | * @param string $token
129 | *
130 | * @Route("/{token}/translation/create-asset", name="_profiler_translations_create_assets")
131 | * @Method("POST")
132 | *
133 | * @return Response
134 | */
135 | public function createAssetsAction(Request $request, $token)
136 | {
137 | if (!$request->isXmlHttpRequest()) {
138 | return $this->redirectToRoute('_profiler', ['token' => $token]);
139 | }
140 |
141 | $messages = $this->getSelectedMessages($request, $token);
142 |
143 | if ($messages instanceof Data) {
144 | $messages = $messages->getValue(true);
145 | }
146 |
147 | if (empty($messages)) {
148 | return new Response('No translations selected.');
149 | }
150 |
151 | $uploaded = array();
152 | $trans = $this->get('happyr.translation');
153 | foreach ($messages as $message) {
154 | if ($trans->createAsset($message)) {
155 | $uploaded[] = $message;
156 | }
157 | }
158 |
159 | $saved = count($uploaded);
160 | if ($saved > 0) {
161 | $this->get('happyr.translation.filesystem')->updateMessageCatalog($uploaded);
162 | }
163 |
164 | return new Response(sprintf('%s new assets created!', $saved));
165 | }
166 |
167 | /**
168 | * @param Request $request
169 | * @param string $token
170 | *
171 | * @return Message
172 | */
173 | protected function getMessage(Request $request, $token)
174 | {
175 | $profiler = $this->get('profiler');
176 | $profiler->disable();
177 |
178 | $messageId = $request->request->get('message_id', $request->query->get('message_id'));
179 |
180 | $profile = $profiler->loadProfile($token);
181 | $messages = $profile->getCollector('translation')->getMessages();
182 | if (!isset($messages[$messageId])) {
183 | throw $this->createNotFoundException(sprintf('No message with key "%s" was found.', $messageId));
184 | }
185 | $message = new Message($messages[$messageId]);
186 |
187 | if ($message->getState() === DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK) {
188 | $message->setLocale($profile->getCollector('request')->getLocale())
189 | ->setTranslation(sprintf('[%s]', $message->getTranslation()));
190 | }
191 |
192 | return $message;
193 | }
194 |
195 | /**
196 | * @param Request $request
197 | * @param string $token
198 | *
199 | * @return array
200 | */
201 | protected function getSelectedMessages(Request $request, $token)
202 | {
203 | $profiler = $this->get('profiler');
204 | $profiler->disable();
205 |
206 | $selected = $request->request->get('selected');
207 | if (!$selected || count($selected) == 0) {
208 | return array();
209 | }
210 |
211 | $profile = $profiler->loadProfile($token);
212 | $dataCollector = $profile->getCollector('translation');
213 | $messages = $dataCollector->getMessages();
214 |
215 | if ($messages instanceof Data) {
216 | $messages = $messages->getValue(true);
217 | }
218 |
219 | $toSave = array_intersect_key($messages, array_flip($selected));
220 |
221 | $messages = array();
222 | foreach ($toSave as $data) {
223 | //We do not want do add the placeholder to Loco. That messes up the stats.
224 | $data['translation'] = '';
225 |
226 | $messages[] = new Message($data);
227 | }
228 |
229 | return $messages;
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | root('happyr_translation');
20 |
21 | $root->children()
22 | ->enumNode('translation_service')->values(array('blackhole', 'filesystem', 'loco'))->defaultValue('loco')->end()
23 | ->scalarNode('target_dir')->defaultValue('%kernel.root_dir%/Resources/translations')->end()
24 | ->scalarNode('httplug_client')->defaultValue('httplug.client')->cannotBeEmpty()->end()
25 | ->scalarNode('httplug_message_factory')->defaultValue('httplug.message_factory')->cannotBeEmpty()->end()
26 | ->booleanNode('sync_empty_translations')->defaultTrue()->end()
27 | ->booleanNode('auto_add_assets')->defaultFalse()->end()
28 | ->booleanNode('allow_edit')->defaultTrue()->end()
29 | ->enumNode('file_extension')->values(array('csv', 'ini', 'json', 'mo', 'php', 'po', 'qt', 'yml', 'xlf'))->defaultValue('xlf')->end()
30 | ->arrayNode('locales')
31 | ->requiresAtLeastOneElement()
32 | ->prototype('scalar')->end()
33 | ->end()
34 | ->arrayNode('domains')
35 | ->requiresAtLeastOneElement()
36 | ->prototype('scalar')->end()
37 | ->end()
38 | ->append($this->getProjectNode())
39 | ->end();
40 |
41 | return $treeBuilder;
42 | }
43 |
44 | /**
45 | * @return \Symfony\Component\Config\Definition\Builder\NodeDefinition
46 | */
47 | private function getProjectNode()
48 | {
49 | $treeBuilder = new TreeBuilder();
50 | $node = $treeBuilder->root('projects');
51 | $node
52 | ->useAttributeAsKey('name')
53 | ->prototype('array')
54 | ->children()
55 | ->scalarNode('api_key')->isRequired()->end()
56 | ->arrayNode('locales')
57 | ->requiresAtLeastOneElement()
58 | ->prototype('scalar')->end()
59 | ->end()
60 | ->arrayNode('domains')
61 | ->requiresAtLeastOneElement()
62 | ->prototype('scalar')->end()
63 | ->end()
64 | ->end()
65 | ->end();
66 |
67 | return $node;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/DependencyInjection/HappyrTranslationExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration($configuration, $configs);
21 | $this->copyValuesFromParentToProject('locales', $config);
22 | $this->copyValuesFromParentToProject('domains', $config);
23 |
24 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
25 | $loader->load('services.yml');
26 |
27 | if ($config['auto_add_assets']) {
28 | $loader->load('autoAdd.yml');
29 | }
30 |
31 | $container->setParameter('translation.toolbar.allow_edit', $config['allow_edit']);
32 |
33 | $targetDir = rtrim($config['target_dir'], '/');
34 | $container->findDefinition('happyr.translation.filesystem')
35 | ->replaceArgument(2, $targetDir)
36 | ->replaceArgument(3, $config['file_extension'])
37 | ->replaceArgument(4, $config['sync_empty_translations']);
38 |
39 | $this->configureLoaderAndDumper($container, $config['file_extension']);
40 |
41 | $container->getDefinition('happyr.translation.request_manager')
42 | ->replaceArgument(0, new Reference($config['httplug_client']))
43 | ->replaceArgument(1, new Reference($config['httplug_message_factory']));
44 |
45 | /*
46 | * Set alias for the translation service
47 | */
48 | $container->setAlias('happyr.translation', 'happyr.translation.service.'.$config['translation_service']);
49 |
50 | $container->findDefinition('happyr.translation.service.loco')
51 | ->replaceArgument(3, $config['projects']);
52 | }
53 |
54 | /**
55 | * Copy the parent configuration to the children.
56 | *
57 | * @param string $key
58 | * @param array $config
59 | */
60 | private function copyValuesFromParentToProject($key, array &$config)
61 | {
62 | if (empty($config[$key])) {
63 | return;
64 | }
65 |
66 | foreach ($config['projects'] as &$project) {
67 | if (empty($project[$key])) {
68 | $project[$key] = $config[$key];
69 | }
70 | }
71 | }
72 |
73 | /**
74 | * @param ContainerBuilder $container
75 | * @param string $fileExtension
76 | */
77 | protected function configureLoaderAndDumper(ContainerBuilder $container, $fileExtension)
78 | {
79 | switch ($fileExtension) {
80 | case 'xlf':
81 | $fileExtension = 'xliff';
82 | break;
83 | case 'yml':
84 | $fileExtension = 'yaml';
85 | break;
86 | }
87 |
88 | $loader = $container->register('happyr.translation.loader', sprintf('Symfony\Component\Translation\Loader\%sFileLoader', ucfirst($fileExtension)));
89 | $loader->addTag('translation.loader', ['alias' => $fileExtension]);
90 |
91 | $dumper = $container->register('happyr.translation.dumper', sprintf('Symfony\Component\Translation\Dumper\%sFileDumper', ucfirst($fileExtension)));
92 | $dumper->addTag('translation.dumper', ['alias' => $fileExtension]);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Exception/HappyrTranslationException.php:
--------------------------------------------------------------------------------
1 | client = new PluginClient($client, [new ErrorPlugin()]);
37 | $this->messageFactory = $messageFactory;
38 | }
39 |
40 | /**
41 | * @param array $data
42 | *
43 | * @return mixed
44 | */
45 | public function downloadFiles(FilesystemUpdater $filesystem, array $data)
46 | {
47 | $factory = $this->getMessageFactory();
48 | $client = $this->getClient();
49 |
50 | foreach ($data as $url => $fileName) {
51 | $response = $client->sendRequest($factory->createRequest('GET', $url));
52 | $filesystem->writeToFile($fileName, $response->getBody()->__toString());
53 | }
54 | }
55 |
56 | /**
57 | * @param string $method
58 | * @param string $url
59 | * @param array $data
60 | *
61 | * @return array
62 | *
63 | * @throws HttpException
64 | */
65 | public function send($method, $url, $body = null, $headers = array())
66 | {
67 | $request = $this->getMessageFactory()->createRequest($method, $url, $headers, $body);
68 |
69 | try {
70 | $response = $this->getClient()->sendRequest($request);
71 | } catch (TransferException $e) {
72 | $message = 'Error sending request. ';
73 | if ($e instanceof \Http\Client\Exception\HttpException) {
74 | $message .= (string) $e->getResponse()->getBody();
75 | }
76 |
77 | throw new HttpException($message, $e->getCode(), $e);
78 | }
79 |
80 | // TODO add more error checks
81 | return json_decode($response->getBody()->__toString(), true);
82 | }
83 |
84 | /**
85 | * @return HttpClient
86 | */
87 | public function getClient()
88 | {
89 | return $this->client;
90 | }
91 |
92 | /**
93 | *
94 | * @return \Http\Message\MessageFactory
95 | */
96 | private function getMessageFactory()
97 | {
98 | return $this->messageFactory;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Model/Message.php:
--------------------------------------------------------------------------------
1 | 2.8
61 | */
62 | private $transChoiceNumber;
63 |
64 | /**
65 | * @var array
66 | *
67 | * The parameters sent to the translations
68 | * Used only in Symfony >2.8
69 | */
70 | private $parameters;
71 |
72 | /**
73 | * @param array $data
74 | * array( count = 1, domain = "navigation", id = "logout", locale = "sv", state = 1, translation = "logout" )
75 | */
76 | public function __construct($data)
77 | {
78 | $this->domain = $data['domain'];
79 | $this->id = $data['id'];
80 | $this->locale = $data['locale'];
81 | $this->state = $data['state'];
82 | $this->translation = $data['translation'];
83 |
84 | if (isset($data['count'])) {
85 | $this->count = $data['count'];
86 | }
87 |
88 | if (isset($data['transChoiceNumber'])) {
89 | $this->transChoiceNumber = $data['transChoiceNumber'];
90 | }
91 |
92 | if (isset($data['parameters'])) {
93 | $this->parameters = $data['parameters'];
94 | }
95 | }
96 |
97 | /**
98 | * @return int
99 | */
100 | public function getCount()
101 | {
102 | return $this->count;
103 | }
104 |
105 | /**
106 | * @param int $count
107 | *
108 | * @return $this
109 | */
110 | public function setCount($count)
111 | {
112 | $this->count = $count;
113 |
114 | return $this;
115 | }
116 |
117 | /**
118 | * @return string
119 | */
120 | public function getDomain()
121 | {
122 | return $this->domain;
123 | }
124 |
125 | /**
126 | * @param string $domain
127 | *
128 | * @return $this
129 | */
130 | public function setDomain($domain)
131 | {
132 | $this->domain = $domain;
133 |
134 | return $this;
135 | }
136 |
137 | /**
138 | * @return string
139 | */
140 | public function getId()
141 | {
142 | return $this->id;
143 | }
144 |
145 | /**
146 | * @param string $id
147 | *
148 | * @return $this
149 | */
150 | public function setId($id)
151 | {
152 | $this->id = $id;
153 |
154 | return $this;
155 | }
156 |
157 | /**
158 | * @return string
159 | */
160 | public function getLocale()
161 | {
162 | return $this->locale;
163 | }
164 |
165 | /**
166 | * @param string $locale
167 | *
168 | * @return $this
169 | */
170 | public function setLocale($locale)
171 | {
172 | $this->locale = $locale;
173 |
174 | return $this;
175 | }
176 |
177 | /**
178 | * @return int
179 | */
180 | public function getState()
181 | {
182 | return $this->state;
183 | }
184 |
185 | /**
186 | * @param int $state
187 | *
188 | * @return $this
189 | */
190 | public function setState($state)
191 | {
192 | $this->state = $state;
193 |
194 | return $this;
195 | }
196 |
197 | /**
198 | * @return string
199 | */
200 | public function getTranslation()
201 | {
202 | return $this->translation;
203 | }
204 |
205 | /**
206 | * @param string $translation
207 | *
208 | * @return $this
209 | */
210 | public function setTranslation($translation)
211 | {
212 | $this->translation = $translation;
213 |
214 | return $this;
215 | }
216 |
217 | /**
218 | * @return int
219 | */
220 | public function getTransChoiceNumber()
221 | {
222 | return $this->transChoiceNumber;
223 | }
224 |
225 | /**
226 | * @param int $transChoiceNumber
227 | *
228 | * @return $this
229 | */
230 | public function setTransChoiceNumber($transChoiceNumber)
231 | {
232 | $this->transChoiceNumber = $transChoiceNumber;
233 |
234 | return $this;
235 | }
236 |
237 | /**
238 | * @return array
239 | */
240 | public function getParameters()
241 | {
242 | return $this->parameters;
243 | }
244 |
245 | /**
246 | * @return bool
247 | */
248 | public function hasParameters()
249 | {
250 | return !empty($this->parameters);
251 | }
252 |
253 | /**
254 | * @param array $parameters
255 | *
256 | * @return $this
257 | */
258 | public function setParameters($parameters)
259 | {
260 | $this->parameters = $parameters;
261 |
262 | return $this;
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/src/Resources/config/autoAdd.yml:
--------------------------------------------------------------------------------
1 |
2 |
3 | services:
4 | happyr.translation.auto_adder:
5 | class: Happyr\TranslationBundle\Translation\AutoAdder
6 | arguments: [ '@?translator.data_collector', '@happyr.translation', '@happyr.translation.filesystem' ]
7 | tags:
8 | - { name: kernel.event_listener, event: kernel.terminate, method: onTerminate, priority: 10 }
9 |
--------------------------------------------------------------------------------
/src/Resources/config/routing_dev.yml:
--------------------------------------------------------------------------------
1 | _logo_translation_profiler:
2 | resource: "@HappyrTranslationBundle/Controller/ProfilerController.php"
3 | type: annotation
4 | prefix: /_profiler
5 |
--------------------------------------------------------------------------------
/src/Resources/config/services.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 |
3 | services:
4 |
5 | # Custom data_collector to use our own template
6 | happyr.translation.data_collector:
7 | class: Symfony\Component\Translation\DataCollector\TranslationDataCollector
8 | arguments: [ '@?translator.data_collector' ]
9 | tags:
10 | - { name: 'data_collector', template: "@HappyrTranslation/Profiler/translation", id: "translation", priority: 200 }
11 |
12 | happyr.translation.service.blackhole:
13 | class: Happyr\TranslationBundle\Service\Blackhole
14 |
15 | happyr.translation.service.filesystem:
16 | class: Happyr\TranslationBundle\Service\Filesystem
17 | arguments: ['@happyr.translation.filesystem']
18 |
19 | happyr.translation.filesystem:
20 | class: Happyr\TranslationBundle\Translation\FilesystemUpdater
21 | arguments:
22 | - '@happyr.translation.loader'
23 | - '@happyr.translation.dumper'
24 | - ~
25 | - ~
26 | - ~
27 | tags:
28 | - { name: kernel.event_listener, event: kernel.terminate, method: onTerminate, priority: -20 }
29 | - { name: kernel.event_listener, event: console.terminate, method: onTerminate, priority: -20 }
30 |
31 | happyr.translation.service.loco:
32 | class: Happyr\TranslationBundle\Service\Loco
33 | arguments:
34 | - '@happyr.translation.request_manager'
35 | - '@happyr.translation.filesystem'
36 | - '@translator'
37 | - ~
38 |
39 | happyr.translation.request_manager:
40 | class: Happyr\TranslationBundle\Http\RequestManager
41 | arguments: [~, ~]
42 | public: false
43 |
--------------------------------------------------------------------------------
/src/Resources/doc/images/edit-flag-sync-example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Happyr/TranslationBundle/497c042a1b89434b0b874e32c80cec2f5a08cc17/src/Resources/doc/images/edit-flag-sync-example.gif
--------------------------------------------------------------------------------
/src/Resources/doc/images/missing-translation-example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Happyr/TranslationBundle/497c042a1b89434b0b874e32c80cec2f5a08cc17/src/Resources/doc/images/missing-translation-example.gif
--------------------------------------------------------------------------------
/src/Resources/doc/images/toolbar-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Happyr/TranslationBundle/497c042a1b89434b0b874e32c80cec2f5a08cc17/src/Resources/doc/images/toolbar-example.png
--------------------------------------------------------------------------------
/src/Resources/views/Profiler/edit.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/Resources/views/Profiler/javascripts.html.twig:
--------------------------------------------------------------------------------
1 |
176 |
--------------------------------------------------------------------------------
/src/Resources/views/Profiler/translation.html.twig:
--------------------------------------------------------------------------------
1 | {% extends '@WebProfiler/Collector/translation.html.twig' %}
2 |
3 | {#
4 | # Authors Tobias Nyholm, Damien Alexandre (damienalexandre), Damien Harper
5 | #}
6 |
7 | {% import _self as translation_helper %}
8 |
9 | {% block panelContent %}
10 |
Translation Metrics
11 |
12 |
16 |
17 |
18 |
19 | {{ collector.countdefines }}
20 | Defined messages
21 |
22 |
23 |
24 | {{ collector.countFallbacks }}
25 | Fallback messages
26 |
27 |
28 |
29 | {{ collector.countMissings }}
30 | Missing messages
31 |
32 |
33 |
34 | Translation Messages
35 |
36 | {# sort translation messages in groups #}
37 | {% set messages_defined, messages_missing, messages_fallback = [], [], [] %}
38 | {% for key, message in collector.messages %}
39 | {% if message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_DEFINED') %}
40 | {% set messages_defined = messages_defined|merge({(key): message}) %}
41 | {% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_MISSING') %}
42 | {% set messages_missing = messages_missing|merge({(key): message}) %}
43 | {% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK') %}
44 | {% set messages_fallback = messages_fallback|merge({(key): message}) %}
45 | {% endif %}
46 | {% endfor %}
47 |
48 |
117 | {% include "HappyrTranslationBundle:Profiler:javascripts.html.twig" %}
118 | {% endblock %}
119 |
120 | {% macro render_table(messages) %}
121 |
180 | {% endmacro %}
181 |
--------------------------------------------------------------------------------
/src/Service/Blackhole.php:
--------------------------------------------------------------------------------
1 | filesystemService = $filesystemService;
22 | }
23 |
24 | /**
25 | * @inheritDoc
26 | */
27 | public function fetchTranslation(Message $message, $updateFs = false)
28 | {
29 | if ($updateFs) {
30 | $this->filesystemService->updateMessageCatalog([$message]);
31 | }
32 | }
33 |
34 | /**
35 | * @inheritDoc
36 | */
37 | public function updateTranslation(Message $message)
38 | {
39 | $this->filesystemService->updateMessageCatalog([$message]);
40 | }
41 |
42 | /**
43 | * @inheritDoc
44 | */
45 | public function flagTranslation(Message $message, $type = 0)
46 | {
47 | }
48 |
49 | /**
50 | * @inheritDoc
51 | */
52 | public function createAsset(Message $message)
53 | {
54 | }
55 |
56 | /**
57 | * @inheritDoc
58 | */
59 | public function downloadAllTranslations()
60 | {
61 | }
62 |
63 | /**
64 | * @inheritDoc
65 | */
66 | public function importAllTranslations()
67 | {
68 | }
69 |
70 | /**
71 | * @inheritDoc
72 | */
73 | public function synchronizeAllTranslations()
74 | {
75 | }
76 |
77 | /**
78 | * @inheritDoc
79 | */
80 | public function uploadAllTranslations()
81 | {
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Service/Loco.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class Loco implements TranslationServiceInterface
17 | {
18 | const BASE_URL = 'https://localise.biz/api/';
19 |
20 | /**
21 | * @var RequestManager
22 | */
23 | private $requestManager;
24 |
25 | /**
26 | * @var array projects
27 | */
28 | private $projects;
29 |
30 | /**
31 | * @var FilesystemUpdater filesystemService
32 | */
33 | private $filesystemService;
34 |
35 | /**
36 | * @var TranslatorInterface filesystemService
37 | */
38 | private $translator;
39 |
40 | /**
41 | * @param TranslatorInterface $translator
42 | * @param RequestManager $requestManager
43 | * @param FilesystemUpdater $fs
44 | * @param array $projects
45 | */
46 | public function __construct(RequestManager $requestManager, FilesystemUpdater $fs, TranslatorInterface $translator, array $projects)
47 | {
48 | $this->translator = $translator;
49 | $this->requestManager = $requestManager;
50 | $this->projects = $projects;
51 | $this->filesystemService = $fs;
52 | }
53 |
54 | /**
55 | * @param $key
56 | * @param $method
57 | * @param $resource
58 | * @param null $body
59 | * @param string $type
60 | * @param array $extraQuery
61 | * @return array
62 | * @throws HttpException
63 | */
64 | protected function makeApiRequest($key, $method, $resource, $body = null, $type = 'form', $extraQuery = array())
65 | {
66 | $headers = array();
67 | if ($body !== null) {
68 | if ($type === 'form') {
69 | if (is_array($body)) {
70 | $body = http_build_query($body);
71 | }
72 | $headers['Content-Type'] = 'application/x-www-form-urlencoded';
73 | } elseif ($type === 'json') {
74 | $body = json_encode($body);
75 | $headers['Content-Type'] = 'application/json';
76 | }
77 | }
78 |
79 | $query = array_merge($extraQuery, ['key' => $key]);
80 | $url = self::BASE_URL . $resource . '?' . http_build_query($query);
81 |
82 | return $this->requestManager->send($method, $url, $body, $headers);
83 | }
84 |
85 | /**
86 | * Fetch a translation form Loco.
87 | *
88 | * @param Message $message
89 | */
90 | public function fetchTranslation(Message $message, $updateFs = false)
91 | {
92 | $project = $this->getProject($message);
93 |
94 | try {
95 | $resource = sprintf('translations/%s/%s', $message->getId(), $message->getLocale());
96 | $response = $this->makeApiRequest($project['api_key'], 'GET', $resource);
97 | } catch (HttpException $e) {
98 | if ($e->getCode() === 404) {
99 | //Message does not exist
100 | return;
101 | }
102 | throw $e;
103 | }
104 |
105 | $logoTranslation = $response['translation'];
106 | $messageTranslation = $message->getTranslation();
107 | $message->setTranslation($logoTranslation);
108 |
109 | // update filesystem
110 | if ($updateFs && $logoTranslation !== $messageTranslation) {
111 | $this->filesystemService->updateMessageCatalog([$message]);
112 | }
113 |
114 | return $logoTranslation;
115 | }
116 |
117 | /**
118 | * Update the translation in Loco.
119 | *
120 | * @param Message $message
121 | */
122 | public function updateTranslation(Message $message)
123 | {
124 | $project = $this->getProject($message);
125 |
126 | try {
127 | $resource = sprintf('translations/%s/%s', $message->getId(), $message->getLocale());
128 | $this->makeApiRequest($project['api_key'], 'POST', $resource, $message->getTranslation());
129 | } catch (HttpException $e) {
130 | if ($e->getCode() === 404) {
131 | //Asset does not exist
132 | if ($this->createAsset($message)) {
133 | //Try again
134 | return $this->updateTranslation($message);
135 | }
136 |
137 | return false;
138 | }
139 | throw $e;
140 | }
141 |
142 | $this->filesystemService->updateMessageCatalog([$message]);
143 |
144 | return true;
145 | }
146 |
147 | /**
148 | * If there is something wrong with the translation, please flag it.
149 | *
150 | * @param Message $message
151 | * @param int $type 0: Fuzzy, 1: Incorrect, 2: Provisional, 3: Unapproved, 4: Incomplete
152 | *
153 | * @return bool
154 | */
155 | public function flagTranslation(Message $message, $type = 0)
156 | {
157 | $project = $this->getProject($message);
158 | $flags = ['fuzzy', 'incorrect', 'provisional', 'unapproved', 'incomplete'];
159 |
160 | try {
161 | $resource = sprintf('translations/%s/%s/flag', $message->getId(), $message->getLocale());
162 | $this->makeApiRequest($project['api_key'], 'POST', $resource, ['flag' => $flags[$type]]);
163 | } catch (HttpException $e) {
164 | if ($e->getCode() === 404) {
165 | //Message does not exist
166 | return false;
167 | }
168 | throw $e;
169 | }
170 |
171 | return true;
172 | }
173 |
174 | /**
175 | * Create a new asset in Loco.
176 | *
177 | * @param Message $message
178 | *
179 | * @return bool
180 | */
181 | public function createAsset(Message $message)
182 | {
183 | $project = $this->getProject($message);
184 |
185 | try {
186 | $response = $this->makeApiRequest($project['api_key'], 'POST', 'assets', [
187 | 'id' => $message->getId(),
188 | 'name' => $message->getId(),
189 | 'type' => 'text',
190 | // Tell Loco not to translate the asset
191 | 'default' => 'untranslated',
192 | ]);
193 |
194 | if ($message->hasParameters()) {
195 | // Send those parameter as a note to Loco
196 | $notes = '';
197 | foreach ($message->getParameters() as $key => $value) {
198 | if (!is_array($value)) {
199 | $notes .= 'Parameter: ' . $key . ' (i.e. : ' . $value . ")\n";
200 | } else {
201 | foreach ($value as $k => $v) {
202 | $notes .= 'Parameter: ' . $k . ' (i.e. : ' . $v . ")\n";
203 | }
204 | }
205 | }
206 |
207 | $resource = sprintf('assets/%s.json', $message->getId());
208 | $this->makeApiRequest($project['api_key'], 'PATCH', $resource, ['notes' => $notes], 'json');
209 | }
210 | } catch (HttpException $e) {
211 | if ($e->getCode() === 409) {
212 | //conflict.. ignore
213 | return false;
214 | }
215 | throw $e;
216 | }
217 |
218 | // if this project has multiple domains. Make sure to tag it
219 | if (!empty($project['domains'])) {
220 | $this->addTagToAsset($project, $response['id'], $message->getDomain());
221 | }
222 |
223 | return true;
224 | }
225 |
226 | /**
227 | * @param Message $message
228 | *
229 | * @return array
230 | */
231 | protected function getProject(Message $message)
232 | {
233 | if (isset($this->projects[$message->getDomain()])) {
234 | return $this->projects[$message->getDomain()];
235 | }
236 |
237 | // Return the first project that has the correct domain and locale
238 | foreach ($this->projects as $project) {
239 | if (in_array($message->getDomain(), $project['domains'])) {
240 | if (in_array($message->getLocale(), $project['locales'])) {
241 | return $project;
242 | }
243 | }
244 | }
245 | }
246 |
247 | /**
248 | * @param $project
249 | * @param $messageId
250 | * @param $domain
251 | */
252 | protected function addTagToAsset($project, $messageId, $domain)
253 | {
254 | $resource = sprintf('assets/%s/tags', $messageId);
255 | $this->makeApiRequest($project['api_key'], 'POST', $resource, ['name' => $domain]);
256 | }
257 |
258 | /**
259 | * Download all the translations from Loco. This will replace all the local files.
260 | * This is a quick method of getting all the latest translations and assets.
261 | */
262 | public function downloadAllTranslations()
263 | {
264 | $data = [];
265 | foreach ($this->projects as $name => $config) {
266 | if (empty($config['domains'])) {
267 | $this->getUrls($data, $config, $name, false);
268 | } else {
269 | foreach ($config['domains'] as $domain) {
270 | $this->getUrls($data, $config, $domain, true);
271 | }
272 | }
273 | }
274 | $this->requestManager->downloadFiles($this->filesystemService, $data);
275 | }
276 |
277 | /**
278 | * Upload all the translations from the symfony project into Loco. This will override
279 | * every changed strings in loco
280 | */
281 | public function uploadAllTranslations()
282 | {
283 | foreach ($this->projects as $name => $config) {
284 | if (empty($config['domains'])) {
285 | $this->doUploadDomains($config, $name, false);
286 | } else {
287 | foreach ($config['domains'] as $domain) {
288 | $this->doUploadDomains($config, $domain, true);
289 | }
290 | }
291 | }
292 | }
293 |
294 | /**
295 | * @param array $config
296 | * @param $domain
297 | * @param $useDomainAsFilter
298 | */
299 | protected function doUploadDomains(array &$config, $domain, $useDomainAsFilter)
300 | {
301 | $query = $this->getExportQueryParams($config['api_key']);
302 |
303 | if ($useDomainAsFilter) {
304 | $query['filter'] = $domain;
305 | }
306 |
307 | foreach ($config['locales'] as $locale) {
308 | $extension = $this->filesystemService->getFileExtension();
309 | $file = $this->filesystemService->getTargetDir();
310 | $file .= sprintf('/%s.%s.%s', $domain, $locale, $extension);
311 |
312 | if (is_file($file)) {
313 | $query = [
314 | 'index' => 'id',
315 | 'tag' => $domain,
316 | 'locale'=> $locale
317 | ];
318 |
319 | $resource = sprintf('import/%s', $extension);
320 | $response = $this->makeApiRequest($config['api_key'], 'POST', $resource, file_get_contents($file), 'form', $query);
321 | $this->flatten($response);
322 | } else {
323 | throw new FileNotFoundException(sprintf("Can't find %s file, perhaps you should generate the translations file ?", $file));
324 | }
325 | }
326 | }
327 |
328 | /**
329 | * Synchronize all the translations with Loco. This will keep placeholders. This function is slower
330 | * than just to download the translations.
331 | */
332 | public function synchronizeAllTranslations()
333 | {
334 | foreach ($this->projects as $name => $config) {
335 | if (empty($config['domains'])) {
336 | $this->doSynchronizeDomain($config, $name, false);
337 | } else {
338 | foreach ($config['domains'] as $domain) {
339 | $this->doSynchronizeDomain($config, $domain, true);
340 | }
341 | }
342 | }
343 | }
344 |
345 | /**
346 | * @param array $config
347 | * @param $domain
348 | * @param $useDomainAsFilter
349 | */
350 | protected function doSynchronizeDomain(array &$config, $domain, $useDomainAsFilter)
351 | {
352 | $query = $this->getExportQueryParams($config['api_key']);
353 |
354 | if ($useDomainAsFilter) {
355 | $query['filter'] = $domain;
356 | }
357 |
358 | foreach ($config['locales'] as $locale) {
359 | $resource = sprintf('export/locale/%s.%s', $locale, 'json');
360 | $response = $this->makeApiRequest($config['api_key'], 'GET', $resource, ['query' => $query]);
361 |
362 | $this->flatten($response);
363 |
364 | $messages = array();
365 | foreach ($response as $id => $translation) {
366 | $messages[] = new Message([
367 | 'count' => 1,
368 | 'domain' => $domain,
369 | 'id' => $id,
370 | 'locale' => $locale,
371 | 'state' => 1,
372 | 'translation' => $translation,
373 | ]);
374 | }
375 |
376 | $this->filesystemService->updateMessageCatalog($messages);
377 | }
378 | }
379 |
380 | /**
381 | * Flattens an nested array of translations.
382 | *
383 | * The scheme used is:
384 | * 'key' => array('key2' => array('key3' => 'value'))
385 | * Becomes:
386 | * 'key.key2.key3' => 'value'
387 | *
388 | * This function takes an array by reference and will modify it
389 | *
390 | * @param array &$messages The array that will be flattened
391 | * @param array $subnode Current subnode being parsed, used internally for recursive calls
392 | * @param string $path Current path being parsed, used internally for recursive calls
393 | */
394 | private function flatten(array &$messages, array $subnode = null, $path = null)
395 | {
396 | if (null === $subnode) {
397 | $subnode = &$messages;
398 | }
399 | foreach ($subnode as $key => $value) {
400 | if (is_array($value)) {
401 | $nodePath = $path ? $path . '.' . $key : $key;
402 | $this->flatten($messages, $value, $nodePath);
403 | if (null === $path) {
404 | unset($messages[$key]);
405 | }
406 | } elseif (null !== $path) {
407 | $messages[$path . '.' . $key] = $value;
408 | }
409 | }
410 | }
411 |
412 | /**
413 | * @param array $data
414 | * @param array $config
415 | * @param string $domain
416 | * @param bool $useDomainAsFilter
417 | */
418 | protected function getUrls(array &$data, array $config, $domain, $useDomainAsFilter)
419 | {
420 | $query = $this->getExportQueryParams($config['api_key']);
421 |
422 | if ($useDomainAsFilter) {
423 | $query['filter'] = $domain;
424 | }
425 |
426 | foreach ($config['locales'] as $locale) {
427 | // Build url
428 | $url = sprintf('%sexport/locale/%s.%s?%s', self::BASE_URL, $locale, $this->filesystemService->getFileExtension(), http_build_query($query));
429 | $fileName = sprintf('%s.%s.%s', $domain, $locale, $this->filesystemService->getFileExtension());
430 |
431 | $data[$url] = $fileName;
432 | }
433 | }
434 |
435 | /**
436 | * @param array $config
437 | *
438 | * @return array
439 | */
440 | private function getExportQueryParams($key)
441 | {
442 | $data = array(
443 | 'index' => 'id',
444 | 'status' => 'translated',
445 | 'key' => $key,
446 | );
447 | switch ($this->filesystemService->getFileExtension()) {
448 | case 'php':
449 | $data['format'] = 'zend'; // 'Zend' will give us a flat array
450 | break;
451 | case 'xlf':
452 | default:
453 | $data['format'] = 'symfony';
454 | }
455 |
456 | return $data;
457 | }
458 | }
459 |
--------------------------------------------------------------------------------
/src/Service/TranslationServiceInterface.php:
--------------------------------------------------------------------------------
1 | translator = $translator;
33 | $this->transService = $transService;
34 | $this->fileSystemUpdater = $fileSystemUpdater;
35 | }
36 |
37 | public function onTerminate(Event $event)
38 | {
39 | if ($this->translator === null) {
40 | return;
41 | }
42 |
43 | $messages = $this->translator->getCollectedMessages();
44 | $created = array();
45 | foreach ($messages as $message) {
46 | if ($message['state'] === DataCollectorTranslator::MESSAGE_MISSING) {
47 | $m = new Message($message);
48 | $this->transService->createAsset($m);
49 | $created[] = $m;
50 | }
51 | }
52 |
53 | if (count($created) > 0) {
54 | // update filesystem
55 | $this->fileSystemUpdater->updateMessageCatalog($created);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Translation/FilesystemUpdater.php:
--------------------------------------------------------------------------------
1 | loader = $loader;
61 | $this->dumper = $dumper;
62 | $this->targetDir = $targetDir;
63 | $this->messages = array();
64 | $this->fileExtension = $fileExtension;
65 | $this->syncEmptyTranslations = $syncEmptyTranslations;
66 | }
67 |
68 | /**
69 | * Returns translation file type.
70 | *
71 | * @return string
72 | */
73 | public function getFileExtension()
74 | {
75 | return $this->fileExtension;
76 | }
77 |
78 | /**
79 | * @param string $fileName
80 | * @param string $data the file contents
81 | */
82 | public function writeToFile($fileName, $data)
83 | {
84 | if (!is_dir($this->targetDir)) {
85 | mkdir($this->targetDir, 0777, true);
86 | }
87 |
88 | file_put_contents(sprintf('%s/%s', $this->targetDir, $fileName), $data);
89 | }
90 |
91 | /**
92 | * Update message catalogues.
93 | *
94 | * @param Message[] $messages
95 | */
96 | public function updateMessageCatalog(array $messages)
97 | {
98 | $this->messages = array_merge($messages, $this->messages);
99 | }
100 |
101 | /**
102 | * Update the file system after the Response has been sent back to the client.
103 | *
104 | * @param Event $event
105 | *
106 | * @throws \ErrorException
107 | * @throws \Exception
108 | */
109 | public function onTerminate(Event $event)
110 | {
111 | if (empty($this->messages)) {
112 | return;
113 | }
114 |
115 | /** @var MessageCatalogue[] $catalogues */
116 | $catalogues = array();
117 | foreach ($this->messages as $m) {
118 | $key = $m->getLocale().$m->getDomain();
119 | if (!isset($catalogues[$key])) {
120 | $file = sprintf('%s/%s.%s.%s', $this->targetDir, $m->getDomain(), $m->getLocale(), $this->getFileExtension());
121 |
122 | try {
123 | $catalogues[$key] = $this->loader->load($file, $m->getLocale(), $m->getDomain());
124 | } catch (NotFoundResourceException $e) {
125 | $catalogues[$key] = new MessageCatalogue($m->getLocale());
126 | }
127 | }
128 |
129 | $translation = $m->getTranslation();
130 | if (empty($translation)) {
131 | if ($this->syncEmptyTranslations) {
132 | $translation = sprintf('[%s]', $m->getId());
133 | } else {
134 | continue;
135 | }
136 | }
137 |
138 | $catalogues[$key]->set($m->getId(), $translation, $m->getDomain());
139 | }
140 |
141 | foreach ($catalogues as $catalogue) {
142 | try {
143 | $this->dumper->dump($catalogue, ['path' => $this->targetDir]);
144 | } catch (\ErrorException $e) {
145 | // Could not save file
146 | // TODO better error handling
147 | throw $e;
148 | }
149 | }
150 | }
151 |
152 | /**
153 | * @return string
154 | */
155 | public function getTargetDir()
156 | {
157 | return $this->targetDir;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/tests/Functional/AppKernel.php:
--------------------------------------------------------------------------------
1 | isAbsolutePath($config)) {
20 | $config = __DIR__.'/config/'.$config;
21 | }
22 |
23 | if (!file_exists($config)) {
24 | throw new \RuntimeException(sprintf('The config file "%s" does not exist.', $config));
25 | }
26 |
27 | $this->config = $config;
28 | }
29 |
30 | public function registerBundles()
31 | {
32 | return array(
33 | new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
34 | new \Symfony\Bundle\SecurityBundle\SecurityBundle(),
35 | new \Happyr\TranslationBundle\HappyrTranslationBundle(),
36 | new \Http\HttplugBundle\HttplugBundle(),
37 | );
38 | }
39 |
40 | public function registerContainerConfiguration(LoaderInterface $loader)
41 | {
42 | $loader->load($this->config);
43 | }
44 |
45 | public function getCacheDir()
46 | {
47 | return sys_get_temp_dir().'/HappyrTranslationBundle';
48 | }
49 |
50 | public function serialize()
51 | {
52 | return $this->config;
53 | }
54 |
55 | public function unserialize($config)
56 | {
57 | $this->__construct($config);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Functional/BaseTestCase.php:
--------------------------------------------------------------------------------
1 | getContainer();
15 | $container->get('happyr.translation.service.loco');
16 | $container->get('happyr.translation.service.blackhole');
17 | $container->get('happyr.translation.service.filesystem');
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Functional/config/default.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: framework.yml }
3 |
4 |
--------------------------------------------------------------------------------
/tests/Functional/config/framework.yml:
--------------------------------------------------------------------------------
1 | framework:
2 | secret: test
3 | test: ~
4 | session:
5 | storage_id: session.storage.mock_file
6 | form: false
7 | csrf_protection: false
8 | validation:
9 | enabled: false
10 | router:
11 | resource: "%kernel.root_dir%/config/routing.yml"
12 |
13 | httplug:
14 | classes:
15 | client: Http\Adapter\Guzzle6\Client
16 | message_factory: Http\Message\MessageFactory\GuzzleMessageFactory
17 | uri_factory: Http\Message\UriFactory\GuzzleUriFactory
18 | stream_factory: Http\Message\StreamFactory\GuzzleStreamFactory
--------------------------------------------------------------------------------
/tests/Functional/config/routing.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Happyr/TranslationBundle/497c042a1b89434b0b874e32c80cec2f5a08cc17/tests/Functional/config/routing.yml
--------------------------------------------------------------------------------