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

Erstellen einer Sprachübersetzungs-App auf Deno mit Azure und Vonage

Zuletzt aktualisiert am May 4, 2021

Lesedauer: 12 Minuten

Vonage veröffentlichte kürzlich Automatische Spracherkennung (ASR) als neue Funktion der Voice API veröffentlicht. Ein guter Grund, eine unterhaltsame neue Sprachanwendung zu entwickeln, die diese neue Fähigkeit nutzt!

In diesem Tutorial werden wir eine Voice-Anwendung erstellen, die auf Deno die:

  1. Telefonanrufe erhalten

  2. Akzeptieren Sie die vom Anrufer nach der Aufforderung gesprochene Sprache

  3. Konvertieren Sie diese Sprache in Text mit Vonage ASR

  4. Übersetzen Sie es mit Microsoft Azure in eine zufällig ausgewählte Sprache

  5. Sprechen Sie sowohl den englischen Originaltext als auch den neu übersetzten Text wieder.

  6. Wenn der neu übersetzte Text einen begleitenden Vonage Voice verfügbarwird diese Voice verwendet.

Wir verwenden Deno als unsere Laufzeitumgebung, weil wir mit Deno eine serverseitige Anwendung in TypeScript mit geringen Abhängigkeiten erstellen können. Es ermöglicht uns, nur den externen Code zu integrieren, den wir tatsächlich benötigen, und ihm nur die Laufzeitberechtigungen zu geben, die er haben soll.

Es gibt mehrere mögliche Anbieter, die wir integrieren können, um Textübersetzungen anzubieten. In diesem Tutorial werden wir mit Hilfe der Microsoft Azure-API für Sprachübersetzung.

Fangen wir an!

tl;dr Wenn Sie es überspringen und die Anwendung einfach ausführen möchten, finden Sie eine voll funktionsfähige Version auf GitHub.

Voraussetzungen

Um diese Anwendung zu erstellen, benötigen Sie mehrere Elemente, bevor wir mit der Implementierung beginnen können:

Wenn Sie all das erledigt haben, können wir mit der Implementierung unserer Anwendung beginnen.

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch kein Konto haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

In diesem Lernprogramm wird auch eine virtuelle Telefonnummer verwendet. Um eine zu erwerben, gehen Sie zu Numbers > Buy Numbers und suchen Sie nach einer Nummer, die Ihren Anforderungen entspricht.

Start building with Vonage

Erstellen der Ordnerstruktur

Der erste Schritt besteht darin, die Ordnerstruktur für unsere Anwendung zu erstellen. Sie wird am Ende wie folgt aussehen:

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

In diesem Tutorial werden wir den Stammordner der Anwendung so nennen, speech-translation-appnennen, aber Sie können ihn benennen, wie Sie möchten. Sobald Sie den Stammordner erstellt haben, wechseln Sie in dieses Verzeichnis und erstellen Sie die Ordner data, services, und services/auth Unterordner an.

Innerhalb des Stammordners erstellen Sie server.ts und .env Dateien, indem Sie touch server.ts .env innerhalb des Stammverzeichnisses ausführt.

Führen Sie eine ähnliche Aktion innerhalb der data, services, und services/auth aus, indem Sie touch um die im obigen Verzeichnisbaum angezeigten Dateien zu erstellen.

Erstellen des Deno-Servers

Öffnen Sie in Ihrem bevorzugten Code-Editor die Datei server.ts Datei aus dem Stammverzeichnis, die Sie im letzten Schritt erstellt haben.

In dieser Datei werden wir einen HTTP-Server instanziieren, ihn mit seinen Routen versehen und den Ablauf der Anwendung steuern.

Wir werden Folgendes verwenden Opine als unser Web-Framework für den Server verwenden. Opine ist ein minimalistisches Framework, das für Deno entwickelt und von ExpressJS portiert wurde. Wenn Sie mit ExpressJS vertraut sind, dann werden Ihnen die Konstrukte in Opine vertraut vorkommen.

Um Opine zu verwenden, müssen wir es am Anfang unserer Datei importieren. Deno verwendet im Gegensatz zu NodeJS kein node_modules oder ein ähnliches Paketverwaltungssystem. Daher wird jedes Paket, das in Ihre Anwendung eingebracht wird, direkt aus der Quelle importiert:

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

Sobald wir opine zur Verfügung gestellt haben, können wir eine Instanz davon instanziieren und die Grundstruktur für unsere Routen erstellen:

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

