Translator
- community
- integration
- by limon
- Takaro v0.0.29
- all
Enables real-time translation of in-game chat messages, facilitating cross-lingual communication among players.
Key Functionality
- Two-Way Translation: Translates chat messages between players speaking different languages.
- Automatic Translation: Can automatically translate messages based on players' configured language preferences.
- On-Demand Translation: Players can translate specific messages or recent messages from other players using commands.
- Configurable Languages: Server administrators can define the list of supported languages.
- Google Translate Integration: Leverages the Google Cloud Translation API for accurate and comprehensive language support.
- Permission Control: Granular permissions allow administrators to control who can use translation features.
How to Use
- Configuration:
googleApiKey: (Required) Enter your Google Cloud API key with access to the Cloud Translation API.maxMessageLength: Set the maximum character length for messages to be translated (to manage API usage/costs). Default: 500.defaultLanguage: Set the default language code (ISO 639-1) for translations when a player hasn't specified their preference. Default:en.supportedLanguages: Define an array of ISO 639-1 language codes (e.g.,["en", "es", "fr"]) for the languages supported by the module.commandPrefixes: (Optional) An array of prefixes that are used to identify commands. Messages that start with these prefixes will not be translated. Default:["/"]
- Permissions:
TRANSLATE_PERMISSION: Grants players access to basic translation commands (/translate,/setlanguage,/toggletranslation,/translateme).TRANSLATE_ADMIN: Allows administrators to set language preferences for other players (/setplayerlanguage).AUTO_TRANSLATE_PERMISSION: Allows players to receive automatically translated messages.
- In-Game Usage:
/translate <language> "text": Translates the providedtextinto the specifiedlanguageand sends it to the global chat./setlanguage <code>: Sets the player's preferred language for receiving translations (e.g.,/setlanguage es)./toggletranslation on/off: Enables or disables automatic translation for the player./translateme <player> [count]: Translates the lastcountmessages from the specifiedplayerand sends them to the user via DM. Ifcountis omitted, translates the last message./setplayerlanguage <player> <language>: (Admin only) Sets the preferred language for another player.
Important Considerations
- Google Cloud API Key: A valid Google Cloud API key with the Cloud Translation API enabled is essential for this module to function.
- Language Codes: Use correct ISO 639-1 two-letter language codes (e.g., 'en', 'es', 'fr'). Refer to the Google Cloud Translation API documentation for a full list.
- Permissions Management: Carefully manage the module's permissions to control access to translation features.
- Message Length: Be mindful of the
maxMessageLengthsetting to avoid excessive API usage and potential costs. - Error Handling: The module includes error handling, but it's important to monitor server logs for any translation-related issues.
Configuration 5
Settings you fill in when installing the module on a server.
| Setting | Type | Default | Description |
|---|---|---|---|
googleApiKey required googleApiKey | string | — | Your Google Cloud API key with access to the Translation API |
maxMessageLength maxMessageLength | number | 500 | Maximum length of messages that will be translated (to control API costs) |
enabledByDefault enabledByDefault | boolean | false | If true, all new players will automatically receive translated messages in English |
defaultLanguage defaultLanguage | string | "en" | The default language code to use if enabledByDefault is true |
supportedLanguages supportedLanguages | array | ["en","es","fr","de","it","pt","ru","zh","ja","ko","ar","hi","zh-CN","ro"] | List of supported language codes for translation |
Raw config schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"googleApiKey"
],
"additionalProperties": false,
"properties": {
"googleApiKey": {
"title": "googleApiKey",
"description": "Your Google Cloud API key with access to the Translation API",
"type": "string",
"minLength": 1
},
"maxMessageLength": {
"title": "maxMessageLength",
"description": "Maximum length of messages that will be translated (to control API costs)",
"default": 500,
"type": "number",
"minimum": 1
},
"enabledByDefault": {
"title": "enabledByDefault",
"description": "If true, all new players will automatically receive translated messages in English",
"default": false,
"type": "boolean"
},
"defaultLanguage": {
"title": "defaultLanguage",
"description": "The default language code to use if enabledByDefault is true",
"default": "en",
"type": "string"
},
"supportedLanguages": {
"title": "supportedLanguages",
"description": "List of supported language codes for translation",
"default": [
"en",
"es",
"fr",
"de",
"it",
"pt",
"ru",
"zh",
"ja",
"ko",
"ar",
"hi",
"zh-CN",
"ro"
],
"type": "array",
"items": {
"type": "string"
}
}
}
} Raw UI schema
{} Commands 6
Chat commands players trigger in game.
-
gettranslation
No help text available
Argument Type Default Help targetPlayerNamestring player name Command source
import { takaro, data, TakaroUserError } from '@takaro/helpers'; const LANGUAGE_VARIABLE_KEY = 'user_language'; async function translateText(text, targetLanguage, apiKey) { if (!text || !text.trim()) return ''; try { const response = await takaro.axios.post( `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`, { q: text, target: targetLanguage }, { headers: { 'Content-Type': 'application/json' } } ); const result = response.data; if (result.error) { throw new Error(result.error.message || "Unknown translation error"); } if (!result.data?.translations?.[0]) { throw new Error("Invalid response format from translation API"); } return result.data.translations[0].translatedText; } catch (error) { console.error(`Translation API error:`, error); return '[Translation error]'; } } async function main() { try { const { player, arguments: args, gameServerId, module: mod } = data; // Validate configuration if (!mod.userConfig.googleApiKey) { throw new TakaroUserError('Translation feature is not configured. Google API key is missing.'); } // Find target player if (!args.targetPlayerName) { throw new TakaroUserError('Please specify a player to translate messages from.'); } const targetPlayer = await getTargetPlayer(args.targetPlayerName); // Determine target language const targetLanguage = await determineTargetLanguage(args.targetLang, player.id, gameServerId, mod); // Get message count (between 1-20) const messageCount = Math.min(Math.max(parseInt(args.count || 5, 10), 1), 20); // Fetch and translate messages const messages = await fetchRecentMessages(targetPlayer.id, gameServerId, messageCount); const consolidatedMessage = await buildTranslatedOutput(messages, targetPlayer.name, targetLanguage, mod.userConfig.googleApiKey); await player.pm(consolidatedMessage); } catch (error) { console.error('Error in translation module:', error); throw error instanceof TakaroUserError ? error : new TakaroUserError(`An error occurred while translating messages: ${error.message}`); } } async function getTargetPlayer(targetPlayerName) { if (targetPlayerName.id) return targetPlayerName; const playerSearch = await takaro.player.playerControllerSearch({ search: { name: [targetPlayerName] } }); if (!playerSearch.data.data?.length) { throw new TakaroUserError(`Player "${targetPlayerName}" not found.`); } return playerSearch.data.data[0]; } async function determineTargetLanguage(requestedLanguage, playerId, gameServerId, mod) { // First priority: explicitly requested language if (requestedLanguage) { validateLanguage(requestedLanguage, mod); return requestedLanguage; } // Second priority: user's saved preference const userLanguageVar = await takaro.variable.variableControllerSearch({ filters: { key: [LANGUAGE_VARIABLE_KEY], gameServerId: [gameServerId], playerId: [playerId], moduleId: [mod.moduleId] } }); if (userLanguageVar.data.data?.length) { const savedLanguage = userLanguageVar.data.data[0].value; validateLanguage(savedLanguage, mod); return savedLanguage; } // Last priority: module default or English const defaultLanguage = mod.userConfig.defaultLanguage || 'en'; validateLanguage(defaultLanguage, mod); return defaultLanguage; } function validateLanguage(language, mod) { if (mod.userConfig.supportedLanguages && !mod.userConfig.supportedLanguages.includes(language)) { throw new TakaroUserError( `Unsupported target language: ${language}. ` + `Available languages: ${mod.userConfig.supportedLanguages.join(', ')}` ); } } async function fetchRecentMessages(playerId, gameServerId, messageCount) { const events = await takaro.event.eventControllerSearch({ limit: 100, sortBy: 'createdAt', sortDirection: 'desc', filters: { eventName: ['chat-message'], gameserverId: [gameServerId], playerId: [playerId] } }); if (!events.data.data?.length) { throw new TakaroUserError(`No chat messages found for this player.`); } // Filter out command messages (messages starting with '/' or '!') const filteredMessages = events.data.data .filter(event => { const message = event.meta.msg; return message && typeof message === 'string' && !message.startsWith('/') && !message.startsWith('!'); }) .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) .slice(0, messageCount) .map(event => ({ content: event.meta.msg, timestamp: event.createdAt })); if (filteredMessages.length === 0) { throw new TakaroUserError(`No non-command messages found for this player.`); } return filteredMessages; } async function buildTranslatedOutput(messages, playerName, targetLanguage, apiKey) { let output = `Messages from ${playerName} (${targetLanguage}):\n\n`; // Reverse the messages array to start from the oldest const reversedMessages = [...messages].reverse(); for (let i = 0; i < reversedMessages.length; i++) { const message = reversedMessages[i]; const translatedText = await translateText(message.content, targetLanguage, apiKey); // Just show the translation, not the original message output += `${i + 1}. ${translatedText}\n\n`; } return output; } await main(); -
translateme
Translate recent messages from a specific player into your preferred language. Specify a number to translate multiple messages.
Argument Type Default Help targetPlayerNamestring player name countstring 1 Number of most recent messages to translate (default: 1) Command source
import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers'; const LANGUAGE_VARIABLE_KEY = 'user_language'; const TRANSLATE_PERMISSION = 'TRANSLATE_PERMISSION'; async function translateText(text, targetLanguage, apiKey) { if (!text || !text.trim()) return ''; try { const response = await takaro.axios.post( `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`, { q: text, target: targetLanguage }, { headers: { 'Content-Type': 'application/json' } } ); const result = response.data; if (result.error) { throw new Error(result.error.message || "Unknown translation error"); } if (!result.data?.translations?.[0]) { throw new Error("Invalid response format from translation API"); } return result.data.translations[0].translatedText; } catch (error) { console.error(`Translation API error:`, error); return '[Translation error]'; } } async function main() { try { const { player, arguments: args, gameServerId, module: mod, pog } = data; // Check permission - ensure player has TRANSLATE_PERMISSION if (!checkPermission(pog, TRANSLATE_PERMISSION)) { throw new TakaroUserError("You don't have permission to use the translation feature."); } // Validate configuration if (!mod.userConfig.googleApiKey) { throw new TakaroUserError('Translation feature is not configured. Google API key is missing.'); } // Find target player if (!args.targetPlayerName) { throw new TakaroUserError('Please specify a player to translate messages from.'); } const targetPlayer = await getTargetPlayer(args.targetPlayerName); // Determine target language const targetLanguage = await determineTargetLanguage(args.targetLang, player.id, gameServerId, mod); // Get message count from args or default to 1 (just the last message) const messageCount = args.count ? Math.min(Math.max(parseInt(args.count, 10), 1), 20) : 1; // Fetch and translate messages const messages = await fetchRecentMessages(targetPlayer.id, gameServerId, messageCount); const consolidatedMessage = await buildTranslatedOutput(messages, targetPlayer.name, targetLanguage, mod.userConfig.googleApiKey); await player.pm(consolidatedMessage); } catch (error) { console.error('Error in translation module:', error); throw error instanceof TakaroUserError ? error : new TakaroUserError(`An error occurred while translating messages: ${error.message}`); } } async function getTargetPlayer(targetPlayerName) { if (targetPlayerName.id) return targetPlayerName; const playerSearch = await takaro.player.playerControllerSearch({ search: { name: [targetPlayerName] } }); if (!playerSearch.data.data?.length) { throw new TakaroUserError(`Player "${targetPlayerName}" not found.`); } return playerSearch.data.data[0]; } async function determineTargetLanguage(requestedLanguage, playerId, gameServerId, mod) { // First priority: explicitly requested language if (requestedLanguage) { validateLanguage(requestedLanguage, mod); return requestedLanguage; } // Second priority: user's saved preference const userLanguageVar = await takaro.variable.variableControllerSearch({ filters: { key: [LANGUAGE_VARIABLE_KEY], gameServerId: [gameServerId], playerId: [playerId], moduleId: [mod.moduleId] } }); if (userLanguageVar.data.data?.length) { const savedLanguage = userLanguageVar.data.data[0].value; validateLanguage(savedLanguage, mod); return savedLanguage; } // Last priority: module default or English const defaultLanguage = mod.userConfig.defaultLanguage || 'en'; validateLanguage(defaultLanguage, mod); return defaultLanguage; } function validateLanguage(language, mod) { if (mod.userConfig.supportedLanguages && !mod.userConfig.supportedLanguages.includes(language)) { throw new TakaroUserError( `Unsupported target language: ${language}. ` + `Available languages: ${mod.userConfig.supportedLanguages.join(', ')}` ); } } async function fetchRecentMessages(playerId, gameServerId, messageCount) { const events = await takaro.event.eventControllerSearch({ limit: 100, sortBy: 'createdAt', sortDirection: 'desc', filters: { eventName: ['chat-message'], gameserverId: [gameServerId], playerId: [playerId] } }); if (!events.data.data?.length) { throw new TakaroUserError(`No chat messages found for this player.`); } // Filter out command messages (messages starting with '/' or '!') const filteredMessages = events.data.data .filter(event => { const message = event.meta.msg; return message && typeof message === 'string' && !message.startsWith('/') && !message.startsWith('!'); }) .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) .slice(0, messageCount) .map(event => ({ content: event.meta.msg, timestamp: event.createdAt })); if (filteredMessages.length === 0) { throw new TakaroUserError(`No non-command messages found for this player.`); } return filteredMessages; } async function buildTranslatedOutput(messages, playerName, targetLanguage, apiKey) { let output = `Messages from ${playerName} (${targetLanguage}):\n\n`; // Reverse the messages array to start from the oldest const reversedMessages = [...messages].reverse(); for (let i = 0; i < reversedMessages.length; i++) { const message = reversedMessages[i]; const translatedText = await translateText(message.content, targetLanguage, apiKey); // Just show the translation, not the original message output += `${i + 1}. ${translatedText}\n\n`; } return output; } await main(); -
setplayerlanguage
No help text available
Argument Type Default Help targetPlayerstring Name of the player to set language for languagestring Language code to set (e.g., 'en' for English) Command source
import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers'; const LANGUAGE_VARIABLE_KEY = 'user_language'; async function main() { const { player, pog, gameServerId, module, arguments: args } = data; // Check if player has admin permissions to set other players' languages if (!checkPermission(pog, 'TRANSLATE_ADMIN')) { throw new TakaroUserError('You do not have permission to set languages for other players.'); } // Validate target player parameter if (!args.targetPlayer) { throw new TakaroUserError('Please specify a player name. Usage: /setplayerlanguage <player> <language>'); } // Validate language parameter if (!args.language) { throw new TakaroUserError('Please specify a language code. Usage: /setplayerlanguage <player> <language>'); } // Find the target player let targetPlayer; try { const targetPlayerSearch = await takaro.player.playerControllerSearch({ search: { name: [args.targetPlayer] } }); if (!targetPlayerSearch.data.data?.length) { throw new TakaroUserError(`Player "${args.targetPlayer}" not found.`); } targetPlayer = targetPlayerSearch.data.data[0]; } catch (error) { throw new TakaroUserError(`Failed to find player: ${error.message}`); } // Validate language code against supported languages in config const supportedLanguages = module.userConfig.supportedLanguages || []; if (supportedLanguages.length > 0 && !supportedLanguages.includes(args.language)) { throw new TakaroUserError( `Invalid language code. Valid options are: ${supportedLanguages.join(', ')}` ); } // Convert to lowercase for consistency const language = args.language.toLowerCase(); try { // Check if target player already has a language set const existingLanguageVar = await takaro.variable.variableControllerSearch({ filters: { key: [LANGUAGE_VARIABLE_KEY], gameServerId: [gameServerId], playerId: [targetPlayer.id], moduleId: [module.moduleId] } }); // If language variable exists, update it if (existingLanguageVar.data.data.length > 0) { await takaro.variable.variableControllerUpdate(existingLanguageVar.data.data[0].id, { value: language }); } // Otherwise create a new variable else { await takaro.variable.variableControllerCreate({ key: LANGUAGE_VARIABLE_KEY, value: language, gameServerId: gameServerId, playerId: targetPlayer.id, moduleId: module.moduleId }); } await player.pm(`Language for ${targetPlayer.name} has been set to: ${language}`); // Also notify the target player if they're online try { const pogList = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({ filters: { gameServerId: [gameServerId], playerId: [targetPlayer.id], online: [true] } }); if (pogList.data.data.length > 0) { // The player is online, we can send them a notification await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `An admin has set your translation language to: ${language}`, opts: { recipient: { gameId: pogList.data.data[0].gameId } } }); } } catch (error) { console.error(`Error notifying target player: ${error.message}`); // We don't want to fail the whole command if just the notification fails } } catch (error) { console.error(`Error setting player language: ${error.message}`); throw new TakaroUserError(`Failed to set language: ${error.message}`); } } await main(); -
toggletranslation
Enable or disable automatic translation of messages. Use 'toggletranslation on' to receive translated messages, 'toggletranslation off' to disable.
Argument Type Default Help statusstring off on/off to enable/disable translations Command source
import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers'; const TRANSLATION_ENABLED_KEY = 'translation_enabled'; async function main() { const { player, pog, gameServerId, module, arguments: args } = data; // Check if player has permission to use this command if (!checkPermission(pog, 'TRANSLATE_PERMISSION')) { throw new TakaroUserError('You do not have permission to use translation features.'); } // Get the current status if no argument is provided if (!args.status || (args.status !== 'on' && args.status !== 'off')) { const currentStatusVar = await takaro.variable.variableControllerSearch({ filters: { key: [TRANSLATION_ENABLED_KEY], gameServerId: [gameServerId], playerId: [player.id], moduleId: [module.moduleId] } }); // Check if we have a stored preference if (currentStatusVar.data.data.length > 0) { const isEnabled = currentStatusVar.data.data[0].value === 'true'; await player.pm(`Your translation status is currently: ${isEnabled ? 'ENABLED' : 'DISABLED'}`); await player.pm('Use "/toggletranslation on" or "/toggletranslation off" to change this setting.'); } else { // If no preference is set, check the module default setting const defaultEnabled = module.userConfig.enabledByDefault ?? false; await player.pm(`Your translation status is currently: ${defaultEnabled ? 'ENABLED' : 'DISABLED'} (using default setting)`); await player.pm('Use "/toggletranslation on" or "/toggletranslation off" to change this setting.'); } return; } // Convert argument to boolean const newStatus = args.status === 'on'; // Check if user already has a preference set const existingStatusVar = await takaro.variable.variableControllerSearch({ filters: { key: [TRANSLATION_ENABLED_KEY], gameServerId: [gameServerId], playerId: [player.id], moduleId: [module.moduleId] } }); // If preference exists, update it if (existingStatusVar.data.data.length > 0) { await takaro.variable.variableControllerUpdate(existingStatusVar.data.data[0].id, { value: newStatus.toString() }); } // Otherwise create a new variable else { await takaro.variable.variableControllerCreate({ key: TRANSLATION_ENABLED_KEY, value: newStatus.toString(), gameServerId: gameServerId, playerId: player.id, moduleId: module.moduleId }); } await player.pm(`Translation has been ${newStatus ? 'ENABLED' : 'DISABLED'} for you.`); // If translations were enabled but no language is set, prompt the user if (newStatus) { const languageVar = await takaro.variable.variableControllerSearch({ filters: { key: ['user_language'], gameServerId: [gameServerId], playerId: [player.id], moduleId: [module.moduleId] } }); if (languageVar.data.data.length === 0) { const defaultLanguage = module.userConfig.defaultLanguage || 'en'; await player.pm(`No language preference is set. Messages will be translated to ${defaultLanguage} by default.`); await player.pm(`Use "/setlanguage <code>" to set your preferred language.`); await player.pm(`Example: /setlanguage es (for Spanish)`); // List available languages if (module.userConfig.supportedLanguages && module.userConfig.supportedLanguages.length > 0) { await player.pm(`Supported languages: ${module.userConfig.supportedLanguages.join(', ')}`); } } } } await main(); -
translate
No help text available
Argument Type Default Help languagestring what language you want to translate to? textstring The text that you want to translate Command source
import { takaro, data, TakaroUserError } from '@takaro/helpers'; async function main() { const { player, gameServerId, module: mod, arguments: args } = data; // Get the text to translate const textToTranslate = args.text; if (!textToTranslate || textToTranslate.trim() === "") { throw new TakaroUserError("No text to translate was provided."); } // Validate language code against supported languages in config const validLanguages = mod.userConfig.supportedLanguages; if (!validLanguages.includes(args.language)) { throw new TakaroUserError( `Invalid language code. Valid options are: ${validLanguages.join(', ')}` ); } // First verify API key is configured if (!mod.userConfig.googleApiKey) { throw new TakaroUserError('Translation service is not properly configured. Please contact an administrator.'); } try { // Translate the text const apiKey = mod.userConfig.googleApiKey; const translation = await translateText(textToTranslate, args.language, apiKey); // Get language name for better UX const languageNames = { 'en': 'English', 'es': 'Spanish', 'fr': 'French', 'de': 'German', 'it': 'Italian', 'pt': 'Portuguese', 'ru': 'Russian', 'zh': 'Chinese', 'ja': 'Japanese', 'ko': 'Korean', 'ar': 'Arabic', 'hi': 'Hindi', 'tr': 'Turkish', 'nl': 'Dutch', 'pl': 'Polish', 'vi': 'Vietnamese', 'th': 'Thai', 'id': 'Indonesian', 'zh-CN': 'Chinese Simplified', 'ro': 'Romanian', 'pl': 'Polish' }; const languageName = languageNames[args.language] || args.language; // Send translation to the game server global chat await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `[${player.name} -> ${languageName}]: ${translation}` }); } catch (error) { console.error(`Translation error: ${error.message}`); throw new TakaroUserError(`Translation failed: ${error.message}`); } } // Translation function using Takaro's HTTP methods async function translateText(text, targetLanguage, apiKey) { try { // Use axios (available in the Takaro environment) const response = await takaro.axios.post( `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`, { q: text, target: targetLanguage }, { headers: { 'Content-Type': 'application/json' } } ); const result = response.data; if (result.error) { throw new Error(result.error.message || "Unknown translation error"); } if (!result.data || !result.data.translations || !result.data.translations[0]) { throw new Error("Invalid response format from translation API"); } return result.data.translations[0].translatedText; } catch (error) { console.error(`Translation API error: ${error.message}`); throw new Error(`API error: ${error.message}`); } } await main(); -
setlanguage
No help text available
Argument Type Default Help languagestring Set your language Command source
import { takaro, data, TakaroUserError } from '@takaro/helpers'; const LANGUAGE_VARIABLE_KEY = 'user_language'; async function main() { const { player, pog, gameServerId, module, arguments: args } = data; // List of supported languages - you can expand this as needed const supportedLanguages = [ 'en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'zh', 'ja', 'ko', 'ar', 'hi', 'bn', 'pa', 'te', 'mr', 'tr', 'vi', 'id', 'ms', 'th', 'nl', 'sv', 'no', 'da', 'fi', 'pl', 'uk', 'cs', 'sk', 'hu', 'ro', 'bg', 'el', 'he', 'fa', 'ro', 'zh-CN', 'zh-cn' ]; // If no language specified, show current language or help if (!args.language) { const currentLangVar = await takaro.variable.variableControllerSearch({ filters: { key: [LANGUAGE_VARIABLE_KEY], gameServerId: [gameServerId], playerId: [player.id], moduleId: [module.moduleId] } }); if (currentLangVar.data.data.length > 0) { const currentLang = currentLangVar.data.data[0].value; await player.pm(`Your language is currently set to: ${currentLang}`); } else { await player.pm(`You haven't set a language yet. Use this command with a language code to set your language.`); await player.pm(`Example: /setlanguage en`); await player.pm(`Supported languages: ${supportedLanguages.join(', ')}`); } return; } // Convert to lowercase for consistency const language = args.language.toLowerCase(); // Validate the language if (!supportedLanguages.includes(language)) { throw new TakaroUserError(`Unsupported language: ${language}. Supported languages: ${supportedLanguages.join(', ')}`); } // Check if user already has a language set const existingLanguageVar = await takaro.variable.variableControllerSearch({ filters: { key: [LANGUAGE_VARIABLE_KEY], gameServerId: [gameServerId], playerId: [player.id], moduleId: [module.moduleId] } }); // If language variable exists, update it if (existingLanguageVar.data.data.length > 0) { await takaro.variable.variableControllerUpdate(existingLanguageVar.data.data[0].id, { value: language }); } // Otherwise create a new variable else { await takaro.variable.variableControllerCreate({ key: LANGUAGE_VARIABLE_KEY, value: language, gameServerId: gameServerId, playerId: player.id, moduleId: module.moduleId }); } await player.pm(`Your language has been set to: ${language}`); } await main();
Hooks 3
Code that runs in reaction to a game or Takaro event.
-
transslator deactivate
Hook for log events
Hook source
import { takaro, data } from '@takaro/helpers'; const LANGUAGE_VARIABLE_KEY = 'user_language'; const TRANSLATION_ENABLED_KEY = 'translation_enabled'; const TRANSLATOR_ACTIVE_STATE_KEY = 'translator_active_state'; // Stores 'true' or 'false' async function checkAndSetTranslatorActiveState(gameServerId, mod, eventContext) { takaro.log.info(`[Translator State] ${eventContext}: Attempting to update active translation state...`); let rawOnlinePlayers; try { rawOnlinePlayers = (await takaro.gameserver.gameServerControllerGetPlayers(gameServerId)).data.data; } catch (error) { takaro.log.error(`[Translator State] ${eventContext}: Error fetching online players: ${error.message}. Cannot update state.`); return; } // If the player who disconnected was the last one, rawOnlinePlayers might now be empty. if (!rawOnlinePlayers || rawOnlinePlayers.length === 0) { takaro.log.info(`[Translator State] ${eventContext}: No players reported online after event. Setting active translation state to false.`); await updateTranslatorActiveState(gameServerId, mod.moduleId, false, eventContext); return; } const onlinePlayerTakaroDetails = []; for (const rawP of rawOnlinePlayers) { const linkingId = rawP.steamId || rawP.platformId || rawP.id; // Adjust based on game server data if (!linkingId) continue; try { let filter = {}; // Define filter based on available ID, e.g., steamId, platformId if (rawP.steamId) filter = { steamId: [rawP.steamId.toString()] }; else if (rawP.platformId) filter = { platformId: [rawP.platformId.toString()] }; // Example else if (rawP.id && typeof rawP.id === 'string' && rawP.id.length > 10) filter = { steamId: [rawP.id.toString()] }; // Fallback else continue; const playerRes = await takaro.player.playerControllerSearch({ filters: filter }); if (playerRes.data.data.length > 0) { onlinePlayerTakaroDetails.push(playerRes.data.data[0]); } } catch (e) { takaro.log.warn(`[Translator State] ${eventContext}: Error searching for Takaro player with linking ID ${linkingId}: ${e.message}`); } } // It's possible that after a disconnect, no *Takaro-resolved* players remain, even if rawOnlinePlayers had entries. if (onlinePlayerTakaroDetails.length < 1) { takaro.log.info(`[Translator State] ${eventContext}: Fewer than 1 resolved Takaro players online after event. Setting active translation state to false.`); await updateTranslatorActiveState(gameServerId, mod.moduleId, false, eventContext); return; } const defaultLang = (mod.userConfig.defaultLanguage || 'en').toLowerCase(); const uniqueEnabledLanguages = new Set(); let translationPotentiallyNeeded = false; for (const p of onlinePlayerTakaroDetails) { try { const enabledVar = await takaro.variable.variableControllerSearch({ filters: { key: [TRANSLATION_ENABLED_KEY], gameServerId, playerId: [p.id], moduleId: [mod.moduleId] } }); let isPlayerEnabled = mod.userConfig.enabledByDefault ?? false; if (enabledVar.data.data.length > 0 && enabledVar.data.data[0].value) { isPlayerEnabled = enabledVar.data.data[0].value === 'true'; } if (!isPlayerEnabled) { continue; } const langVar = await takaro.variable.variableControllerSearch({ filters: { key: [LANGUAGE_VARIABLE_KEY], gameServerId, playerId: [p.id], moduleId: [mod.moduleId] } }); let playerLang = defaultLang; if (langVar.data.data.length > 0 && langVar.data.data[0].value) { playerLang = langVar.data.data[0].value.toLowerCase(); } uniqueEnabledLanguages.add(playerLang); } catch (e) { takaro.log.warn(`[Translator State] ${eventContext}: Error fetching language/enabled status for player ${p.name} (ID ${p.id}): ${e.message}.`); uniqueEnabledLanguages.add(defaultLang); } } if (uniqueEnabledLanguages.size === 0 && onlinePlayerTakaroDetails.length > 0) { takaro.log.info(`[Translator State] ${eventContext}: All remaining online players have translation disabled. Setting active state to false.`); translationPotentiallyNeeded = false; } else if (uniqueEnabledLanguages.size > 1) { takaro.log.info(`[Translator State] ${eventContext}: Multiple (${uniqueEnabledLanguages.size}) unique languages among remaining enabled players. Setting active state to true.`); translationPotentiallyNeeded = true; } else if (uniqueEnabledLanguages.size === 1) { const singleLang = [...uniqueEnabledLanguages][0]; if (singleLang !== defaultLang) { takaro.log.info(`[Translator State] ${eventContext}: Single unique language ("${singleLang}") among remaining enabled players, different from default ("${defaultLang}"). Setting active state to true.`); translationPotentiallyNeeded = true; } else { takaro.log.info(`[Translator State] ${eventContext}: All remaining enabled online players use the default language ("${defaultLang}"). Setting active state to false.`); translationPotentiallyNeeded = false; } } else { // No unique enabled languages found (e.g. if the only player with a different lang disconnected and no one else is enabled) takaro.log.info(`[Translator State] ${eventContext}: No players with translation enabled or no unique languages identified among remaining players. Setting active state to false.`); translationPotentiallyNeeded = false; } await updateTranslatorActiveState(gameServerId, mod.moduleId, translationPotentiallyNeeded, eventContext); } async function updateTranslatorActiveState(gameServerId, moduleId, isActive, eventContext) { try { const existingStateVar = await takaro.variable.variableControllerSearch({ filters: { key: [TRANSLATOR_ACTIVE_STATE_KEY], gameServerId, moduleId } }); if (existingStateVar.data.data.length > 0) { const variableId = existingStateVar.data.data[0].id; if (existingStateVar.data.data[0].value !== isActive.toString()) { await takaro.variable.variableControllerUpdate(variableId, { value: isActive.toString() }); takaro.log.info(`[Translator State] ${eventContext}: Active translation state UPDATED to: ${isActive}`); } else { takaro.log.info(`[Translator State] ${eventContext}: Active translation state is already: ${isActive}. No update needed.`); } } else { await takaro.variable.variableControllerCreate({ key: TRANSLATOR_ACTIVE_STATE_KEY, value: isActive.toString(), gameServerId, moduleId, }); takaro.log.info(`[Translator State] ${eventContext}: Active translation state CREATED as: ${isActive}`); } } catch (error) { takaro.log.error(`[Translator State] ${eventContext}: Failed to update/create active state variable: ${error.message}`); } } async function main() { const { gameServerId, module: mod, player } = data; const eventContext = "Player disconnected"; // Specific context for this hook // This hook is specifically for 'player-disconnected' // The 'player' object in `data` for a disconnect event usually refers to the player who disconnected. takaro.log.info(`[Translator State] ${eventContext} Hook triggered for player: ${player?.name || 'N/A (Player data might not be fully available or relevant for disconnect event context)'}.`); // When a player disconnects, we re-evaluate the state based on who is *still* online. await checkAndSetTranslatorActiveState(gameServerId, mod, eventContext); } await main(); -
trasnslator active
Hook for player-connected events
Hook source
import { takaro, data } from '@takaro/helpers'; const LANGUAGE_VARIABLE_KEY = 'user_language'; const TRANSLATION_ENABLED_KEY = 'translation_enabled'; const TRANSLATOR_ACTIVE_STATE_KEY = 'translator_active_state'; // Stores 'true' or 'false' async function checkAndSetTranslatorActiveState(gameServerId, mod) { takaro.log.info('[Translator State] Player connected: Attempting to update active translation state...'); let rawOnlinePlayers; try { rawOnlinePlayers = (await takaro.gameserver.gameServerControllerGetPlayers(gameServerId)).data.data; } catch (error) { takaro.log.error(`[Translator State] Player connected: Error fetching online players: ${error.message}. Cannot update state.`); return; } if (!rawOnlinePlayers || rawOnlinePlayers.length === 0) { takaro.log.info('[Translator State] Player connected: No players reported online. Setting active translation state to false.'); await updateTranslatorActiveState(gameServerId, mod.moduleId, false); return; } const onlinePlayerTakaroDetails = []; for (const rawP of rawOnlinePlayers) { const linkingId = rawP.steamId || rawP.platformId || rawP.id; // Adjust based on game server data if (!linkingId) continue; try { let filter = {}; // Define filter based on available ID, e.g., steamId, platformId if (rawP.steamId) filter = { steamId: [rawP.steamId.toString()] }; else if (rawP.platformId) filter = { platformId: [rawP.platformId.toString()] }; // Example else if (rawP.id && typeof rawP.id === 'string' && rawP.id.length > 10) filter = { steamId: [rawP.id.toString()] }; // Fallback else continue; const playerRes = await takaro.player.playerControllerSearch({ filters: filter }); if (playerRes.data.data.length > 0) { onlinePlayerTakaroDetails.push(playerRes.data.data[0]); } } catch (e) { takaro.log.warn(`[Translator State] Player connected: Error searching for Takaro player with linking ID ${linkingId}: ${e.message}`); } } if (onlinePlayerTakaroDetails.length < 1) { takaro.log.info('[Translator State] Player connected: Fewer than 1 resolved Takaro players online. Setting active translation state to false.'); await updateTranslatorActiveState(gameServerId, mod.moduleId, false); return; } const defaultLang = (mod.userConfig.defaultLanguage || 'en').toLowerCase(); const uniqueEnabledLanguages = new Set(); let translationPotentiallyNeeded = false; for (const p of onlinePlayerTakaroDetails) { try { const enabledVar = await takaro.variable.variableControllerSearch({ filters: { key: [TRANSLATION_ENABLED_KEY], gameServerId, playerId: [p.id], moduleId: [mod.moduleId] } }); let isPlayerEnabled = mod.userConfig.enabledByDefault ?? false; if (enabledVar.data.data.length > 0 && enabledVar.data.data[0].value) { isPlayerEnabled = enabledVar.data.data[0].value === 'true'; } if (!isPlayerEnabled) { continue; } const langVar = await takaro.variable.variableControllerSearch({ filters: { key: [LANGUAGE_VARIABLE_KEY], gameServerId, playerId: [p.id], moduleId: [mod.moduleId] } }); let playerLang = defaultLang; if (langVar.data.data.length > 0 && langVar.data.data[0].value) { playerLang = langVar.data.data[0].value.toLowerCase(); } uniqueEnabledLanguages.add(playerLang); } catch (e) { takaro.log.warn(`[Translator State] Player connected: Error fetching language/enabled status for player ${p.name} (ID ${p.id}): ${e.message}.`); uniqueEnabledLanguages.add(defaultLang); } } if (uniqueEnabledLanguages.size === 0 && onlinePlayerTakaroDetails.length > 0) { takaro.log.info('[Translator State] Player connected: All online players have translation disabled. Setting active state to false.'); translationPotentiallyNeeded = false; } else if (uniqueEnabledLanguages.size > 1) { takaro.log.info(`[Translator State] Player connected: Multiple (${uniqueEnabledLanguages.size}) unique languages among enabled players. Setting active state to true.`); translationPotentiallyNeeded = true; } else if (uniqueEnabledLanguages.size === 1) { const singleLang = [...uniqueEnabledLanguages][0]; if (singleLang !== defaultLang) { takaro.log.info(`[Translator State] Player connected: Single unique language ("${singleLang}") among enabled players, different from default ("${defaultLang}"). Setting active state to true.`); translationPotentiallyNeeded = true; } else { takaro.log.info(`[Translator State] Player connected: All enabled online players use the default language ("${defaultLang}"). Setting active state to false.`); translationPotentiallyNeeded = false; } } else { takaro.log.info('[Translator State] Player connected: No players with translation enabled or no unique languages identified. Setting active state to false.'); translationPotentiallyNeeded = false; } await updateTranslatorActiveState(gameServerId, mod.moduleId, translationPotentiallyNeeded); } async function updateTranslatorActiveState(gameServerId, moduleId, isActive) { try { const existingStateVar = await takaro.variable.variableControllerSearch({ filters: { key: [TRANSLATOR_ACTIVE_STATE_KEY], gameServerId, moduleId } }); if (existingStateVar.data.data.length > 0) { const variableId = existingStateVar.data.data[0].id; if (existingStateVar.data.data[0].value !== isActive.toString()) { await takaro.variable.variableControllerUpdate(variableId, { value: isActive.toString() }); takaro.log.info(`[Translator State] Player connected: Active translation state UPDATED to: ${isActive}`); } else { takaro.log.info(`[Translator State] Player connected: Active translation state is already: ${isActive}. No update needed.`); } } else { await takaro.variable.variableControllerCreate({ key: TRANSLATOR_ACTIVE_STATE_KEY, value: isActive.toString(), gameServerId, moduleId, }); takaro.log.info(`[Translator State] Player connected: Active translation state CREATED as: ${isActive}`); } } catch (error) { takaro.log.error(`[Translator State] Player connected: Failed to update/create active state variable: ${error.message}`); } } async function main() { const { gameServerId, module: mod, player } = data; // This hook is specifically for 'player-connected' takaro.log.info(`[Translator State] Player Connected Hook triggered for player: ${player?.name || 'N/A (Player data might not be fully available in this event payload for all game types)'}.`); // The delay was removed as setTimeout is not available. // If player data (like language variables) isn't immediately consistent upon connection, // this hook might occasionally get a slightly outdated view. // However, the state will be corrected on the next player connect/disconnect event. await checkAndSetTranslatorActiveState(gameServerId, mod); } await main(); -
auto-translate
Hook for chat-message events
Hook source
import { takaro, data, TakaroUserError } from '@takaro/helpers'; const LANGUAGE_VARIABLE_KEY = 'user_language'; const TRANSLATION_ENABLED_KEY = 'translation_enabled'; const TRANSLATOR_ACTIVE_STATE_KEY = 'translator_active_state'; // For checking global state async function main() { const { gameServerId, module: mod, eventData } = data; // Check global active state first try { const activeStateVar = await takaro.variable.variableControllerSearch({ filters: { key: [TRANSLATOR_ACTIVE_STATE_KEY], gameServerId, moduleId: [mod.moduleId] } }); if (activeStateVar?.data?.data?.length > 0 && activeStateVar.data.data[0].value === 'false') { takaro.log.info('[Translator Auto-Translate] Globally inactive based on player language diversity. Skipping message processing.'); return; } if (!activeStateVar?.data?.data?.length) { // Variable not found takaro.log.warn(`[Translator Auto-Translate] Global active state variable "${TRANSLATOR_ACTIVE_STATE_KEY}" not found. Proceeding, but connect/disconnect hooks may not be running or initializing state.`); // To be safe, if the state variable isn't set, it might be better to assume 'false' or trigger a state check. // For now, it proceeds if not found or not 'false'. } } catch (error) { takaro.log.error(`[Translator Auto-Translate] Error checking global active translation state: ${error.message}. Proceeding with caution.`); } // Original code logic starts here if (!mod.userConfig.googleApiKey) { takaro.log.error('[Translator Auto-Translate] Not configured: missing Google API key.'); return; } const msg = eventData.msg; if (typeof msg !== 'string' || !msg.trim()) { takaro.log.debug('[Translator Auto-Translate] Msg is not a string or empty, skipping.'); return; } try { const prefixSetting = await takaro.settings.settingsControllerGetOne('commandPrefix', gameServerId); const prefix = prefixSetting?.data?.data?.value; if (prefix && msg.startsWith(prefix)) { takaro.log.debug(`[Translator Auto-Translate] Skipping command message: "${msg}"`); return; } } catch (error) { takaro.log.warn(`[Translator Auto-Translate] Error fetching command prefix: ${error.message}. May translate commands.`); } if (!data.player || !data.player.id) { takaro.log.info('[Translator Auto-Translate] Skipping message: no player or player.id missing in event data.'); return; } const senderName = data.player.name; const senderId = data.player.id; const senderSteamId = data.player.steamId; // Assuming this is available and used for sender comparison const messageToTranslate = msg; const maxMessageLength = mod.userConfig.maxMessageLength || 500; if (messageToTranslate.length > maxMessageLength) { takaro.log.info(`[Translator Auto-Translate] Message from ${senderName} exceeds max length (${messageToTranslate.length}/${maxMessageLength}). Skipping.`); return; } takaro.log.debug(`[Translator Auto-Translate] Processing message from ${senderName}: "${messageToTranslate}"`); let onlinePlayers; try { onlinePlayers = (await takaro.gameserver.gameServerControllerGetPlayers(gameServerId))?.data?.data; if (!onlinePlayers) { takaro.log.error("[Translator Auto-Translate] Failed to get online players list or list is empty."); return; } } catch (error) { takaro.log.error(`[Translator Auto-Translate] Error fetching online players list: ${error.message}`); return; } takaro.log.debug(`[Translator Auto-Translate] Found ${onlinePlayers.length} online players.`); let sourceLanguage; try { sourceLanguage = await detectLanguage(messageToTranslate, mod.userConfig.googleApiKey); takaro.log.info(`[Translator Auto-Translate] Detected source language: "${sourceLanguage}" for message from ${senderName}.`); } catch (error) { takaro.log.error(`[Translator Auto-Translate] Language detection error: ${error.message}. Defaulting source to 'en'.`); sourceLanguage = 'en'; } const translationPromises = onlinePlayers.map(async (playerInfo) => { // playerInfo is from gameServerControllerGetPlayers, may not be full Takaro Player DTO // It typically contains platform-specific IDs like steamId, and gameId for PMs. const recipientLinkingId = playerInfo.steamId || playerInfo.platformId || playerInfo.id; const recipientGameIdForPM = playerInfo.gameId || playerInfo.id || playerInfo.entityId; if (!recipientLinkingId || !recipientGameIdForPM) { takaro.log.warn(`[Translator Auto-Translate] Skipping playerInfo due to missing linkingId or gameSpecificIdForPM: ${JSON.stringify(playerInfo)}`); return null; } // Skip the sender if (senderSteamId && recipientLinkingId === senderSteamId) { // Compare relevant IDs takaro.log.debug(`[Translator Auto-Translate] Skipping translation for message sender: ${playerInfo.name || recipientLinkingId}`); return null; } try { let filter = {}; if (playerInfo.steamId) filter = { steamId: [playerInfo.steamId.toString()] }; else if (playerInfo.platformId) filter = { platformId: [playerInfo.platformId.toString()] }; else filter = { steamId: [recipientLinkingId.toString()] }; // Fallback if only generic id const playerRes = await takaro.player.playerControllerSearch({ filters: filter }); if (!playerRes?.data?.data?.length) { takaro.log.debug(`[Translator Auto-Translate] Could not find Takaro player for linking ID: ${recipientLinkingId}`); return null; } const receiverPlayer = playerRes.data.data[0]; // This is Takaro Player DTO const enabledRes = await takaro.variable.variableControllerSearch({ filters: { key: [TRANSLATION_ENABLED_KEY], gameServerId, playerId: [receiverPlayer.id], moduleId: [mod.moduleId] } }); // Respect enabledByDefault from config if player has no specific setting let isReceiverEnabled = mod.userConfig.enabledByDefault ?? false; if (enabledRes?.data?.data?.length > 0 && enabledRes.data.data[0].value) { isReceiverEnabled = enabledRes.data.data[0].value === 'true'; } if (!isReceiverEnabled) { takaro.log.debug(`[Translator Auto-Translate] Player ${receiverPlayer.name} has translations disabled, skipping.`); return null; } const langVar = await takaro.variable.variableControllerSearch({ filters: { key: [LANGUAGE_VARIABLE_KEY], gameServerId, playerId: [receiverPlayer.id], moduleId: [mod.moduleId] } }); // Use defaultLanguage from config if player has no specific setting let targetLanguage = (mod.userConfig.defaultLanguage || 'en').toLowerCase(); if (langVar?.data?.data?.length > 0 && langVar.data.data[0].value) { targetLanguage = langVar.data.data[0].value.toLowerCase(); } takaro.log.debug(`[Translator Auto-Translate] Processing for ${receiverPlayer.name} (lang: ${targetLanguage}). Source: ${sourceLanguage}.`); if (targetLanguage === sourceLanguage.toLowerCase()) { // Ensure case-insensitive compare takaro.log.info(`[Translator Auto-Translate] Skipping translation for ${receiverPlayer.name} - target language (${targetLanguage}) is same as source.`); return null; } const translation = await translateText(messageToTranslate, targetLanguage, mod.userConfig.googleApiKey); if (!translation || translation === '[Translation error]' || translation.trim().toLowerCase() === messageToTranslate.trim().toLowerCase()) { takaro.log.debug(`[Translator Auto-Translate] Translation no-op or error for ${receiverPlayer.name}. Translation: "${translation}"`); return null; } // Send PM await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `[${senderName} (${sourceLanguage})]: ${translation}`, opts: { recipient: { gameId: recipientGameIdForPM.toString() } } }); takaro.log.info(`[Translator Auto-Translate] Sent translated message to ${receiverPlayer.name} (GameID: ${recipientGameIdForPM}).`); return true; } catch (error) { takaro.log.error(`[Translator Auto-Translate] Error processing player ${playerInfo.name || recipientLinkingId} for translation: ${error.message || error}`); return null; } }); await Promise.allSettled(translationPromises); takaro.log.info("[Translator Auto-Translate] Processing cycle complete."); } async function detectLanguage(text, apiKey) { if (!text || !text.trim()) { takaro.log.debug("[Translator DetectHelper] Text for language detection is empty."); throw new Error("Text for language detection is empty."); } try { const response = await takaro.axios.post( `https://translation.googleapis.com/language/translate/v2/detect?key=${apiKey}`, { q: text }, { headers: { 'Content-Type': 'application/json' } } ); if (response?.data?.error) { takaro.log.warn(`[Translator DetectHelper] Detection API error: ${response.data.error.message}`); throw new Error(response.data.error.message); } if (!response?.data?.data?.detections?.[0]?.[0]?.language) { takaro.log.warn("[Translator DetectHelper] Invalid response format from language detection API."); throw new Error("Invalid response format from language detection API"); } return response.data.data.detections[0][0].language; } catch (error) { takaro.log.error(`[Translator DetectHelper] Language detection API call failed: ${error.message || error}`); throw error; } } async function translateText(text, targetLanguage, apiKey) { if (!text || !text.trim()) { takaro.log.debug(`[Translator TranslateHelper] Text for translation to ${targetLanguage} is empty.`); return ""; } try { const response = await takaro.axios.post( `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`, { q: text, target: targetLanguage, format: "text" }, { headers: { 'Content-Type': 'application/json' } } ); const result = response?.data; if (result?.error) { takaro.log.warn(`[Translator TranslateHelper] Translation API error (target: ${targetLanguage}): ${result.error.message}`); throw new Error(result.error.message); } // Allow empty string as a valid translation result if (!result?.data?.translations?.[0] || typeof result.data.translations[0].translatedText === 'undefined') { takaro.log.warn(`[Translator TranslateHelper] Invalid response format or translatedText missing (target: ${targetLanguage}).`); throw new Error("Invalid response format from translation API"); } return result.data.translations[0].translatedText; } catch (error) { takaro.log.error(`[Translator TranslateHelper] Translation API call failed (target: ${targetLanguage}): ${error.message || error}`); return '[Translation error]'; } } await main();
Permissions 2
Roles you can grant to decide who may use what.
-
TRANSLATE_PERMISSION
Give players Translate permission
-
Admin Set Language
Admins can set language