https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-speech-translation-app-on-deno-with-azure-and-vonage/Blog_Speach-Translation_Deno-Azure_1200x600-1.png

Créer une application de traduction vocale sur Deno avec Azure et Vonage

Publié le May 4, 2021

Temps de lecture : 14 minutes

Vonage a récemment lancé Reconnaissance automatique de la parole (ASR) en tant que nouvelle fonctionnalité de la Voice API, ce qui constitue une excellente raison de créer une nouvelle application vocale divertissante pour tirer parti de cette nouvelle capacité !

Dans ce tutoriel, nous allons créer une application vocale fonctionnant sur Deno qui :

  1. Recevoir un appel téléphonique

  2. Accepter le discours prononcé par l'appelant à l'invite

  3. Convertir la parole en texte à l'aide de l'ASR de Vonage

  4. Traduisez-le dans une langue choisie au hasard à l'aide de Microsoft Azure

  5. Répéter le texte original en anglais et le texte nouvellement traduit.

  6. Si le texte nouvellement traduit est accompagné d'un Voice de Vonage disponiblecette Voice sera utilisée.

Nous construisons en utilisant Deno comme environnement d'exécution parce que Deno nous permet de construire une application côté serveur en TypeScript avec des dépendances légères. Il nous permet à la fois d'intégrer uniquement le code externe dont nous avons réellement besoin et de lui donner uniquement les autorisations d'exécution que nous souhaitons.

Il existe plusieurs fournisseurs possibles que nous pouvons intégrer pour fournir une traduction de texte. Dans ce tutoriel, nous utiliserons l'API de traduction vocale de Microsoft Azure Speech Translation API.

C'est parti !

tl;dr Si vous souhaitez passer directement à l'exécution de l'application, vous pouvez trouver une version entièrement fonctionnelle sur GitHub.

Conditions préalables

Pour construire cette application, vous aurez besoin de plusieurs éléments avant que nous puissions commencer à la mettre en œuvre :

Une fois tout cela réglé, nous pouvons passer à la mise en œuvre de l'application.

Compte API Vonage

Pour compléter ce tutoriel, vous aurez besoin d'un Account API de Vonage. Si vous n'en avez pas encore, vous pouvez vous inscrire dès aujourd'hui et commencer à construire avec du crédit gratuit. Une fois que vous avez un Account, vous pouvez trouver votre clé API et votre secret API en haut du tableau de bord de l'API de Vonage.

Ce tutoriel utilise également un numéro de téléphone virtuel. Pour en acheter un, rendez-vous sur Numbers > Acheter des Numbers et recherchez celui qui répond à vos besoins.

Start building with Vonage

Création de la structure des dossiers

La première étape consiste à créer la structure des dossiers pour notre application. Elle se présentera comme suit à la fin :

.
+-- data/
|   +-- languages.ts
|   +-- voices.ts
+-- services/
|   +-- auth/
|     +-- token.ts
|   +-- translate.ts
|   +-- language_picker.ts
|   +-- voice_picker.ts
+-- server.ts
+-- .env

Pour ce tutoriel, nous appellerons le dossier racine de l'application, speech-translation-appmais vous pouvez lui donner le nom que vous souhaitez. Une fois le dossier racine créé, changez de répertoire et créez les fichiers data, services, et services/auth et les sous-dossiers .

Dans le dossier racine, créez server.ts et .env en exécutant touch server.ts .env à partir du répertoire racine.

Effectuez une action similaire à l'intérieur de la data, services, et services/auth en exécutant touch pour créer les fichiers figurant dans l'arborescence ci-dessus.

Création du serveur Deno

Dans votre éditeur de code préféré, ouvrez le fichier server.ts dans le répertoire racine que vous avez créé à la dernière étape.

Dans ce fichier, nous allons instancier un serveur HTTP, lui fournir ses routes et contrôler le flux de l'application.

Nous allons utiliser Opine comme framework web pour le serveur. Opine est un framework minimaliste conçu pour Deno et porté depuis ExpressJS. Si vous êtes familier avec ExpressJS, alors les constructions d'Opine vous sembleront familières.

Pour utiliser Opine, nous devons l'importer au début de notre fichier. Deno, contrairement à NodeJS, n'utilise pas node_modules ou un autre système de gestion de paquets similaire. Par conséquent, chaque paquetage apporté dans votre application est importé directement depuis sa source :

import { opine } from "https://deno.land/x/opine@master/mod.ts";

