https://a.storyblok.com/f/270183/20709/dd0f60873c/blog_speach-translation_deno-azure_1200x600-1.png

Crie um aplicativo de tradução de fala no Deno com o Azure e a Vonage

Publicado em May 4, 2021

Tempo de leitura: 13 minutos

A Vonage lançou recentemente o Reconhecimento Automático de Fala (ASR) como um novo recurso na Voice API, o que é um ótimo motivo para criar um novo e divertido aplicativo de voz que aproveite essa nova funcionalidade!

Neste tutorial, vamos criar um aplicativo de voz que roda no Deno que irá:

  1. Receber uma ligação

  2. Aceite o que o interlocutor disser quando for solicitado

  3. Converta essa fala em texto usando o ASR da Vonage

  4. Traduza para um idioma escolhido aleatoriamente usando o Microsoft Azure

  5. Leia em voz alta tanto o texto original em inglês quanto o texto recém-traduzido

  6. Se o texto recém-traduzido tiver um o Vonage Voice estiver disponível, essa voz será utilizada

Estamos desenvolvendo usando o Deno como nosso ambiente de execução, pois ele nos permite criar um aplicativo do lado do servidor em TypeScript com dependências leves. Isso nos permite integrar apenas o código externo de que realmente precisamos e conceder a ele apenas as permissões de execução que desejamos que ele tenha.

Existem vários provedores possíveis com os quais podemos nos integrar para oferecer tradução de texto. Neste tutorial, vamos desenvolver utilizando a API de Tradução de Fala do Microsoft Azure.

Vamos começar!

Resumo: Se você quiser pular essa parte e simplesmente rodar o aplicativo, pode encontrar uma versão totalmente funcional no GitHub.

Pré-requisitos

Para desenvolver este aplicativo, você precisará de alguns itens antes de começarmos a implementá-lo:

Depois de resolver tudo isso, podemos seguir em frente e começar a implementar nosso aplicativo.

Account da API da Vonage

Para concluir este tutorial, você precisará de uma Account na API da Vonage. Se você ainda não tiver uma, pode se cadastrar hoje mesmo e começar a desenvolver com crédito gratuito. Depois de criar um Account, você encontrará sua chave de API e seu segredo de API na parte superior do Painel da API da Vonage.

Este tutorial também utiliza um número de telefone virtual. Para adquirir um, acesse Numbers > Comprar Numbers e procure um que atenda às suas necessidades.

Start building with Vonage

Criação da estrutura de pastas

O primeiro passo é criar a estrutura de pastas para o nosso aplicativo. No final, ela ficará assim:

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

Para este tutorial, vamos chamar a pasta raiz do aplicativo de speech-translation-app, mas você pode nomeá-la como quiser. Depois de criar a pasta raiz, acesse-a e crie o arquivo data, services, e services/auth subpastas.

Dentro da pasta raiz, crie server.ts e .env arquivos executando touch server.ts .env a partir do diretório raiz.

Execute uma ação semelhante dentro do data, services, e services/auth pastas, executando touch para criar os arquivos mostrados na árvore de diretórios acima.

Criação do servidor Deno

No seu editor de código preferido, abra o server.ts arquivo no diretório raiz que você criou na etapa anterior.

Neste arquivo, vamos instanciar um servidor HTTP, definir suas rotas e controlar o fluxo da aplicação.

Vamos usar o Opine como nossa estrutura web para o servidor. O Opine é uma estrutura minimalista criada para o Deno, portada do ExpressJS. Se você estiver familiarizado com o ExpressJS, as construções do Opine lhe parecerão familiares.

Para usar o Opine, precisamos importá-lo no início do nosso arquivo. O Deno, ao contrário do NodeJS, não utiliza o node_modules nem outro sistema de gerenciamento de pacotes semelhante. Por isso, cada pacote incorporado à sua aplicação é importado diretamente de sua fonte:

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

Assim que tivermos o opine disponível para uso, podemos instanciar uma instância dele e criar a estrutura básica para nossas rotas:

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");

As três GET solicitações enumeradas no servidor correspondem a três webhooks exclusivos da Voice API da Vonage. O primeiro é aquele para o qual a API envia uma chamada recebida. O segundo é aquele para o qual a API enviará a conversão de fala em texto, utilizando o recurso de Reconhecimento Automático de Fala da Vonage. Por fim, a terceira rota é para onde são enviados todos os dados de eventos relativos ao ciclo de vida da chamada.

Precisamos definir a lógica para cada uma dessas três rotas, que controlará o funcionamento do nosso aplicativo: Esta conversa foi marcada como resolvida por NJalal7

  • A rota de chamadas recebidas será responsável por captar a entrada de voz do chamador e enviá-la à API da Vonage para conversão em texto.

  • A segunda rota receberá o texto e o enviará à API de Tradução de Fala do Azure para traduzi-lo para um segundo idioma. Ela também reproduzirá para o chamador as mensagens original e traduzida.

  • A rota final receberá todos os dados relativos aos eventos do ciclo de vida da chamada e confirmará o recebimento desses dados.

Definindo as rotas

Vamos criar a lógica para a rota da chamada recebida /webhooks/answer .

Dentro da rota, precisamos atribuir o ID do chamador (UUID) a uma variável para que possamos usá-lo mais tarde. O UUID é um componente necessário da solicitação ASR:

const uuid = req.query.uuid

Em seguida, precisamos responder com um código de status HTTP de 200 e enviar de volta, como resposta, um Nexmo Call Control Object (NCCO), que é um objeto JSON contendo o conjunto de instruções que desejamos que a API da Vonage execute:

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'
    }
  }
]);

Como você pode ver, o NCCO é composto por duas ações: talk e input, respectivamente.

A talk ação dá as boas-vindas ao chamador e pede que ele diga algo. Ela também define um parâmetro bargeIn para o valor true, o que permite que o interlocutor comece a falar antes que a mensagem termine.

A input ação é onde aceitamos a entrada de voz do chamador. Nesta ação, definimos alguns parâmetros específicos:

  • eventUrl: Para onde enviar o texto gerado a partir da conversão da fala. Na ação, definimos a URL como uma variável chamada asrWebhook. Vamos criá-la mais tarde.

  • eventMethod: Qual verbo HTTP usar para enviar a transcrição concluída. Neste caso, usamos GET.

  • action: O parâmetro básico para todas as ações do NCCO. Seu valor é igual à ação que você deseja realizar; neste caso, input.

  • speech: Um parâmetro cujo valor é igual a um objeto que contém o UUID do chamador e o language da fala que está sendo convertida em texto.

No total, essa primeira GET rota fica assim:

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'
      }
    }
  ]);
});

A segunda rota que precisamos definir é a /webhooks/asr rotas, que receberá a conversão de fala para texto da API da Vonage e executará as ações necessárias.

Há alguns valores que queremos atribuir às variáveis para utilizá-las. O primeiro é o resultado da conversão ASR, que nos chega na forma de uma matriz de objetos. Os objetos estão ordenados em ordem decrescente de probabilidade de precisão. A segunda variável conterá o texto do objeto com a maior probabilidade de precisão.

Inicializamos a segunda variável como uma variável vazia e atribuímos seu valor com base na condição de se o Vonage ASR conseguiu reconhecer a fala do chamador. Se a fala foi reconhecida, esse valor é utilizado. No entanto, se a fala não foi reconhecida, é fornecido um valor padrão e uma mensagem é exibida no console explicando o motivo.

Nas duas últimas variáveis que criamos, atribuímos o valor da escolha aleatória do idioma para o qual o discurso será traduzido e selecionamos a voz que fará a tradução. Em seguida, exibimos as informações sobre o idioma e a voz no 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}`);

Em seguida, definimos o código de status HTTP da resposta como 200 como na primeira rota e respondemos com outro objeto 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
  }
])

Este objeto NCCO contém três talk ações, cada uma delas com variáveis e funções que precisam ser criadas. Faremos isso depois de terminarmos de definir as rotas.

A primeira talk ação responde ao chamador com a mensagem original em inglês, tal como foi entendida durante a conversão do reconhecimento automático de fala.

A segunda talk ação informa ao autor da chamada para qual idioma sua mensagem foi traduzida.

A terceira talk ação apresenta ao chamador a mensagem recém-traduzida. Ela também utiliza o voiceName parâmetro para reproduzir a mensagem traduzida com a Voice designada para o idioma, caso haja uma disponível para esse idioma.

A última rota que precisamos definir será bem curta. É ela que receberá o restante dos dados do webhook do evento para a chamada. Neste tutorial, não faremos nada com esses dados além de confirmar que os recebemos. Para isso, enviamos de volta um 204 código de status HTTP, o que equivale a dizer que a mensagem foi enviada com sucesso e que não há conteúdo para responder:

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

Com o servidor definido, estamos prontos para criar as funções auxiliares que chamamos nas rotas do servidor.

Criação dos serviços e dos dados

Vamos voltar ao início do server.ts arquivo novamente e adicionar mais algumas instruções de importação para funções e dados que irão definir:

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";

Conforme indica o trecho acima, precisamos criar os cinco itens a seguir:

  • languageList: Uma lista de idiomas possíveis para os quais a mensagem pode ser traduzida

  • voicesList: Uma variedade de vozes possíveis para narrar a mensagem traduzida

  • translateText: A função para traduzir o texto para o segundo idioma

  • voicePicker: A função para escolher uma voz para ler o texto traduzido

  • languagePicker: A função para escolher o idioma para o qual o texto será traduzido

Vamos criar cada um deles agora.

Definindo os dados

Primeiro, vamos adicionar alguns dados ao nosso aplicativo.

Precisamos adicionar dois conjuntos de dados: uma lista de idiomas e uma lista de vozes para falar esses idiomas.

A lista de idiomas suportados é extraída do Guia de ASR da Vonage. A lista de nomes de vozes também é extraída do Guia da Voice API da Vonage.

Abra o data/languages.ts arquivo e vamos adicionar uma matriz de objetos a ele:

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" }
]

Esta é a lista de idiomas compatíveis na data de publicação deste tutorial. A lista está sujeita a alterações; recomenda-se consultar o guia no site para obter as informações mais atualizadas.

Em seguida, abra o data/voices.ts arquivo e adicione também uma matriz de objetos a ele. Assim como a lista de idiomas, os dados aqui representam a lista de nomes de vozes no momento da publicação. Geralmente, há mais de uma voz por idioma. Para fins deste tutorial, removemos as vozes duplicadas de cada idioma para manter apenas uma voz por idioma:

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" }
]

Definição das funções do serviço

Nosso aplicativo utiliza várias funções definidas em services/ que fornecem a funcionalidade principal. Vamos desenvolver cada uma delas a partir de agora.

Para utilizar a API de Tradução de Fala do Microsoft Azure, precisamos nos autenticar na API por meio de um processo em duas etapas. A primeira etapa consiste em obter um JSON Web Token (JWT) do endpoint da API de criação de tokens, que será utilizado na segunda etapa, quando fizermos uma chamada HTTP para o endpoint da API de tradução.

Abra o services/auth/token.ts arquivo e, nele, vamos criar a funcionalidade para obter um JWT do Azure. Observe que isso depende de você ter criado com sucesso um Account no Microsoft Azure e recebido sua chave de API. A função lê a chave de API a partir de uma variável de ambiente em nosso .env arquivo, que definiremos mais adiante neste tutorial:

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; 
};

A getToken() função aceita um key parâmetro e, juntamente com o endpoint da URL do Microsoft Azure definido no seu arquivo dotenv, faz uma fetch() solicitação enviando sua chave de API. O valor recebido é o seu JWT, que é explicitamente retornado como o valor da função. Logo no início do arquivo, importamos um módulo carregador dotenv do Deno que nos permite ler os valores no .env arquivo.

Se o key for undefined ou se não houver nenhum valor para o azureEndpoint, a função retornará antecipadamente e exibirá uma explicação no console sobre o que estava faltando.

Assim que tivermos o token de getToken(), estamos prontos para usá-lo para criar uma função auxiliar que chame a API de tradução e receba o texto traduzido de volta.

Abra o services/translate.ts arquivo e, nesse arquivo, vamos criar uma translateText() função:

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"]
};

Essa função, assim como a anterior, lê nosso .env arquivo para obter a chave da API do Azure que definimos. Ela recebe dois argumentos: o código de idioma de duas letras e o texto resultante da conversão de fala para texto.

A função, então, cria duas variáveis: token e response. A primeira chama a getToken() função, passando a chave da API do Azure como argumento. A segunda invoca uma fetch() POST solicitação ao endpoint da API de tradução de fala do Azure, utilizando o código de idioma de duas letras como parte dos parâmetros de consulta. O JWT gerado pela getToken() função é passado para o Authorization cabeçalho. O body do POST solicitação é a conversão de fala em texto, transformada em uma string JSON.

A resposta à solicitação é armazenada na translation variável, e o texto traduzido propriamente dito é retornado pela função, que está contida dentro de translation[0]["translations][0]["text].

Ainda precisamos criar duas funções antes de podermos passar à definição das nossas .env variáveis de ambiente.

A primeira das duas funções restantes que vamos criar escolherá aleatoriamente um idioma da lista de idiomas para o qual o texto será traduzido.

Abra services/language_picker.ts e adicione o seguinte código:

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

A função utiliza um pouco de matemática para selecionar aleatoriamente um índice da lista de idiomas e retornar o valor do objeto nesse índice.

A última função que vamos criar selecionará uma Voice da Vonage para reproduzir o texto traduzido, caso exista uma para o idioma em questão. Se não houver, ela retornará a Salli voz que representa o inglês americano. Também garantimos que, se o idioma escolhido for um dos dialetos regionais do árabe, a voz selecionada será uma das vozes em árabe da Vonage.

Abra services/voice_picker.ts e adicione o seguinte nele:

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;
};

E isso encerra todas as funções! Se você chegou até aqui, estamos quase na reta final.

Os últimos itens que precisamos resolver são atribuir os valores às nossas .env variáveis de ambiente e provisionar um número de telefone virtual da Vonage.

Definindo as variáveis de ambiente

Há três valores que precisamos atribuir no .env arquivo:

  • AZURE_SUBSCRIPTION_KEY

  • AZURE_ENDPOINT

  • VONAGE_ASR_WEBHOOK

Os dois primeiros são, respectivamente, nossa chave de API do Azure e o endpoint da URL do Azure.

Este último é a URL do webhook para os dados retornados pelo recurso de Reconhecimento Automático de Fala da Vonage. Esse valor precisa ser uma URL acessível externamente. Uma boa ferramenta para usar durante o desenvolvimento é o ngrok, que torna seu ambiente local disponível externamente. Você pode encontrar um guia para configurar o ngrok localmente em nosso site para desenvolvedores.

Ativação de um número de telefone virtual da Vonage

Existem duas maneiras de configurar um número de telefone virtual da Vonage. Depois de criar uma account de desenvolvedor da Vonage , você pode adquirir um número de telefone pelo painel de controle ou usando a CLI da Vonage. Aqui, faremos isso usando a CLI.

Para instalar a CLI, você pode usar o yarn ou o npm: yarn global add @vonage/cli ou npm install @vonage/cli -g. Após a instalação, você precisará fornecer as credenciais da API obtidas no painel de controle:

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

Agora que sua CLI está configurada, você pode usá-la para pesquisar números disponíveis no seu país. Para isso, execute o seguinte comando usando o código de duas letras do seu país. O exemplo abaixo mostra uma pesquisa de números nos Estados Unidos. Certifique-se de adicionar o --features=VOICE sinalizador para retornar apenas números habilitados para chamadas de voz:

vonage numbers:search US --features=VOICE

Depois de encontrar o número que deseja, você também pode comprá-lo pela CLI:

vonage numbers:buy NUMBER COUNTRYCODE

Será solicitado que você digite confirm depois de enviar o comando para comprar oficialmente o número.

Como estamos criando um aplicativo de voz, também precisamos criar um aplicativo Vonage. Isso também pode ser feito pela CLI e, assim que estiver pronto, podemos vincular o número de telefone recém-provisionado ao aplicativo. Você também pode aproveitar a criação do aplicativo para fornecer a ele as URLs do webhook de resposta e do webhook de evento. Se estiver criando em ambiente de desenvolvimento, agora é um bom momento para criar seu servidor ngrok e fornecer as URLs do ngrok:

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

O comando retornará o ID do aplicativo: Application ID: asdasdas-asdd-2344-2344-asdasdasd345. Usaremos esse ID agora para vincular o aplicativo ao número de telefone:

vonage apps:link APP_ID --number=YOUR_VONAGE_NUMBER

Depois de executar esses comandos, você estará pronto para rodar seu aplicativo!

Executando o aplicativo

Para usar seu aplicativo, inicie tanto o servidor ngrok quanto o servidor web Deno. Para iniciar o aplicativo Deno, execute o seguinte comando na pasta raiz:

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

Agora que o sistema está funcionando, você pode ligar para o número de telefone fornecido pela Vonage e seguir as instruções para gravar uma mensagem. Sua mensagem será convertida em texto por meio do recurso de Reconhecimento Automático de Fala da Vonage e, em seguida, traduzida para um segundo idioma aleatório usando o Microsoft Azure, sendo então reproduzida para você. Aproveite!

Compartilhar:

https://a.storyblok.com/f/270183/384x384/e5480d2945/ben-greenberg.png
Ben GreenbergEx-funcionários da Vonage

Ben é um desenvolvedor que mudou de carreira, tendo atuado anteriormente por uma década nas áreas de educação de adultos, organização comunitária e gestão de organizações sem fins lucrativos. Ele trabalhou como representante de desenvolvedores na Vonage. Escreve regularmente sobre a interseção entre desenvolvimento comunitário e tecnologia. Natural do sul da Califórnia e morador de longa data da cidade de Nova York, Ben reside atualmente perto de Tel Aviv, em Israel.