Die drei GET Anfragen, die im Server aufgezählt werden, entsprechen drei eindeutigen Webhooks der Vonage Voice API. Bei der ersten Anfrage sendet die API einen eingehenden Anruf. Beim zweiten sendet die API die in Text umgewandelte Sprache mithilfe der automatischen Spracherkennung von Vonage. Über die dritte Route schließlich werden alle Ereignisdaten für den Lebenszyklus des Anrufs gesendet.

Wir müssen für jede dieser drei Routen eine Logik bereitstellen, die die Funktionsweise unserer Anwendung steuert: Diese Unterhaltung wurde von NJalal7 als gelöst markiert

  • Die Route für eingehende Anrufe nimmt die Spracheingabe des Anrufers auf und sendet sie zur Textumwandlung an die Vonage API.

  • Die zweite Route empfängt den Text und sendet ihn an die Azure Speech Translation API, um ihn in eine zweite Sprache zu übersetzen. Außerdem werden dem Anrufer die ursprüngliche und die übersetzte Nachricht wiedergegeben.

  • Die endgültige Route empfängt alle Ereignisdaten des Anrufslebenszyklus und bestätigt den Empfang der Daten.

Festlegen der Routen

Lassen Sie uns die Logik für den eingehenden Anruf erstellen /webhooks/answer Route.

Innerhalb der Route müssen wir die Anrufer-ID (UUID) einer Variablen zuweisen, damit wir sie später verwenden können. Die UUID ist ein notwendiger Bestandteil der ASR-Anfrage:

const uuid = req.query.uuid

Als nächstes müssen wir mit einem HTTP-Statuscode von 200 antworten und als Antwort ein Nexmo Call Control Object (NCCO) zurücksenden, das ein JSON-Objekt ist, das die Anweisungen enthält, die die Vonage-API ausführen soll:

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

Wie Sie sehen können, besteht die NCCO aus zwei Aktionen: talk und inputzusammen.

Die talk Aktion begrüßt den Anrufer und fordert ihn auf, etwas zu sagen. Sie setzt auch einen Parameter bargeIn auf gleich truegesetzt, der es dem Anrufer ermöglicht, zu sprechen, bevor die Nachricht beendet ist.

Die input wird die Spracheingabe des Anrufers entgegengenommen. In dieser Aktion definieren wir einige eindeutige Parameter:

  • eventUrl: Wohin soll die fertig konvertierte Sprache in Text gesendet werden? In der Aktion definieren wir die URL als eine Variable namens asrWebhook. Wir werden sie später erstellen.

  • eventMethod: Welches HTTP-Verb soll verwendet werden, um die fertige Sprache in Text zu senden. In diesem Fall verwenden wir GET.

  • action: Der Basisparameter für alle NCCO-Aktionen. Sein Wert ist gleich der Aktion, die Sie durchführen möchten, in diesem Fall input.

  • speech: Ein Parameter, dessen Wert gleich einem Objekt ist, das die UUID des Anrufers und die language der Sprache, die in Text umgewandelt wird, enthält.

Alles in allem sieht diese erste GET Route wie folgt aus:

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

Die zweite Route, die wir definieren müssen, ist die /webhooks/asr Route, die die in Text umgewandelte Sprache von der Vonage-API empfängt und darauf reagiert.

Es gibt einige Werte, die wir den zu verwendenden Variablen zuweisen wollen. Der erste ist das Ergebnis der ASR-Umwandlung, das uns in Form eines Arrays von Objekten vorliegt. Die Objekte sind in absteigender Reihenfolge der Genauigkeitswahrscheinlichkeit angeordnet. Die zweite Variable enthält den Text des Objekts mit der höchsten Genauigkeitswahrscheinlichkeit.

Wir instanziieren die zweite Variable als leere Variable und weisen ihr einen Wert zu, der davon abhängt, ob Vonage ASR in der Lage war, die vom Anrufer angebotene Sprache zu erfassen. Wenn die Sprache erkannt wurde, wird dieser Wert verwendet. Wurde die Sprache jedoch nicht erkannt, wird ein Standardwert zugewiesen und in der Konsole eine Meldung über den Grund dafür angezeigt.

In den letzten beiden Variablen, die wir erstellen, weisen wir den Wert der zufällig gewählten Sprache zu, in die die Sprache übersetzt werden soll, und wählen die Stimme, mit der die Übersetzung gesprochen werden soll. Anschließend geben wir die Informationen über die Sprache und die Stimme in der Konsole frei:

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