Une fois que nous avons rendu opine utilisable, nous pouvons l'instancier et créer la structure squelettique de nos itinéraires :

const app = opine();
 app.get("/webhooks/answer", async function (req, res) {
  // Do something on a GET request to /webhooks/answer
});
 app.get("/webhooks/asr", async function (req, res) {
  // Do something on a GET request to /webhooks/asr
});
 app.get("/webhooks/event", async function (req, res) {
  // Do something on a GET request to /webhooks/event
  res.status = 204
})
 app.listen({ port: 8000 });
 console.log("Server is running on port 8000");

Les trois GET énumérées dans le serveur correspondent à trois webhooks uniques de l'API Voice de Vonage. La première est celle où l'API envoie un appel entrant. La deuxième est celle où l'API enverra la parole convertie en texte à l'aide de la fonction de reconnaissance vocale automatique de Vonage. Enfin, la troisième route est celle où sont envoyées toutes les données relatives au cycle de vie de l'appel.

Nous devons fournir une logique pour chacun de ces trois itinéraires qui contrôlera le fonctionnement de notre application : Cette conversation a été marquée comme résolue par NJalal7

  • La route des appels entrants se chargera d'enregistrer la voix de l'appelant et de l'envoyer à l'API de Vonage pour la conversion du texte.

  • La seconde route reçoit le texte et l'envoie à l'API Azure Speech Translation pour le traduire dans une deuxième langue. Elle restitue également à l'appelant les messages originaux et traduits.

  • La route finale reçoit toutes les données relatives aux événements du cycle de vie de l'appel et accuse réception de ces données.

Définir les itinéraires

Construisons la logique de l'appel entrant /webhooks/answer route.

À l'intérieur de la route, nous devons assigner l'ID de l'appelant (UUID) à une variable afin de pouvoir l'utiliser ultérieurement. L'identifiant de l'appelant ( ) est un composant nécessaire de la demande ASR. UUID est un composant nécessaire de la demande ASR :

const uuid = req.query.uuid

Ensuite, nous devons répondre avec un code d'état HTTP de 200 et renvoyer en réponse un Nexmo Call Control Object (NCCO), qui est un objet JSON contenant l'ensemble des instructions que nous souhaitons que l'API de Vonage exécute :

res.json([
  {
    action: 'talk',
    text: 'Welcome to the Vonage Universal Translator Randomizer brought to you by Vonage Automatic Speech Recognition run on Deno. Please say something.',
    bargeIn: true
  },
  {
    eventUrl: [asrWebhook],
    eventMethod: 'GET',
    action: 'input',
    speech: {
      uuid: [uuid],
      language: 'en-us'
    }
  }
]);

Comme vous pouvez le constater, le BCN est composé de deux actions : talk et inputrespectivement.

L'action talk accueille l'appelant et lui demande de dire quelque chose. Elle définit également un paramètre bargeIn à égal truece qui permet à l'appelant de commencer à parler avant la fin du message.

L'action input est l'action par laquelle nous acceptons l'entrée vocale de l'appelant. Dans cette action, nous définissons quelques paramètres uniques :

  • eventUrl: Où envoyer le texte converti en parole. Dans l'action, nous définissons l'URL comme une variable appelée asrWebhook. Nous la créerons plus tard.

  • eventMethod: Le verbe HTTP à utiliser pour envoyer la synthèse vocale terminée. Dans ce cas, nous utilisons GET.

  • action: Le paramètre de base pour toutes les actions NCCO. Sa valeur est égale à l'action que vous souhaitez effectuer, dans le cas présent input.

  • speech: Un paramètre dont la valeur est égale à un objet qui contient les données de l'appelant et de l'utilisateur. UUID de l'appelant et le language de la parole qui est convertie en texte.

Au total, ce premier itinéraire GET ressemble à ce qui suit :

app.get("/webhooks/answer", async function (req, res) {
  const uuid = req.query.uuid
  res.status = 200
  res.json([
    {
      action: 'talk',
      text: 'Welcome to the Vonage Universal Translator Randomizer brought to you by Vonage Automatic Speech Recognition run on Deno. Please say something.',
      bargeIn: true
    },
    {
      eventUrl: [asrWebhook],
      eventMethod: 'GET',
      action: 'input',
      speech: {
        uuid: [uuid],
        language: 'en-us'
      }
    }
  ]);
});

La deuxième route que nous devons définir est la route /webhooks/asr qui recevra la conversion de la parole en texte de l'API Vonage et agira en conséquence.

Il y a quelques valeurs que nous voulons assigner à des variables à utiliser. La première est le résultat de la conversion ASR, qui nous est fourni sous la forme d'un tableau d'objets. Les objets sont classés par ordre décroissant de probabilité de précision. La deuxième variable contiendra le texte de l'objet dont la probabilité de précision est la plus élevée.

Nous instancions la deuxième variable comme une variable vide et lui attribuons une valeur basée sur la condition de savoir si Vonage ASR a été en mesure de capter la parole offerte par l'appelant. Si la parole a été reconnue, cette valeur est utilisée. En revanche, si la parole n'a pas été reconnue, une valeur par défaut est fournie et un message s'affiche dans la console pour en expliquer la raison.

Dans les deux dernières variables que nous créons, nous attribuons la valeur du choix aléatoire de la langue dans laquelle le discours doit être traduit, et nous choisissons la voix avec laquelle la traduction doit être prononcée. Nous partageons ensuite les informations relatives à la langue et à la voix dans la console :

const data = await JSON.parse(req.query.speech)
var mostConfidentResultsText;
if (!data.results) {
  console.log("Vonage ASR did not pick up what you tried to say");
  mostConfidentResultsText = 'Vonage ASR did not pick up your speech. Please call back and try again.';
} else {
  mostConfidentResultsText = data.results[0].text;
};
const languageChoice = languagePicker(languageList);
const voiceChoice = voicePicker(voicesList, languageChoice);
console.log(`Language to translate into: ${languageChoice.name} and Vonage language voice being used: ${voiceChoice}`);

Ensuite, nous définissons le code de statut HTTP de la réponse à 200 comme dans la première route, et nous répondons avec un autre objet JSON NCCO :

res.status = 200
res.json([
  {
    action: 'talk',
    text: `This is what you said in English: ${mostConfidentResultsText}`
  },
  {
    action: 'talk',
    text: `This is your text translated into ${languageChoice.name}`
  },
  {
    action: 'talk',
    text: `${await translateText(languageChoice.code.split('-')[0], mostConfidentResultsText)}`,
    voiceName: voiceChoice
  }
])

Cet objet NCCO contient trois actions talk actions, chacune d'entre elles comportant des variables et des fonctions qui doivent être créées. Nous le ferons une fois que nous aurons fini de définir les itinéraires.

La première action talk répond à l'appelant en anglais, tel qu'il a été compris lors de la conversion par reconnaissance automatique de la parole.

La deuxième action talk indique à l'appelant dans quelle langue son message a été traduit.

La troisième action talk indique à l'appelant son nouveau message traduit. Elle utilise également le paramètre voiceName pour prononcer le message traduit avec la voix désignée pour la langue, s'il en existe une pour cette langue.

La dernière route que nous devons définir est une route courte. C'est celle qui recevra le reste des données de l'événement webhook pour l'appel. Dans ce tutoriel, nous ne ferons rien d'autre avec ces données que d'accuser réception. Nous accusons réception en renvoyant un code d'état 204 qui équivaut à dire que le message a été reçu avec succès et qu'il n'y a pas de contenu à répondre :

app.get("/webhooks/event", async function (req, res) {
  res.status = 204
})

Le serveur étant défini, nous sommes prêts à construire les fonctions d'aide que nous avons invoquées dans les routes du serveur.

Création des services et des données

Revenons au début du fichier server.ts et ajoutons quelques instructions d'importation supplémentaires pour les fonctions et les données qui seront définies :

import { languageList } from "./data/languages.ts";
import { voicesList } from "./data/voices.ts";
import { translateText } from "./services/translate.ts";
import { voicePicker } from "./services/voice_picker.ts";
import { languagePicker } from "./services/language_picker.ts";

Comme l'indique l'extrait ci-dessus, nous devons créer les cinq éléments suivants :

  • languageList: Un tableau des langues possibles pour traduire le message.

  • voicesList: Un tableau de voix possibles pour prononcer le message traduit.

  • translateText: La fonction de traduction du texte dans la deuxième langue

  • voicePicker: La fonction permettant de choisir une voix pour prononcer le texte traduit avec.

  • languagePicker: La fonction permettant de choisir la langue dans laquelle le texte doit être traduit

Nous allons maintenant construire chacun d'entre eux.

Définir les données

Tout d'abord, ajoutons quelques données à notre application.

Nous devons ajouter deux données : une liste de langues et une liste de voix pour parler ces langues.

