From aa8bca734936bba20bb8bec5d162dd6e0a4d7cc6 Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Mon, 10 Feb 2025 14:10:14 -0800 Subject: [PATCH] feat: add translations for responses --- src/i18n/responses.ts | 218 +++++++++++++++++++++++++++++++++++++++ src/modules/translate.ts | 23 +++-- src/utils/i18n.ts | 28 +++++ 3 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 src/i18n/responses.ts create mode 100644 src/utils/i18n.ts diff --git a/src/i18n/responses.ts b/src/i18n/responses.ts new file mode 100644 index 0000000..fd84667 --- /dev/null +++ b/src/i18n/responses.ts @@ -0,0 +1,218 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +/* eslint-disable @typescript-eslint/naming-convention -- This is the convention for these keys. */ +/* eslint-disable stylistic/max-len -- These are going to be long strings and that's okay. */ +import { Locale } from "discord.js"; + +export const responses: Record = { + en: { + "no-message-content": "No message content found.", + "subscription-required": + "You must be subscribed to translate messages.", + "translation": + "{{translation}}\n-# Detected {{language}} with {{confidence}}% confidence.", + "unsupported-locale": "Unsupported locale.", + }, + [Locale.Indonesian]: { + "no-message-content": "Tidak ada konten pesan ditemukan.", + "subscription-required": + "Anda harus berlangganan untuk menerjemahkan pesan.", + "translation": + "{{translation}}\n-# Mendeteksi {{language}} dengan kepercayaan {{confidence}}%.", + "unsupported-locale": "Lokal tidak didukung.", + }, + es: { + "no-message-content": "No se encontró contenido del mensaje.", + "subscription-required": + "Debes estar suscrito para traducir mensajes.", + "translation": + "{{translation}}\n-# Detectado {{language}} con {{confidence}}% de confianza.", + "unsupported-locale": "Lugar no compatible.", + }, + pt: { + "no-message-content": "Nenhum conteúdo de mensagem encontrado.", + "subscription-required": + "Você deve estar inscrito para traduzir mensagens.", + "translation": + "{{translation}}\n-# Detectado {{language}} com {{confidence}}% de confiança.", + "unsupported-locale": "Local não suportado.", + }, + [Locale.Czech]: { + "no-message-content": "Nebyl nalezen žádný obsah zprávy.", + "subscription-required": + "Musíte být přihlášeni k překladu zpráv.", + "translation": + "{{translation}}\n-# Detekováno {{language}} s důvěrou {{confidence}}%.", + "unsupported-locale": "Nepodporované místo.", + }, + [Locale.Danish]: { + "no-message-content": "Ingen beskedindhold fundet.", + "subscription-required": + "Du skal være tilmeldt for at oversætte beskeder.", + "translation": + "{{translation}}\n-# Detekteret {{language}} med {{confidence}}% tillid.", + "unsupported-locale": "Ikke understøttet sted.", + }, + [Locale.Dutch]: { + "no-message-content": "Geen berichtinhoud gevonden.", + "subscription-required": + "U moet zijn geabonneerd om berichten te vertalen.", + "translation": + "{{translation}}\n-# Gedetecteerd {{language}} met {{confidence}}% vertrouwen.", + "unsupported-locale": "Niet-ondersteunde locatie.", + }, + [Locale.Finnish]: { + "no-message-content": "Ei viestisisältöä löytynyt.", + "subscription-required": + "Sinun on tilattava viestien kääntämiseksi.", + "translation": + "{{translation}}\n-# Havaittu {{language}} {{confidence}}% luottamuksella.", + "unsupported-locale": "Ei tuettu paikka.", + }, + [Locale.French]: { + "no-message-content": "Aucun contenu de message trouvé.", + "subscription-required": + "Vous devez être abonné pour traduire les messages.", + "translation": + "{{translation}}\n-# Détecté {{language}} avec {{confidence}}% de confiance.", + "unsupported-locale": "Lieu non pris en charge.", + }, + [Locale.German]: { + "no-message-content": "Kein Nachrichteninhalt gefunden.", + "subscription-required": + "Sie müssen abonniert sein, um Nachrichten zu übersetzen.", + "translation": + "{{translation}}\n-# Erkannt {{language}} mit {{confidence}}% Vertrauen.", + "unsupported-locale": "Nicht unterstützter Ort.", + }, + [Locale.Greek]: { + "no-message-content": "Δεν βρέθηκε περιεχόμενο μηνύματος.", + "subscription-required": + "Πρέπει να είστε συνδρομητής για να μεταφράσετε μηνύματα.", + "translation": + "{{translation}}\n-# Ανιχνεύθηκε {{language}} με {{confidence}}% εμπιστοσύνη.", + "unsupported-locale": "Μη υποστηριζόμενη τοποθεσία.", + }, + [Locale.Hindi]: { + "no-message-content": "कोई संदेश सामग्री नहीं मिली।", + "subscription-required": + "आपको संदेश अनुवाद करने के लिए सब्सक्राइब करना होगा।", + "translation": + "{{translation}}\n-# {{confidence}}% विश्वास के साथ {{language}} का पता लगाया गया।", + "unsupported-locale": "असमर्थित स्थान।", + }, + [Locale.Hungarian]: { + "no-message-content": "Nem található üzenettartalom.", + "subscription-required": + "Fel kell iratkoznia az üzenetek fordításához.", + "translation": + "{{translation}}\n-# {{language}} érzékelve {{confidence}}% bizalommal.", + "unsupported-locale": "Nem támogatott hely.", + }, + [Locale.Italian]: { + "no-message-content": "Nessun contenuto del messaggio trovato.", + "subscription-required": + "Devi essere abbonato per tradurre i messaggi.", + "translation": + "{{translation}}\n-# Rilevato {{language}} con {{confidence}}% di fiducia.", + "unsupported-locale": "Località non supportata.", + }, + [Locale.Japanese]: { + "no-message-content": "メッセージコンテンツが見つかりません。", + "subscription-required": + "メッセージを翻訳するには購読する必要があります。", + "translation": + "{{translation}}\n-# {{confidence}}% の信頼度で {{language}} を検出しました。", + "unsupported-locale": "サポートされていないロケール。", + }, + [Locale.Korean]: { + "no-message-content": "메시지 내용을 찾을 수 없습니다.", + "subscription-required": + "메시지를 번역하려면 구독해야 합니다.", + "translation": + "{{translation}}\n-# {{confidence}}% 신뢰도로 {{language}} 감지.", + "unsupported-locale": "지원되지 않는 로케일.", + }, + [Locale.Lithuanian]: { + "no-message-content": "Nerasta jokio pranešimo turinio.", + "subscription-required": + "Norint išversti žinutes, turite būti prenumeratorius.", + "translation": + "{{translation}}\n-# Aptikta {{language}} su {{confidence}}% pasitikėjimu.", + "unsupported-locale": "Nepalaikomas vietovė.", + }, + [Locale.Polish]: { + "no-message-content": "Nie znaleziono treści wiadomości.", + "subscription-required": + "Aby tłumaczyć wiadomości, musisz być subskrybentem.", + "translation": + "{{translation}}\n-# Wykryto {{language}} z {{confidence}}% pewnością.", + "unsupported-locale": "Nieobsługiwane miejsce.", + }, + sv: { + "no-message-content": "Inget meddelandeinnehåll hittades.", + "subscription-required": + "Du måste prenumerera för att översätta meddelanden.", + "translation": + "{{translation}}\n-# Upptäckt {{language}} med {{confidence}}% förtroende.", + "unsupported-locale": "Ej understödd plats.", + }, + [Locale.Romanian]: { + "no-message-content": "Nu s-a găsit niciun conținut de mesaj.", + "subscription-required": + "Trebuie să fiți abonat pentru a traduce mesajele.", + "translation": + "{{translation}}\n-# Detectat {{language}} cu {{confidence}}% încredere.", + "unsupported-locale": "Locație neacceptată.", + }, + [Locale.Russian]: { + "no-message-content": "Содержимое сообщения не найдено.", + "subscription-required": + "Вы должны быть подписаны на перевод сообщений.", + "translation": + "{{translation}}\n-# Обнаружен {{language}} с {{confidence}}% уверенностью.", + "unsupported-locale": "Неподдерживаемое место.", + }, + zh: { + "no-message-content": "未找到消息内容。", + "subscription-required": "您必须订阅以翻译消息。", + "translation": + "{{translation}}\n-# 检测到 {{language}},{{confidence}}% 的信心。", + "unsupported-locale": "不支持的区域。", + }, + zt: { + "no-message-content": "未找到消息内容。", + "subscription-required": "您必须订阅以翻译消息。", + "translation": + "{{translation}}\n-# 检测到 {{language}},{{confidence}}% 的信心。", + "unsupported-locale": "不支持的区域。", + }, + [Locale.Thai]: { + "no-message-content": "ไม่พบเนื้อหาข้อความ", + "subscription-required": + "คุณต้องสมัครสมาชิกเพื่อแปลข้อความ", + "translation": + "{{translation}}\n-# ตรวจพบ {{language}} ด้วยความมั่นใจ {{confidence}}%", + "unsupported-locale": "ที่ตั้งที่ไม่รองรับ", + }, + [Locale.Turkish]: { + "no-message-content": "Hiçbir mesaj içeriği bulunamadı.", + "subscription-required": + "Mesajları çevirmek için abone olmalısınız.", + "translation": + "{{translation}}\n-# {{confidence}}% güvenle {{language}} tespit edildi.", + "unsupported-locale": "Desteklenmeyen yer.", + }, + [Locale.Ukrainian]: { + "no-message-content": "Не знайдено вмісту повідомлення.", + "subscription-required": + "Ви повинні підписатися на переклад повідомлень.", + "translation": + "{{translation}}\n-# Виявлено {{language}} з {{confidence}}% впевненістю.", + "unsupported-locale": "Непідтримуване місце.", + }, +}; diff --git a/src/modules/translate.ts b/src/modules/translate.ts index 239dfbe..cd8d6cc 100644 --- a/src/modules/translate.ts +++ b/src/modules/translate.ts @@ -9,6 +9,7 @@ import { type MessageContextMenuCommandInteraction, } from "discord.js"; import { supportedLocales } from "../config/locales.js"; +import { i18n } from "../utils/i18n.js"; import { getLocale } from "./getLocale.js"; /** @@ -27,20 +28,22 @@ export const translate = async( }); if (!isEntitled && interaction.user.id !== "465650873650118659") { - await interaction.editReply( - "You must be subscribed to translate messages.", - ); + await interaction.editReply({ + content: i18n("subscription-required", targetLocale), + }); return; } if (!supportedLocales.includes(targetLocale)) { - await interaction.editReply("Unsupported locale."); + await interaction.editReply(i18n("unsupported-locale", targetLocale)); return; } const message = interaction.options.getMessage("message", true); if (message.content === "") { - await interaction.editReply("No message content found."); + await interaction.editReply({ + content: i18n("no-message-content", targetLocale), + }); return; } const sourceLocaleRequestParameters = new URLSearchParams(); @@ -66,7 +69,8 @@ export const translate = async( translationRequestParameters.append("source", sourceLocale?.language ?? "en"); translationRequestParameters.append("target", targetLocale); translationRequestParameters.append( - "api_key", process.env.TRANSLATE_TOKEN ?? "", + "api_key", + process.env.TRANSLATE_TOKEN ?? "", ); const translationRequest = await fetch( "https://trans.nhcarrigan.com/translate", @@ -78,6 +82,11 @@ export const translate = async( }; await interaction.editReply({ - content: `${translation.translatedText}\n-# Detected ${sourceLocale?.language ?? "unknown"} with ${sourceLocale?.confidence.toString() ?? "unknown"}% confidence.`, + content: i18n("translation", targetLocale, { + confidence: sourceLocale?.confidence, + language: sourceLocale?.language, + lng: targetLocale, + translation: translation.translatedText, + }), }); }; diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts new file mode 100644 index 0000000..ddf9043 --- /dev/null +++ b/src/utils/i18n.ts @@ -0,0 +1,28 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { responses } from "../i18n/responses.js"; + +/** + * Translates a key to the specified locale, performing + * interpolation on the string. + * @param key -- The key to translate. + * @param locale -- The user's locale. + * @param interpolation -- An object of keys to replace with values. + * @returns The translated string. + */ +export const i18n = ( + key: keyof (typeof responses)["en"], + locale: string, + interpolation: Record = {}, +): string => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- We know the en key exists, but having the loose type helps. + const string = responses[locale]?.[key] ?? responses.en![key]; + // eslint-disable-next-line unicorn/no-array-reduce -- This is the cleanest way to do it, really. + return Object.entries(interpolation).reduce((accumulator, [ k, v ]) => { + return accumulator.replace(`{{${k}}}`, String(v)); + }, string); +};