Dann setzen wir den HTTP-Statuscode der Antwort auf 200 wie in der ersten Route, und wir antworten mit einem weiteren NCCO JSON-Objekt:

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

Dieses NCCO-Objekt enthält drei talk Aktionen, von denen jede mit Variablen und Funktionen ausgestattet ist, die erstellt werden müssen. Wir werden dies tun, nachdem wir die Definition der Routen abgeschlossen haben.

Die erste talk Aktion gibt dem Anrufer seine ursprüngliche Nachricht in englischer Sprache zurück, so wie sie bei der automatischen Spracherkennung verstanden wurde.

Die zweite talk Aktion teilt dem Anrufer mit, in welche Sprache seine Nachricht übersetzt wurde.

Die dritte talk Aktion teilt dem Anrufer seine neu übersetzte Nachricht mit. Sie nutzt auch den voiceName Parameter, um die übersetzte Nachricht mit der für die Sprache vorgesehenen Stimme zu sprechen, sofern eine solche für diese Sprache verfügbar ist.

Die letzte Route, die wir definieren müssen, ist eine kurze Route. Dies ist die Route, die den Rest der Ereignis-Webhook-Daten für den Anruf erhält. In diesem Tutorial werden wir nichts mit diesen Daten machen, außer zu bestätigen, dass wir sie erhalten haben. Wir bestätigen den Empfang durch das Zurücksenden eines 204 HTTP-Statuscode zurück, der besagt, dass die Nachricht erfolgreich war und es keinen Inhalt gibt, mit dem man antworten könnte:

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

Nachdem der Server definiert ist, können wir die Hilfsfunktionen erstellen, die wir in den Server-Routen aufgerufen haben.

Erstellen der Dienste und Daten

Navigieren wir wieder an den Anfang der server.ts Datei und fügen einige weitere Importanweisungen für Funktionen und Daten hinzu, die definiert werden sollen:

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

Wie der obige Ausschnitt zeigt, müssen wir die fünf folgenden Elemente erstellen:

  • languageList: Eine Reihe von möglichen Sprachen, in die die Nachricht übersetzt werden kann

  • voicesList: Eine Reihe von möglichen Stimmen, mit denen die übersetzte Nachricht gesprochen wird

  • translateText: Die Funktion zur Übersetzung des Textes in die zweite Sprache

  • voicePicker: Die Funktion zur Auswahl einer Stimme, mit der der übersetzte Text gesprochen wird

  • languagePicker: Die Funktion zur Auswahl einer Sprache, in die der Text übersetzt werden soll

Wir werden jetzt jedes dieser Elemente bauen.

Definition der Daten

Fügen wir zunächst einige Daten zu unserer Anwendung hinzu.

Wir müssen zwei Datenelemente hinzufügen: eine Liste von Sprachen und eine Liste von Stimmen, die diese Sprachen sprechen.

Die Liste der unterstützten Sprachen stammt aus dem Vonage ASR-Leitfaden. Die Liste der Sprachnamen stammt ebenfalls aus dem Voice API-Leitfaden von Vonage.

Öffnen Sie die data/languages.ts Datei und fügen Sie ein Array von Objekten hinzu:

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

Dies ist die Liste der unterstützten Sprachen zum Zeitpunkt der Veröffentlichung dieses Handbuchs. Die Liste kann sich ändern. Die aktuellsten Informationen finden Sie im Leitfaden auf der Website.

Als nächstes öffnen Sie die Datei data/voices.ts und fügen dieser Datei ebenfalls ein Array von Objekten hinzu. Wie die Sprachliste stellen die Daten hier die Liste der Stimmennamen zum Zeitpunkt der Veröffentlichung dar. Oft gibt es mehr als eine Voice pro Sprache. Für dieses Tutorial haben wir doppelte Stimmen entfernt, um es bei einer Stimme pro Sprache zu belassen:

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

Definieren der Servicefunktionen

Unsere Anwendung verwendet mehrere Funktionen, die in services/ definiert sind, die die Kernfunktionalität bereitstellen. Wir werden jede von ihnen an dieser Stelle ausbauen.

Um die Microsoft Azure Speech Translation API nutzen zu können, müssen wir uns in einem zweistufigen Verfahren bei der API authentifizieren. Der erste Schritt besteht darin, ein JSON-Web-Token (JWT) vom Token-Erstellungs-API-Endpunkt zu erhalten, das wir dann im zweiten Schritt verwenden, wenn wir einen HTTP-Aufruf an den Übersetzungs-API-Endpunkt tätigen.