La liste des langues prises en charge est dérivée du Guide ASR de Vonage. La liste des noms de voix est également dérivée du Voice API Guide de Vonage.

Ouvrez le fichier data/languages.ts et nous allons y ajouter un tableau d'objets :

export const languageList = [
  { "name": "Afrikaans (South Africa)", "code": "af-ZA" },
  { "name": "Albanian (Albania)", "code": "sq-AL" },
  { "name": "Amharic (Ethiopia)", "code": "am-ET" },
  { "name": "Arabic (Algeria)", "code": "ar-DZ" },
  { "name": "Arabic (Bahrain)", "code": "ar-BH" },
  { "name": "Arabic (Egypt)", "code": "ar-EG" },
  { "name": "Arabic (Iraq)", "code": "ar-IQ" },
  { "name": "Arabic (Israel)", "code": "ar-IL" },
  { "name": "Arabic (Jordan)", "code": "ar-JO" },
  { "name": "Arabic (Kuwait)", "code": "ar-KW" },
  { "name": "Arabic (Lebanon)", "code": "ar-LB" },
  { "name": "Arabic (Morocco)", "code": "ar-MA" },
  { "name": "Arabic (Oman)", "code": "ar-OM" },
  { "name": "Arabic (Qatar)", "code": "ar-QA" },
  { "name": "Arabic (Saudi Arabia)", "code": "ar-SA" },
  { "name": "Arabic (State of Palestine)", "code": "ar-PS" },
  { "name": "Arabic (Tunisia)", "code": "ar-TN" },
  { "name": "Arabic (United Arab Emirates)", "code": "ar-AE" },
  { "name": "Armenian (Armenia)", "code": "hy-AM" },
  { "name": "Azerbaijani (Azerbaijan)", "code": "az-AZ" },
  { "name": "Basque (Spain)", "code": "eu-ES" },
  { "name": "Bengali (Bangladesh)", "code": "bn-BD" },
  { "name": "Bengali (India)", "code": "bn-IN" },
  { "name": "Bulgarian (Bulgaria)", "code": "bg-BG" },
  { "name": "Catalan (Spain)", "code": "ca-ES" },
  { "name": "Chinese, Mandarin (Simplified, China)", "code": "zh" },
  { "name": "Croatian (Croatia)", "code": "hr-HR" },
  { "name": "Czech (Czech Republic)", "code": "cs-CZ" },
  { "name": "Danish (Denmark)", "code": "da-DK" },
  { "name": "Dutch (Netherlands)", "code": "nl-NL" },
  { "name": "English (Australia)", "code": "en-AU" },
  { "name": "English (Canada)", "code": "en-CA" },
  { "name": "English (Ghana)", "code": "en-GH" },
  { "name": "English (India)", "code": "en-IN" },
  { "name": "English (Ireland)", "code": "en-IE" },
  { "name": "English (Kenya)", "code": "en-KE" },
  { "name": "English (New Zealand)", "code": "en-NZ" },
  { "name": "English (Nigeria)", "code": "en-NG" },
  { "name": "English (Philippines)", "code": "en-PH" },
  { "name": "English (South Africa)", "code": "en-ZA" },
  { "name": "English (Tanzania)", "code": "en-TZ" },
  { "name": "English (United Kingdom)", "code": "en-GB" },
  { "name": "English (United States)", "code": "en-US" },
  { "name": "Finnish (Finland)", "code": "fi-FI" },
  { "name": "French (Canada)", "code": "fr-CA" },
  { "name": "French (France)", "code": "fr-FR" },
  { "name": "Galician (Spain)", "code": "gl-ES" },
  { "name": "Georgian (Georgia)", "code": "ka-GE" },
  { "name": "German (Germany)", "code": "de-DE" },
  { "name": "Greek (Greece)", "code": "el-GR" },
  { "name": "Gujarati (India)", "code": "gu-IN" },
  { "name": "Hebrew (Israel)", "code": "he-IL" },
  { "name": "Hindi (India)", "code": "hi-IN" },
  { "name": "Hungarian (Hungary)", "code": "hu-HU" },
  { "name": "Icelandic (Iceland)", "code": "is-IS" },
  { "name": "Indonesian (Indonesia)", "code": "id-ID" },
  { "name": "Italian (Italy)", "code": "it-IT" },
  { "name": "Japanese (Japan)", "code": "ja-JP" },
  { "name": "Javanese (Indonesia)", "code": "jv-ID" },
  { "name": "Kannada (India)", "code": "kn-IN" },
  { "name": "Khmer (Cambodia)", "code": "km-KH" },
  { "name": "Korean (South Korea)", "code": "ko-KR" },
  { "name": "Lao (Laos)", "code": "lo-LA" },
  { "name": "Latvian (Latvia)", "code": "lv-LV" },
  { "name": "Lithuanian (Lithuania)", "code": "lt-LT" },
  { "name": "Malay (Malaysia)", "code":  "ms-MY" },
  { "name": "Malayalam (India)", "code": "ml-IN" }, 
  { "name": "Marathi (India)", "code": "mr-IN" },
  { "name": "Nepali (Nepal)", "code":  "ne-NP"},
  { "name": "Norwegian Bokmål (Norway)",  "code": "nb-NO"},
  { "name": "Persian (Iran)", "code":  "fa-IR"},
  { "name": "Polish (Poland)", "code":  "pl-PL"},
  { "name": "Portuguese (Brazil)", "code": "pt-BR"},
  { "name": "Portuguese (Portugal)", "code": "pt-PT"},
  { "name": "Romanian (Romania)", "code": "ro-RO"} ,
  { "name": "Russian (Russia)", "code": "ru-RU" },
  { "name": "Serbian (Serbia)", "code": "sr-RS" },
  { "name": "Sinhala (Sri Lanka)", "code": "si-LK" },
  { "name": "Slovak (Slovakia)", "code": "sk-SK" },
  { "name": "Slovenian (Slovenia)", "code": "sl-SI" },
  { "name": "Spanish (Argentina)", "code": "es-AR" },
  { "name": "Spanish (Bolivia)", "code": "es-BO" },
  { "name": "Spanish (Chile)", "code": "es-CL" },
  { "name": "Spanish (Colombia)", "code": "es-CO" },
  { "name": "Spanish (Costa Rica)", "code":  "es-CR" },
  { "name": "Spanish (Dominican Republic)", "code": "es-DO" },
  { "name": "Spanish (Ecuador)", "code": "es-EC" },
  { "name": "Spanish (El Salvador)", "code": "es-SV" },
  { "name": "Spanish (Guatemala)", "code": "es-GT" },
  { "name": "Spanish (Honduras)", "code": "es-HN" },
  { "name": "Spanish (Mexico)", "code": "es-MX" },
  { "name": "Spanish (Nicaragua)", "code": "es-NI" },
  { "name": "Spanish (Panama)", "code": "es-PA" },
  { "name": "Spanish (Paraguay)", "code": "es-PY" },
  { "name": "Spanish (Peru)", "code": "es-PE" },
  { "name": "Spanish (Puerto Rico)", "code": "es-PR" },
  { "name": "Spanish (Spain)", "code": "es-ES" },
  { "name": "Spanish (United States)", "code": "es-US" },
  { "name": "Spanish (Uruguay)", "code": "es-UY" },
  { "name": "Spanish (Venezuela)", "code": "es-VE" },
  { "name": "Sundanese (Indonesia)", "code": "su-ID" },
  { "name": "Swahili (Kenya)", "code": "sw-KE" },
  { "name": "Swahili (Tanzania)", "code": "sw-TZ" },
  { "name": "Swedish (Sweden)", "code": "sv-SE" },
  { "name": "Tamil (India)", "code": "ta-IN" },
  { "name": "Tamil (Malaysia)", "code": "ta-MY" },
  { "name": "Tamil (Singapore)", "code": "ta-SG" },
  { "name": "Tamil (Sri Lanka)", "code": "ta-LK" },
  { "name": "Telugu (India)", "code": "te-IN" },
  { "name": "Thai (Thailand)", "code": "th-TH" },
  { "name": "Turkish (Turkey)", "code": "tr-TR" },
  { "name": "Ukrainian (Ukraine)", "code": "uk-UA" },
  { "name": "Urdu (India)", "code": "ur-IN" },
  { "name": "Urdu (Pakistan)", "code": "ur-PK" },
  { "name": "Vietnamese (Vietnam)", "code": "vi-VN" },
  { "name": "Zulu (South Africa)", "code": "zu-ZA" }
]

Il s'agit de la liste des langues prises en charge au moment de la publication de ce didacticiel. Cette liste est susceptible d'être modifiée et il convient de consulter le guide sur le site web pour obtenir les informations les plus récentes.

Ensuite, ouvrez le fichier data/voices.ts et ajoutez-y un tableau d'objets. Comme pour la liste des langues, les données représentent ici la liste des noms de voix au moment de la publication. Il y a souvent plus d'une Voice par langue. Pour les besoins de ce tutoriel, nous avons supprimé les doublons afin de ne conserver qu'une seule voix par langue :

export const voicesList = [
  { "name": "Salli", "code": "en-US" },
  { "name": "Marlene", "code": "de-DE" },
  { "name": "Nicole", "code": "en-AU" },
  { "name": "Gwyneth", "code": "en-GB" },
  { "name": "Geraint", "code": "cy-GB" },
  { "name": "Raveena", "code": "en-IN" },
  { "name": "Conchita", "code": "es-ES" },
  { "name": "Penelope", "code": "es-US" },
  { "name": "Chantal", "code": "fr-CA" },
  { "name": "Mathieu", "code": "fr-FR" },
  { "name": "Aditi", "code": "hi-IN" },
  { "name": "Dora", "code": "is-IS" },
  { "name": "Carla", "code": "it-IT" },
  { "name": "Liv", "code": "nb-NO" },
  { "name": "Lotte", "code": "nl-NL" },
  { "name": "Jacek", "code": "pl-PL" },
  { "name": "Vitoria", "code": "pt-BR" },
  { "name": "Ines", "code": "pt-PT" },
  { "name": "Carmen", "code": "ro-RO" },
  { "name": "Tatyana", "code": "ru-RU" },
  { "name": "Astrid", "code": "sv-SE" },
  { "name": "Filiz", "code": "tr-TR" },
  { "name": "Mizuki", "code": "ja-JP" },
  { "name": "Seoyeon", "code": "ko-KR" },
  { "name": "Laila", "code": "ara-XWW" },
  { "name": "Damayanti", "code": "ind-IDN" },
  { "name": "Miren", "code": "baq-ESP" },
  { "name": "Sin-Ji", "code": "yue-CHN" },
  { "name": "Jordi", "code": "cat-ESP" },
  { "name": "Montserrat", "code": "cat-ESP" },
  { "name": "Iveta", "code": "ces-CZE" },
  { "name": "Tessa", "code": "eng-ZAF" },
  { "name": "Satu", "code": "fin-FIN" },
  { "name": "Melina", "code": "ell-GRC" },
  { "name": "Carmit", "code": "heb-ISR" },
  { "name": "Lekha", "code": "hin-IND" },
  { "name": "Mariska", "code": "hun-HUN" },
  { "name": "Sora", "code": "kor-KOR" },
  { "name": "Tian-Tian", "code": "cmn-CHN" },
  { "name": "Mei-Jia", "code": "cmn-TWN" },
  { "name": "Nora", "code": "nor-NOR" },
  { "name": "Henrik", "code": "nor-NOR" },
  { "name": "Felipe", "code": "por-BRA" },
  { "name": "Joana", "code": "por-PRT" },
  { "name": "Ioana", "code": "ron-ROU" },
  { "name": "Laura", "code": "slk-SVK" },
  { "name": "Alva", "code": "swe-SWE" },
  { "name": "Kanya", "code": "tha-THA" },
  { "name": "Yelda", "code": "tur-TUR" },
  { "name": "Empar", "code": "spa-ESP" }
]

