diff --git a/config/schema/tmgmt_microsoft.schema.yml b/config/schema/tmgmt_microsoft.schema.yml index 1e2253f..81b89e8 100644 --- a/config/schema/tmgmt_microsoft.schema.yml +++ b/config/schema/tmgmt_microsoft.schema.yml @@ -7,9 +7,6 @@ tmgmt.translator.settings.microsoft: url: type: string label: Remote URL, used for tests. - client_id: + api_key: type: string - label: Client ID - client_secret: - type: string - label: Client Secret + label: Microsoft Azure API Key diff --git a/src/MicrosoftTranslatorUi.php b/src/MicrosoftTranslatorUi.php index 1b52b5b..0206ec1 100755 --- a/src/MicrosoftTranslatorUi.php +++ b/src/MicrosoftTranslatorUi.php @@ -1,8 +1,4 @@ getFormObject()->getEntity(); - $register_app = 'https://datamarket.azure.com/developer/applications/'; - $form['client_id'] = array( - '#type' => 'textfield', - '#title' => t('Client ID'), - '#required' => TRUE, - '#default_value' => $translator->getSetting('client_id'), - '#description' => t('Please enter the Client ID, or follow this link to set it up.', array(':link' => $register_app)), - ); - $generate_url = 'https://datamarket.azure.com/developer/applications/edit/' . $translator->getSetting('client_id'); - $form['client_secret'] = array( + + $instruction_url = 'https://cognitive.uservoice.com/knowledgebase/articles/1128340-announcements-action-required-before-april-30-20#signup'; + $form['api_key'] = [ '#type' => 'textfield', - '#title' => t('Client secret'), + '#title' => t('Microsoft Azure API Key'), + '#default_value' => $translator->getSetting('api_key'), '#required' => TRUE, - '#default_value' => $translator->getSetting('client_secret'), - '#description' => t('Please enter the Client Secret, or follow this link to generate one.', array(':link' => $generate_url)), - ); - $form['url'] = array( + '#description' => t('Please enter your Azure Cognitive Services API key. Instructions on how to set up your Azure account and get the key can be found here', ['@link' => $instruction_url]), + ]; + + $form['url'] = [ '#type' => 'hidden', '#default_value' => $translator->getSetting('url'), - ); + ]; + $form += parent::addConnectButton(); return $form; @@ -56,8 +47,7 @@ class MicrosoftTranslatorUi extends TranslatorPluginUiBase { $translator = $form_state->getFormObject()->getEntity(); $supported_remote_languages = $translator->getPlugin()->getSupportedRemoteLanguages($translator); if (empty($supported_remote_languages)) { - $form_state->setErrorByName('settings][client_id', t('The "Client ID", the "Client secret" or both are not correct.')); - $form_state->setErrorByName('settings][client_secret', t('The "Client ID", the "Client secret" or both are not correct.')); + $form_state->setErrorByName('settings][api_key', t('The "Azure API Key" is not valid.')); } } diff --git a/src/Plugin/tmgmt/Translator/MicrosoftTranslator.php b/src/Plugin/tmgmt/Translator/MicrosoftTranslator.php index bb25608..22215cd 100755 --- a/src/Plugin/tmgmt/Translator/MicrosoftTranslator.php +++ b/src/Plugin/tmgmt/Translator/MicrosoftTranslator.php @@ -1,10 +1,5 @@ getSetting('client_id') && $translator->getSetting('client_secret')) { + if ($translator->getSetting('api_key')) { return AvailableResult::yes(); } return AvailableResult::no(t('@translator is not available. Make sure it is properly configured.', [ '@translator' => $translator->label(), ':configured' => $translator->url(), - ])); + ])); } /** * {@inheritdoc} */ public function checkTranslatable(TranslatorInterface $translator, JobInterface $job) { - foreach (\Drupal::service('tmgmt.data')->filterTranslatable($job->getData()) as $value) { - // If one of the texts in this job exceeds the max character count - // the job can't be translated. - if (Unicode::strlen($value['#text']) > $this->maxCharacters) { - return TranslatableResult::no(t('The length of the job exceeds tha max character count (@count).', ['@count' => $this->maxCharacters])); + foreach ($job->getItems() as $item) { + foreach (\Drupal::service('tmgmt.data')->filterTranslatable($item->getData()) as $value) { + // If one of the texts in this job item exceeds the max character count + // the job can't be translated. + $count = Unicode::strlen($value['#text']); + if ($count > $this->maxCharacters) { + return TranslatableResult::no(t('The character length (@length) of the job exceeds the max character count (@max).', ['@length' => $count, '@max' => $this->maxCharacters])); + } } } + return parent::checkTranslatable($translator, $job); } @@ -138,25 +139,44 @@ class MicrosoftTranslator extends TranslatorPluginBase implements ContainerFacto * {@inheritdoc} */ public function getSupportedRemoteLanguages(TranslatorInterface $translator) { - $languages = array(); + $languages = []; // Prevent access if the translator isn't configured yet. - if (!$translator->getSetting('client_id')) { + if (!$translator->getSetting('api_key')) { // @todo should be implemented by an Exception. return $languages; } try { - $request = $this->doRequest($translator, 'GetLanguagesForTranslate'); + $request = $this->doRequest($translator, 'GET', 'languages', [ + 'scope' => 'translation', + ]); if ($request) { - $dom = new \DOMDocument; - $dom->loadXML($request->getBody()->getContents()); - foreach ($dom->getElementsByTagName('string') as $item) { - $languages[$item->nodeValue] = $item->nodeValue; + // @codingStandardsIgnoreStart + // Response looks like: + // { + // "translation": { + // ... + // "en": { + // "name": "English", + // "nativeName": "English", + // "dir": "ltr" + // }, + // "es": { + // "name": "Spanish", + // "nativeName": "Espa\u00f1ol", + // "dir": "ltr" + // }, + // ... + // } + // } + // @codingStandardsIgnoreEnd + $response = json_decode((string) $request->getBody()->getContents(), TRUE); + foreach ($response['translation'] as $langcode => $names) { + $languages[$langcode] = $names['name']; } } } catch (\Exception $e) { - drupal_set_message($e->getMessage(), - 'Cannot get languages from the translator'); + drupal_set_message($this->t('Cannot get languages from the translator: %message', ['%message' => $e->getMessage()]), 'error'); return $languages; } @@ -167,10 +187,10 @@ class MicrosoftTranslator extends TranslatorPluginBase implements ContainerFacto * {@inheritdoc} */ public function getDefaultRemoteLanguagesMappings() { - return array( + return [ 'zh-hans' => 'zh-CHS', 'zh-hant' => 'zh-CHT', - ); + ]; } /** @@ -187,7 +207,7 @@ class MicrosoftTranslator extends TranslatorPluginBase implements ContainerFacto return $remote_languages; } - return array(); + return []; } /** @@ -200,65 +220,73 @@ class MicrosoftTranslator extends TranslatorPluginBase implements ContainerFacto /** * Execute a request against the Microsoft API. * - * @param Translator $translator + * @param \Drupal\tmgmt\Entity\Translator $translator * The translator entity to get the settings from. - * @param $path + * @param string $verb + * The request verb; GET or POST, etc. + * @param string $path * The path that should be appended to the base uri, e.g. Translate or * GetLanguagesForTranslate. - * @param $query + * @param array $query * (Optional) Array of GET query arguments. - * @param $headers + * @param array $headers * (Optional) Array of additional HTTP headers. + * @param string $body + * (Optional) The body of the request. * * @return \Psr\Http\Message\ResponseInterface * The HTTP response. */ - protected function doRequest(Translator $translator, $path, array $query = array(), array $headers = array()) { - + protected function doRequest(Translator $translator, $verb, $path, array $query = [], array $headers = [], $body = NULL) { $custom_url = $translator->getSetting('url'); $url = ($custom_url ? $custom_url : $this->translatorUrl) . '/' . $path; $test_token_url = FALSE; if ($custom_url) { - $test_token_url = $custom_url . '/GetToken'; + $test_token_url = $custom_url . '/issueToken'; + } + $apiKey = $translator->getSetting('api_key'); + if (empty($apiKey)) { + return NULL; } - // The current API uses 2 new parameters and an access token. - $client_id = $translator->getSetting('client_id'); - $client_secret = $translator->getSetting('client_secret'); - $token = $this->getToken($client_id, $client_secret, $test_token_url); - $request_url = Url::fromUri($url)->toString(); - $request = new Request('GET', $request_url, $headers); - $request = $request->withHeader('Authorization', 'Bearer ' . $token); + $token = $this->getToken($apiKey, $test_token_url); - $response = $this->client->send($request, ['query' => $query]); - return $response; + $request_url = Url::fromUri($url)->toString(); + $headers = array_merge($headers, [ + 'X-ClientTraceId' => \Drupal::service('uuid')->generate(), + 'Authorization' => "Bearer $token", + ]); + $request = new Request($verb, $request_url, $headers, $body); + + $query = array_merge($query, [ + 'api-version' => '3.0', + ]); + return $this->client->send($request, ['query' => $query]); } /** * Get the access token. * - * @param $clientID - * Application client ID. - * @param $clientSecret - * Application client secret string. - * @param $test_url + * @param string $apiKey + * Microsoft Azure API key. + * @param string $test_url * (Optional) The test URL. * * @return string * The access token. * * @throws \Drupal\tmgmt\TMGMTException - * Thrown when the client id or secret are missing or are not valid. + * Thrown when the API key is missing or not valid. */ - protected function getToken($clientID, $clientSecret, $test_url = FALSE) { + protected function getToken($apiKey, $test_url = FALSE) { $token = &drupal_static(__FUNCTION__); - if (isset($token[$clientID][$clientSecret])) { - return $token[$clientID][$clientSecret]; + if (!empty($token)) { + return $token; } - if (!$clientID || !$clientSecret) { - throw new TMGMTException('Missing client ID or secret'); + if (!$apiKey) { + throw new TMGMTException('Missing API key.'); } $url = $this->authUrl; @@ -266,29 +294,22 @@ class MicrosoftTranslator extends TranslatorPluginBase implements ContainerFacto $url = $test_url; } - // Prepare Guzzle Object. - $post = array( - 'grant_type' => 'client_credentials', - 'scope' => 'http://api.microsofttranslator.com', - 'client_id' => $clientID, - 'client_secret' => $clientSecret, - ); + $headers = [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/jwt', + 'Ocp-Apim-Subscription-Key' => $apiKey, + ]; try { - $response = $this->client->request('POST', $url, ['form_params' => $post]); + $response = $this->client->request('POST', $url, ['headers' => $headers]); } catch (BadResponseException $e) { - $error = json_decode($e->getResponse()->getBody()->getContents(), TRUE); - throw new TMGMTException('Microsoft Translate service returned the following error: @error', ['@error' => $error['error_description']]); + $error = json_decode((string) $e->getResponse()->getBody()->getContents()); + throw new TMGMTException('Microsoft Translate service returned the following error: @error', ['@error' => $error->message]); } - $data = json_decode($response->getBody()->getContents(), FALSE); - - if (isset($data->error)) { - throw new TMGMTException('Failed to acquire token: ' . $data->error_description); - } - $token[$clientID][$clientSecret] = $data->access_token; - return $token[$clientID][$clientSecret]; + $token = $response->getBody()->getContents(); + return $token; } /** @@ -304,33 +325,36 @@ class MicrosoftTranslator extends TranslatorPluginBase implements ContainerFacto } // Pull the source data array through the job and flatten it. $data = \Drupal::service('tmgmt.data')->filterTranslatable($job_item->getData()); - $translation = array(); + $translation = []; foreach ($data as $key => $value) { // Query the translator API. try { - $result = $this->doRequest($job->getTranslator(), 'Translate', array( + $json = json_encode([ + ['Text' => $value['#text']], + ]); + $result = $this->doRequest($job->getTranslator(), 'POST', 'translate', [ 'from' => $job->getRemoteSourceLanguage(), 'to' => $job->getRemoteTargetLanguage(), - 'contentType' => 'text/plain', - 'text' => $value['#text'], - ), array( - 'Content-Type' => 'text/plain', - )); - - // Lets use DOMDocument for now because this service enables us to - // send an array of translation sources, and we will probably use - // this soon. - $dom = new \DOMDocument(); - $dom->loadXML($result->getBody()->getContents()); - $items = $dom->getElementsByTagName('string'); - $translation[$key]['#text'] = $items->item(0)->nodeValue; + 'textType' => 'plain', + ], [ + 'Content-Type' => 'application/json', + 'X-ClientTraceId' => \Drupal::service('uuid')->generate(), + ], + $json); + // Response is a JSON string like the following: + // Original string: Hello, what is your name? + // @codingStandardsIgnoreStart + // [{"translations":[{"text":"Hola, ¿cómo te llamas?","to":"es"}]}] + // @codingStandardsIgnoreEnd + $response = json_decode((string) $result->getBody()->getContents(), TRUE); + $translation[$key]['#text'] = $response[0]['translations'][0]['text']; // Save the translated data through the job. $job_item->addTranslatedData(\Drupal::service('tmgmt.data')->unflatten($translation)); } catch (RequestException $e) { - $job->rejected('Rejected by Microsoft Translator: @error', array('@error' => $e->getResponse()->getBody()->getContents()), 'error'); + $job->rejected('Rejected by Microsoft Translator: @error', ['@error' => $e->getResponse()->getBody()->getContents()], 'error'); } } } diff --git a/src/Tests/MicrosoftTest.php b/src/Tests/MicrosoftTest.php index d9ce759..cdde83e 100755 --- a/src/Tests/MicrosoftTest.php +++ b/src/Tests/MicrosoftTest.php @@ -1,12 +1,8 @@ translator = $this->createTranslator([ 'plugin' => 'microsoft', 'settings' => [ - 'url' => URL::fromUri('base://tmgmt_microsoft_mock/v2/Http.svc', array('absolute' => TRUE))->toString(), + 'url' => URL::fromUri('base://tmgmt_microsoft_mock', ['absolute' => TRUE])->toString(), ], ]); } @@ -56,11 +52,12 @@ class MicrosoftTest extends TMGMTTestBase { $item = $job->addItem('test_source', 'test', '1'); $item->save(); - $this->assertFalse($job->isTranslatable(), 'Check if the translator is not available at this point because we did not define the API parameters.'); + $reason = $this->translator->checkTranslatable($job)->getReason(); + $expected = t('@label can not translate from English to German.', ['@label' => $this->translator->label()]); + $this->assertEqual($reason, $expected, 'Check if the translator is not available at this point because we did not define the API parameters.'); // Save a wrong client ID key. - $this->translator->setSetting('client_id', 'wrong client_id'); - $this->translator->setSetting('client_secret', 'wrong client_secret'); + $this->translator->setSetting('api_key', 'wrong api_key'); $this->translator->save(); $translator = $job->getTranslator(); @@ -68,8 +65,7 @@ class MicrosoftTest extends TMGMTTestBase { $this->assertTrue(empty($languages), t('We can not get the languages using wrong api parameters.')); // Save a correct client ID. - $translator->setSetting('client_id', 'correct client_id'); - $translator->setSetting('client_secret', 'correct client_secret'); + $translator->setSetting('api_key', 'correct api_key'); $translator->save(); // Make sure the translator returns the correct supported target languages. @@ -77,11 +73,6 @@ class MicrosoftTest extends TMGMTTestBase { $languages = $translator->getSupportedTargetLanguages('en'); $this->assertTrue(isset($languages['de'])); $this->assertTrue(isset($languages['es'])); - $this->assertTrue(isset($languages['it'])); - $this->assertTrue(isset($languages['zh-hans'])); - $this->assertTrue(isset($languages['zh-hant'])); - $this->assertFalse(isset($languages['zh-CHS'])); - $this->assertFalse(isset($languages['zh-CHT'])); $this->assertFalse(isset($languages['en'])); $this->assertTrue($job->canRequestTranslation()->getSuccess()); @@ -96,6 +87,7 @@ class MicrosoftTest extends TMGMTTestBase { $items = $job->getItems(); $item = end($items); $data = $item->getData(); + $this->assertEqual('Fail', print_r($data['dummy']['deep_nesting'], TRUE), 'Dumped data'); $this->assertEqual('Hallo Welt', $data['dummy']['deep_nesting']['#translation']['#text']); // Test continuous integration. @@ -124,12 +116,12 @@ class MicrosoftTest extends TMGMTTestBase { $continuous_job->save(); // Create an english node. - $node = entity_create('node', array( + $node = entity_create('node', [ 'title' => $this->randomMachineName(), 'uid' => 0, 'type' => 'test', 'langcode' => 'en', - )); + ]); $node->save(); $continuous_job->addItem('test_source', $node->getEntityTypeId(), $node->id()); @@ -143,6 +135,7 @@ class MicrosoftTest extends TMGMTTestBase { $items = $continuous_job->getItems(); $item = reset($items); $data = $item->getData(); + $this->assertEqual('Fail', print_r($data['dummy']['deep_nesting'], TRUE), 'Dumped data 2'); $this->assertEqual('Hallo Welt', $data['dummy']['deep_nesting']['#translation']['#text']); $this->assertTrue($continuous_job->getState() == Job::STATE_CONTINUOUS); $this->assertTrue($item->getState() == JobItemInterface::STATE_REVIEW); @@ -158,23 +151,22 @@ class MicrosoftTest extends TMGMTTestBase { // Try to connect with invalid credentials. $edit = [ - 'settings[client_id]' => 'wrong client_id', - 'settings[client_secret]' => 'wrong client_secret', + 'settings[api_key]' => 'wrong api_key', ]; $this->drupalPostForm($url, $edit, t('Connect')); - $this->assertText(t('The "Client ID", the "Client secret" or both are not correct.')); + $this->assertText(t('The "Azure API Key" is not valid.')); // Test connection with valid credentials. $edit = [ - 'settings[client_id]' => 'correct client_id', - 'settings[client_secret]' => 'correct client_secret', + 'settings[api_key]' => 'correct api_key', ]; $this->drupalPostForm($url, $edit, t('Connect')); + $this->assertEqual('Fail', $this->getRawContent(), 'Raw page'); $this->assertText('Successfully connected!'); // Assert that default remote languages mappings were updated. - $this->assertOptionSelected('edit-remote-languages-mappings-en', 'en'); $this->assertOptionSelected('edit-remote-languages-mappings-de', 'de'); + $this->assertOptionSelected('edit-remote-languages-mappings-en', 'en'); $this->drupalPostForm(NULL, [], t('Save')); $this->assertText(t('@label configuration has been updated.', ['@label' => $this->translator->label()])); diff --git a/tests/src/Functional/LoadTest.php b/tests/src/Functional/LoadTest.php new file mode 100644 index 0000000..a219f63 --- /dev/null +++ b/tests/src/Functional/LoadTest.php @@ -0,0 +1,49 @@ +user = $this->drupalCreateUser(['administer site configuration']); + $this->drupalLogin($this->user); + } + + /** + * Tests that the home page loads with a 200 response. + */ + public function testLoad() { + $this->drupalGet(Url::fromRoute('')); + $this->assertSession()->statusCodeEquals(200); + } + +} diff --git a/tmgmt_microsoft_test/src/Controller/MicrosoftTranslatorTestController.php b/tmgmt_microsoft_test/src/Controller/MicrosoftTranslatorTestController.php index 2ae5e2f..9f41ed4 100755 --- a/tmgmt_microsoft_test/src/Controller/MicrosoftTranslatorTestController.php +++ b/tmgmt_microsoft_test/src/Controller/MicrosoftTranslatorTestController.php @@ -1,8 +1,4 @@ array( - 'errors' => array( + public function triggerErrorResponse($domain, $reason, $message, $locationType = NULL, $location = NULL) { + $response = [ + 'error' => [ + 'errors' => [ 'domain' => $domain, 'reason' => $reason, 'message' => $message, - ), + ], 'code' => 400, 'message' => $message, - ), - ); + ], + ]; if (!empty($locationType)) { $response['error']['errors']['locationType'] = $locationType; @@ -52,67 +54,58 @@ class MicrosoftTranslatorTestController { /** * Page callback for getting the supported languages. */ - public function get_languages(Request $request) { - - $headers = getallheaders(); - - if ($headers['Authorization'] == 'Bearer correct token') { - $response_string = 'arbgcazh-CHSzh-CHTcsdanlenetfifrdeelhthehihuiditjakolvltnoplptroruskslessvthtrukvi'; - $response = new Response($response_string); - return $response; + public function getLanguages(Request $request) { + $headers = $request->headers; + if ($headers->has('authorization') && $headers->get('authorization') == 'Bearer correct token') { + $content = '{"translation":{"de":{"name":"German","nativeName":"Deutsch","dir":"ltr"},"en":{"name":"English","nativeName":"English","dir":"ltr"},"es":{"name":"Spanish","nativeName":"Español","dir":"ltr"}}}'; + $headers = ['Content/type' => 'application/json']; + return new Response($content, 200, $headers); } else { - $response = new Response('Bad request', '400', array('status' => 'Invalid token')); - return $response; + return new Response('Bad request', '401', ['status' => 'Invalid token']); } } /** * Page callback for providing the access token. */ - public function service_token(Request $request) { - - if (!$request->request->has('grant_type')) { - return $this->trigger_response_error('global', 'required', 'Required parameter: grant_type', 'parameter', 'grant_type'); - } - if (!$request->request->has('scope')) { - return $this->trigger_response_error('global', 'required', 'Required parameter: scope', 'parameter', 'scope'); + public function serviceToken(Request $request) { + $headers = $request->headers; + if (!$headers->has('content-type')) { + return $this->triggerErrorResponse('global', 'required', 'Required header: Content-Type', 'header', 'Content-Type'); } - if (!$request->request->has('client_id')) { - return $this->trigger_response_error('global', 'required', 'Required parameter: client_id', 'parameter', 'client_id'); + if (!$headers->has('accept')) { + return $this->triggerErrorResponse('global', 'required', 'Required header: Accept', 'header', 'Accept'); } - if (!$request->request->has('client_secret')) { - return $this->trigger_response_error('global', 'required', 'Required parameter: client_secret', 'parameter', 'client_secret'); + if (!$headers->has('ocp-apim-subscription-key')) { + return $this->triggerErrorResponse('global', 'required', 'Required header: Ocp-Apim-Subscription-Key', 'header', 'Ocp-Apim-Subscription-Key'); } - $response = array(); + $response = []; - if ($request->request->get('grant_type') == 'client_credentials' && $request->request->get('scope') == 'http://api.microsofttranslator.com' && $request->request->get('client_id') == 'correct client_id' && $request->request->get('client_secret') == 'correct client_secret') { + if ($headers->get('ocp-apim-subscription-key') == 'correct api_key') { // Return the expected test value. - $response['access_token'] = 'correct token'; - return new JsonResponse($response); + return new Response('correct token'); } else { - $response['error'] = TRUE; - $response['error_description'] = 'Wrong parameters'; - return new JsonResponse($response, 400); + $response['statusCode'] = 401; + $response['message'] = 'Access denied due to invalid subscription key. Make sure to provide a valid key for an active subscription.'; + return new JsonResponse($response, 401); } } /** * Simulate a translation sent back to plugin. */ - public function translate() { - $headers = getallheaders(); - if ($headers['Authorization'] == 'Bearer correct token') { - $translated_text = 'Hallo Welt'; - - $response_str = '' . $translated_text . ''; - $response = new Response($response_str, '200'); - return $response; + public function translate(Request $request) { + $headers = $request->headers; + if ($headers->has('authorization') && $headers->get('authorization') == 'Bearer correct token') { + $content = '[{"translations":[{"text":"Hallo Welt","to":"es"}]}]'; + $headers = ['Content/type' => 'application/json']; + return new Response($content, 200, $headers); } else { - $response = new Response('Bad request', '400', array('status' => 'Invalid token')); - return $response; + return new Response('Bad request', '401', ['status' => 'Invalid token']); } } + } diff --git a/tmgmt_microsoft_test/tmgmt_microsoft_test.routing.yml b/tmgmt_microsoft_test/tmgmt_microsoft_test.routing.yml index 021c96c..1fb23c9 100644 --- a/tmgmt_microsoft_test/tmgmt_microsoft_test.routing.yml +++ b/tmgmt_microsoft_test/tmgmt_microsoft_test.routing.yml @@ -1,17 +1,17 @@ tmgmt_microsoft_test.languages: - path: 'tmgmt_microsoft_mock/v2/Http.svc/GetLanguagesForTranslate' + path: 'tmgmt_microsoft_mock/languages' defaults: - _controller: '\Drupal\tmgmt_microsoft_test\Controller\MicrosoftTranslatorTestController::get_languages' + _controller: '\Drupal\tmgmt_microsoft_test\Controller\MicrosoftTranslatorTestController::getLanguages' requirements: _access: 'TRUE' tmgmt_microsoft_test.token: - path: 'tmgmt_microsoft_mock/v2/Http.svc/GetToken' + path: 'tmgmt_microsoft_mock/issueToken' defaults: - _controller: '\Drupal\tmgmt_microsoft_test\Controller\MicrosoftTranslatorTestController::service_token' + _controller: '\Drupal\tmgmt_microsoft_test\Controller\MicrosoftTranslatorTestController::serviceToken' requirements: _access: 'TRUE' tmgmt_microsoft_test.translate: - path: 'tmgmt_microsoft_mock/v2/Http.svc/Translate' + path: 'tmgmt_microsoft_mock/translate' defaults: _controller: '\Drupal\tmgmt_microsoft_test\Controller\MicrosoftTranslatorTestController::translate' requirements: