feat: add translations for responses
All checks were successful
Node.js CI / Lint and Test (pull_request) Successful in 39s

This commit is contained in:
Naomi Carrigan 2025-02-10 14:10:14 -08:00
parent 42809c808a
commit aa8bca7349
Signed by: naomi
SSH Key Fingerprint: SHA256:rca1iUI2OhAM6n4FIUaFcZcicmri0jgocqKiTTAfrt8
3 changed files with 262 additions and 7 deletions

218
src/i18n/responses.ts Normal file
View File

@ -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<string, { "no-message-content": string; "subscription-required": string; "translation": string; "unsupported-locale": string }> = {
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": "Непідтримуване місце.",
},
};

View File

@ -9,6 +9,7 @@ import {
type MessageContextMenuCommandInteraction, type MessageContextMenuCommandInteraction,
} from "discord.js"; } from "discord.js";
import { supportedLocales } from "../config/locales.js"; import { supportedLocales } from "../config/locales.js";
import { i18n } from "../utils/i18n.js";
import { getLocale } from "./getLocale.js"; import { getLocale } from "./getLocale.js";
/** /**
@ -27,20 +28,22 @@ export const translate = async(
}); });
if (!isEntitled && interaction.user.id !== "465650873650118659") { if (!isEntitled && interaction.user.id !== "465650873650118659") {
await interaction.editReply( await interaction.editReply({
"You must be subscribed to translate messages.", content: i18n("subscription-required", targetLocale),
); });
return; return;
} }
if (!supportedLocales.includes(targetLocale)) { if (!supportedLocales.includes(targetLocale)) {
await interaction.editReply("Unsupported locale."); await interaction.editReply(i18n("unsupported-locale", targetLocale));
return; return;
} }
const message = interaction.options.getMessage("message", true); const message = interaction.options.getMessage("message", true);
if (message.content === "") { if (message.content === "") {
await interaction.editReply("No message content found."); await interaction.editReply({
content: i18n("no-message-content", targetLocale),
});
return; return;
} }
const sourceLocaleRequestParameters = new URLSearchParams(); const sourceLocaleRequestParameters = new URLSearchParams();
@ -66,7 +69,8 @@ export const translate = async(
translationRequestParameters.append("source", sourceLocale?.language ?? "en"); translationRequestParameters.append("source", sourceLocale?.language ?? "en");
translationRequestParameters.append("target", targetLocale); translationRequestParameters.append("target", targetLocale);
translationRequestParameters.append( translationRequestParameters.append(
"api_key", process.env.TRANSLATE_TOKEN ?? "", "api_key",
process.env.TRANSLATE_TOKEN ?? "",
); );
const translationRequest = await fetch( const translationRequest = await fetch(
"https://trans.nhcarrigan.com/translate", "https://trans.nhcarrigan.com/translate",
@ -78,6 +82,11 @@ export const translate = async(
}; };
await interaction.editReply({ 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,
}),
}); });
}; };

28
src/utils/i18n.ts Normal file
View File

@ -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, unknown> = {},
): 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);
};