Définition des fonctions de service

Notre application utilise plusieurs fonctions définies dans services/ qui fournissent les fonctionnalités de base. Nous allons construire chacune d'entre elles à ce stade.

Pour utiliser l'API de traduction vocale Microsoft Azure, nous devons nous authentifier auprès de l'API à l'aide d'un processus en deux étapes. La première étape consiste à obtenir un jeton Web JSON (JWT) à partir du point d'extrémité de l'API de création de jetons, que nous utilisons ensuite dans la deuxième étape lorsque nous effectuons un appel HTTP au point d'extrémité de l'API de traduction.

Ouvrez le fichier services/auth/token.ts et nous y créerons la fonctionnalité permettant d'obtenir un JWT d'Azure. Veuillez noter que cela dépend de la création réussie d'un Account sur Microsoft Azure et de la réception de votre clé API. La fonction lit la clé API à partir d'une variable d'environnement dans notre fichier .env que nous définirons plus tard dans ce tutoriel :

import "https://deno.land/x/dotenv/load.ts";
const azureEndpoint: any = Deno.env.get("AZURE_ENDPOINT");
var data;
 export const getToken = async (key: string | undefined) => {
  if (!key) {
    console.log("You are missing your Azure Subscription Key. You must add it as an environment variable.");
    return;
  };
  if (!azureEndpoint) {
    console.log("You are missing your Azure endpoint definition. You must add it as an environment variable.");
  };
  data = await fetch(`${azureEndpoint.toString()}sts/v1.0/issuetoken`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Content-length': '0',
      'Ocp-Apim-Subscription-Key':key.toString()
    }
  })
  var text = await data.text();
  return text; 
};