Öffnen Sie die services/auth/token.ts Datei und erstellen Sie darin die Funktionalität, um ein JWT von Azure zu erhalten. Bitte beachten Sie, dass dies davon abhängt, ob Sie erfolgreich einen Account bei Microsoft Azure erstellt und Ihren API-Schlüssel erhalten haben. Die Funktion liest den API-Schlüssel aus einer Umgebungsvariablen in unserer .env Datei, die wir später in diesem Tutorial definieren werden:

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

Die Funktion getToken() Funktion akzeptiert einen key Parameter, und zusammen mit dem in Ihrer dotenv-Datei definierten Microsoft Azure-URL-Endpunkt stellt sie eine fetch() Anfrage, die Ihren API-Schlüssel sendet. Der Wert, der zurückkommt, ist Ihr JWT, der ausdrücklich als Wert von der Funktion zurückgegeben wird. Ganz oben in der Datei importieren wir ein dotenv-Loader-Modul von Deno, mit dem wir die Werte in der .env Datei liest.

Wenn die key ist undefined oder gibt es keinen Wert für den azureEndpointist, kehrt die Funktion vorzeitig zurück und gibt in der Konsole eine Erklärung für den fehlenden Wert.

Sobald wir das Token von getToken()haben, können wir es verwenden, um eine Hilfsfunktion zu erstellen, die die Übersetzungs-API aufruft und den übersetzten Text zurückerhält.

Öffnen Sie die services/translate.ts Datei, und in dieser Datei werden wir eine translateText() Funktion:

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

Diese Funktion, wie auch die vorherige, liest aus unserer .env Datei, um den Azure-API-Schlüssel zu erhalten, den wir definiert haben. Sie nimmt zwei Argumente entgegen: den aus zwei Buchstaben bestehenden Sprachcode und die in Text umgewandelte Sprache.

Die Funktion erstellt dann zwei Variablen: token und response. Die erste ruft die Funktion getToken() Funktion auf und übergibt den Azure-API-Schlüssel als Argument. Letztere ruft eine fetch() POST Anfrage an den Azure-Sprachübersetzungs-API-Endpunkt unter Verwendung des zweistelligen Sprachcodes als Teil der Abfrageparameter. Das von der Funktion generierte JWT getToken() Funktion generierte JWT wird in den Authorization Kopfzeile übergeben. Die body der POST Anfrage wird die in einen JSON-String umgewandelte Sprache in Text umgewandelt.

Die Antwort auf die Anfrage wird in der Variablen translation gespeichert, und der tatsächlich übersetzte Text wird von der Funktion zurückgegeben, die in translation[0]["translations][0]["text].

Wir müssen noch zwei Funktionen erstellen, bevor wir mit der Definition unserer .env Umgebungsvariablen zu definieren.

Die erste der beiden verbleibenden Funktionen, die wir erstellen werden, wählt zufällig eine Sprache aus der Sprachenliste aus, in die der Text übersetzt werden soll.

Öffnen Sie services/language_picker.ts und fügen Sie den folgenden Code ein:

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

Die Funktion verwendet ein wenig Mathematik, um zufällig einen Index aus der Sprachenliste auszuwählen und den Wert des Objekts in diesem Index zurückzugeben.

Die letzte Funktion, die wir erstellen, wählt eine Voice von Vonage aus, um die übersetzte Sprache zu sprechen, sofern eine solche für die Sprache existiert. Wenn keine vorhanden ist, wird die Stimme Salli Voice zurück, die für amerikanisches Englisch steht. Wenn die gewählte Sprache einer der regionalen Dialekte des Arabischen ist, garantieren wir außerdem, dass die gewählte Stimme eine der Vonage Arabic Voices ist.

Öffnen Sie services/voice_picker.ts und fügen Sie den folgenden Text hinzu:

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

Das war's für alle Funktionen! Wenn Sie es bis hierher geschafft haben, sind wir fast an der Ziellinie angelangt.

Die letzten Punkte, um die wir uns kümmern müssen, sind die Zuweisung der Werte zu unseren .env Umgebungsvariablen zuzuweisen und eine virtuelle Vonage-Telefonnummer einzurichten.

Definieren der Umgebungsvariablen

Es gibt drei Werte, die wir in der folgenden Datei zuweisen müssen .env Datei zuweisen:

  • AZURE_ABONNEMENT_SCHLÜSSEL

  • AZURE_ENDPOINT

  • VONAGE_ASR_WEBHOOK

Die ersten beiden sind unser Azure-API-Schlüssel bzw. unser Azure-URL-Endpunkt.

Letzteres ist die Webhook-URL für die Daten, die von der automatischen Spracherkennung von Vonage zurückgegeben werden. Dieser letztere Wert muss eine von außen zugängliche URL sein. Ein gutes Tool, das Sie während der Entwicklung verwenden können, ist ngrok, um Ihre lokale Umgebung extern verfügbar zu machen. Eine Anleitung zur lokalen Einrichtung von ngrok finden Sie auf unserer Entwickler-Website.

Einrichten einer virtuellen Vonage-Telefonnummer

Es gibt zwei Möglichkeiten, eine virtuelle Vonage-Telefonnummer einzurichten. Sobald Sie ein Vonage Entwickler Account haben, können Sie entweder eine Telefonnummer über das Dashboard oder über die Vonage CLI erwerben. Wir werden dies hier über die CLI tun.

Um das CLI zu installieren, können Sie entweder yarn oder npm verwenden: yarn global add @vonage/cli oder npm install @vonage/cli -g. Nach der Installation müssen Sie Ihre API-Anmeldeinformationen aus dem Dashboard angeben:

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

Nachdem Sie Ihre CLI eingerichtet haben, können Sie sie nun verwenden, um nach verfügbaren Numbers in Ihrem Land zu suchen. Führen Sie dazu den folgenden Befehl aus und geben Sie dabei Ihren zweistelligen Ländercode ein. Das folgende Beispiel zeigt eine Suche nach Nummern in den Vereinigten Staaten. Achten Sie darauf, dass Sie das Kennzeichen --features=VOICE hinzufügen, um nur Nummern zu erhalten, die Voice-fähig sind:

vonage numbers:search US --features=VOICE

Wenn Sie eine gewünschte Nummer gefunden haben, können Sie diese auch über die CLI erwerben:

vonage numbers:buy NUMBER COUNTRYCODE

Sie werden aufgefordert, Folgendes einzugeben confirm einzugeben, um die Nummer offiziell zu erwerben.

Da wir eine Voice-App erstellen, müssen wir auch eine Vonage Application erstellen. Auch dies kann über die Befehlszeilenschnittstelle (CLI) erfolgen, und sobald dies geschehen ist, können wir die kürzlich eingerichtete Telefonnummer mit der Anwendung verknüpfen. Sie können die Erstellung der Anwendung auch nutzen, um sie mit den URLs für den Antwort-Webhook und den Ereignis-Webhook zu versorgen. Wenn Sie die Anwendung in der Entwicklung erstellen, ist jetzt ein guter Zeitpunkt, um Ihren ngrok-Server zu erstellen und die ngrok-URLs bereitzustellen:

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

Der Befehl gibt Ihnen die Anwendungs-ID zurück: Application ID: asdasdas-asdd-2344-2344-asdasdasd345. Wir werden diese ID nun verwenden, um die Anwendung mit der Rufnummer zu verknüpfen:

vonage apps:link APP_ID --number=YOUR_VONAGE_NUMBER

Sobald Sie diese Befehle ausgeführt haben, können Sie Ihre Anwendung starten!

Ausführen der Anwendung

Um Ihre Anwendung zu verwenden, starten Sie sowohl Ihren ngrok-Server als auch Ihren Deno-Webserver. Um die Deno-Anwendung zu starten, führen Sie den folgenden Befehl im Stammverzeichnis aus:

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

Jetzt können Sie Ihre von Vonage bereitgestellte Telefonnummer anrufen und der Aufforderung folgen, eine Nachricht zu sprechen. Ihre Nachricht wird mit der automatischen Spracherkennung von Vonage in Text umgewandelt und dann mit Microsoft Azure in eine zufällige zweite Sprache übersetzt und an Sie zurückgesprochen. Viel Spaß!

Teilen Sie:

https://a.storyblok.com/f/270183/384x384/e5480d2945/ben-greenberg.png
Ben GreenbergVonage Ehemalige

Ben ist ein Entwickler im zweiten Beruf, der zuvor ein Jahrzehnt in den Bereichen Erwachsenenbildung, Community-Organisation und Non-Profit-Management tätig war. Er arbeitete als Anwalt für Entwickler bei Vonage. Er schreibt regelmäßig über die Überschneidung von Gemeindeentwicklung und Technologie. Ursprünglich aus Südkalifornien stammend und lange Zeit in New York City ansässig, wohnt Ben jetzt in der Nähe von Tel Aviv, Israel.