La fonction getToken() accepte un paramètre key et, avec le point de terminaison de l'URL Microsoft Azure défini dans votre fichier dotenv, elle effectue une requête fetch() en envoyant votre clé API. La valeur qui revient est votre JWT qui est explicitement renvoyée comme valeur de la fonction. Tout en haut du fichier, nous importons un module dotenv loader de Deno qui nous permet de lire les valeurs du fichier .env qui nous permet de lire les valeurs dans le fichier

Si le key est undefined ou s'il n'y a pas de valeur pour azureEndpointla fonction retournera prématurément et fournira une explication dans la console sur ce qui manquait.

Une fois que nous avons le jeton de getToken()nous sommes prêts à l'utiliser pour construire une fonction d'aide qui appellera l'API de traduction et récupérera le texte traduit.

Ouvrez le fichier services/translate.ts et dans ce fichier, nous allons créer une translateText() fonction :

import { getToken } from './auth/token.ts';
import "https://deno.land/x/dotenv/load.ts";
const azureSubscriptionKey: string | undefined = Deno.env.get("AZURE_SUBSCRIPTION_KEY");
 export const translateText = async (languageCode: string, text: string) => {
  const token =  await getToken(azureSubscriptionKey);
  const response = await fetch(`https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from=en&to=${languageCode}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify([{"text": text}])
  });
  var translation = await response.json();
  return translation[0][<any>"translations"][0][<any>"text"]
};

Cette fonction, comme la précédente, lit notre fichier .env pour obtenir la clé API Azure que nous avons définie. Elle prend deux arguments : le code linguistique à deux lettres et la conversion de la parole en texte.

La fonction crée ensuite deux variables : token et response. La première invoque la fonction getToken() en prenant comme argument la clé de l'API Azure. La seconde invoque une requête fetch() POST au point de terminaison de l'API de traduction vocale Azure en utilisant le code de langue à deux lettres dans les paramètres de la requête. Le JWT généré par la fonction getToken() est transmis dans l'en-tête Authorization dans l'en-tête. L'en-tête body de la requête POST est la conversion de la parole en texte en une chaîne JSON.

La réponse à la demande est conservée dans la variable translation et le texte traduit est renvoyé par la fonction, qui est contenue dans la variable translation[0]["translations][0]["text].

Il nous reste deux fonctions à créer avant de pouvoir définir nos variables d'environnement. .env variables d'environnement.

La première des deux fonctions restantes que nous allons créer choisira au hasard une langue dans la liste des langues pour le texte à traduire.

Ouvrez services/language_picker.ts et ajoutez le code suivant :

export const languagePicker = (languages: any) => {
 const language = languages[Math.floor(Math.random() * languages.length)];
 return language;
}

La fonction utilise un peu de mathématiques pour choisir au hasard un index dans la liste des langues et renvoie la valeur de l'objet dans cet index.

La dernière fonction que nous construirons choisira une voix Vonage pour parler la langue traduite, s'il en existe une pour cette langue. S'il n'en existe pas, elle renverra la voix Salli qui représente l'anglais américain. Nous garantissons également que si la langue choisie est l'un des dialectes régionaux de l'arabe, la voix choisie sera l'une des voix arabes de Vonage.

Ouvrez services/voice_picker.ts et ajoutez-y ce qui suit :

var voiceChoice: any = { "name": "Salli", "code": "en-US" }
 export const voicePicker = (voices: Array<object>, language: any) => {
  voiceChoice = voices.find((voice: any) => voice.code === language.code)
  if (language.code.split('-')[0] === 'ar') {
    voiceChoice = { "name": "Laila", "code": "ara-XWW" }
  };
  if (voiceChoice === undefined) {
    voiceChoice = { "name": "Salli", "code": "en-US" }
  };
  return voiceChoice.name;
};

Voilà pour toutes les fonctions ! Si vous êtes arrivés jusqu'ici, nous sommes presque à la ligne d'arrivée.

Les derniers éléments dont nous devons nous occuper sont l'attribution des valeurs à nos variables d'environnement et le provisionnement d'un numéro de téléphone virtuel Vonage. .env et de fournir un numéro de téléphone virtuel Vonage.

Définition des variables d'environnement

Il y a trois valeurs que nous devons assigner dans le fichier .env nous devons attribuer trois valeurs :

  • AZURE_SUBSCRIPTION_KEY

  • AZURE_ENDPOINT

  • VONAGE_ASR_WEBHOOK

Les deux premiers sont respectivement notre clé API Azure et notre point de terminaison URL Azure.

Cette dernière est l'URL du webhook pour les données renvoyées par la fonction de reconnaissance vocale automatique de Vonage. Cette dernière valeur doit être une URL accessible de l'extérieur. Un bon outil à utiliser pendant le développement est ngrok pour rendre votre environnement local accessible à l'extérieur. Vous pouvez trouver un guide pour configurer ngrok localement sur notre site web des développeurs.

Approvisionnement d'un numéro de téléphone virtuel Vonage

Il y a deux façons d'approvisionner un numéro de téléphone virtuel Vonage. Une fois que vous avez un Account de développeur Vonage vous pouvez acheter un numéro de téléphone via le tableau de bord ou en utilisant le CLI de Vonage. Nous le ferons ici à l'aide de l'interface de programmation.

Pour installer le CLI, vous pouvez utiliser soit yarn soit npm : yarn global add @vonage/cli ou npm install @vonage/cli -g. Après l'installation, vous devez lui fournir vos identifiants API obtenus depuis le tableau de bord :

vonage config:set --apiKey=VONAGE_API_KEY --apiSecret=VONAGE_API_SECRET

Maintenant que votre CLI est configuré, vous pouvez l'utiliser pour rechercher les numéros disponibles dans votre pays. Pour ce faire, exécutez la commande suivante en utilisant le code à deux lettres de votre pays. L'exemple ci-dessous montre une recherche de numéros aux États-Unis. Veillez à ajouter l'indicateur --features=VOICE pour ne renvoyer que les numéros dotés d'une fonction vocale :

vonage numbers:search US --features=VOICE

Une fois que vous avez trouvé le numéro qui vous intéresse, vous pouvez également l'acheter à l'aide de la CLI :

vonage numbers:buy NUMBER COUNTRYCODE

Il vous sera demandé de taper confirm après avoir envoyé la commande pour acheter officiellement le numéro.

Puisque nous créons une application vocale, nous devons également créer une Application Voice. Cette opération peut également être réalisée à l'aide de l'interface de programmation et, une fois terminée, nous pouvons lier le numéro de téléphone récemment provisionné à l'application. Vous pouvez également utiliser la création de l'application pour lui fournir les URL du webhook de réponse et du webhook d'événement. Si vous créez une application en cours de développement, c'est le bon moment pour créer votre serveur ngrok et lui fournir les URLs ngrok :

vonage apps:create APP_NAME --voice_answer_url=https://www.example.com/answer --voice_event_url=https://www.example.com/event

La commande vous renverra l'identifiant de la demande : Application ID: asdasdas-asdd-2344-2344-asdasdasd345. Nous allons maintenant utiliser cet identifiant pour relier l'application au numéro de téléphone :

vonage apps:link APP_ID --number=YOUR_VONAGE_NUMBER

Une fois ces commandes terminées, vous êtes prêt à exécuter votre application !

Exécution de l'application

Pour utiliser votre application, démarrez à la fois votre serveur ngrok et votre serveur web Deno. Pour démarrer l'application Deno, exécutez ce qui suit à partir du dossier racine :

deno run --allow-read --allow-env --allow-net server.ts

Maintenant qu'il fonctionne, vous pouvez appeler votre numéro de téléphone provisionné par Vonage et suivre l'invite pour dire un message. Votre message sera converti en texte à l'aide de la fonction de reconnaissance vocale automatique de Vonage, puis traduit dans une deuxième langue aléatoire à l'aide de Microsoft Azure, avant de vous être retransmis. Bonne lecture !

Partager:

https://a.storyblok.com/f/270183/384x384/e5480d2945/ben-greenberg.png
Ben GreenbergAnciens de Vonage

Ben est un développeur en seconde carrière qui a auparavant passé une décennie dans les domaines de la formation pour adultes, de l'organisation communautaire et de la gestion d'organisations à but non lucratif. Il a travaillé comme défenseur des développeurs pour Vonage. Il écrit régulièrement sur l'intersection du développement communautaire et de la technologie. Originaire de Californie du Sud et ayant longtemps vécu à New York, Ben réside aujourd'hui près de Tel Aviv, en Israël.