Translator

  • community
  • integration
  • by limon
  • Takaro v0.0.29
  • all
View export JSON

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

  1. 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: ["/"]
  2. 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.
  3. In-Game Usage:
    • /translate <language> "text": Translates the provided text into the specified language and 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 last count messages from the specified player and sends them to the user via DM. If count is 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 maxMessageLength setting 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.

SettingTypeDefaultDescription
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.

  • setlanguage

    No help text available

    ArgumentTypeDefaultHelp
    language string 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();
  • translateme

    Translate recent messages from a specific player into your preferred language. Specify a number to translate multiple messages.

    ArgumentTypeDefaultHelp
    targetPlayerName string player name
    count string 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();
  • gettranslation

    No help text available

    ArgumentTypeDefaultHelp
    targetPlayerName string 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();
  • toggletranslation

    Enable or disable automatic translation of messages. Use 'toggletranslation on' to receive translated messages, 'toggletranslation off' to disable.

    ArgumentTypeDefaultHelp
    status string 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

    ArgumentTypeDefaultHelp
    text string The text that you want to translate
    language string what language you want to translate to?
    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();
  • setplayerlanguage

    No help text available

    ArgumentTypeDefaultHelp
    language string Language code to set (e.g., 'en' for English)
    targetPlayer string Name of the player to set language for
    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();

Hooks 3

Code that runs in reaction to a game or Takaro event.

  • 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;
    
        // Get message from eventData
        const msg = eventData.msg;
    
        // Initial check: msg must be a non-empty string
        if (typeof msg !== 'string' || !msg.trim()) {
            takaro.log.debug('[Translator Auto-Translate] Msg is not a string or empty/whitespace, skipping.');
            return;
        }
    
        // User's exact code for prefix and sender checks
        // (Using 'msg' variable for consistency after the initial type check)
        if (msg.startsWith('/')) {
            takaro.log.debug('[Translator Auto-Translate] Message starts with "/", skipping translation (user-defined rule).');
            return;
        }
        if (msg.startsWith('$')) {
            takaro.log.debug('[Translator Auto-Translate] Message starts with "$", skipping translation (user-defined rule).');
            return;
        }
        const senderNameFromUserCheck = data.player ? data.player.name : 'Non-player';
        if (senderNameFromUserCheck === 'Non-player') {
            // This implies data.player was null or undefined.
            takaro.log.info('[Translator Auto-Translate] Sender identified as "Non-player" (data.player was falsy by user-defined rule). Skipping translation.');
            return;
        }
        // If we reach here, data.player is truthy and senderNameFromUserCheck holds data.player.name.
    
        // Define senderName, senderId, and senderSteamId based on data.player.
        // We know data.player is valid from the check above.
        // A specific check for data.player.id is still a good practice.
        if (!data.player.id) {
            takaro.log.info(`[Translator Auto-Translate] Sender is "${senderNameFromUserCheck}", but sender ID (data.player.id) is missing. Skipping.`);
            return;
        }
        const senderName = senderNameFromUserCheck; // This is data.player.name
        const senderId = data.player.id;
        const senderSteamId = data.player.steamId; // This might be undefined if not present, handled later.
    
        // Dynamic command prefix check (after player validation)
        try {
            const prefixSetting = await takaro.settings.settingsControllerGetOne('commandPrefix', gameServerId);
            const dynamicPrefix = prefixSetting?.data?.data?.value;
            // Check if dynamicPrefix is not one of the already checked static prefixes
            if (dynamicPrefix && dynamicPrefix !== '/' && dynamicPrefix !== '$' && msg.startsWith(dynamicPrefix)) {
                takaro.log.debug(`[Translator Auto-Translate] Skipping command message (dynamic prefix "${dynamicPrefix}"): "${msg}"`);
                return;
            }
        } catch (error) {
            takaro.log.warn(`[Translator Auto-Translate] Error fetching dynamic command prefix: ${error.message}.`);
        }
    
        // Check global active state (from previous optimization)
        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) {
                takaro.log.warn(`[Translator Auto-Translate] Global active state variable "${TRANSLATOR_ACTIVE_STATE_KEY}" not found. Proceeding with translation checks. Ensure connect/disconnect hooks are running or have run at least once.`);
            }
        } catch (error) {
            takaro.log.error(`[Translator Auto-Translate] Error checking global active translation state: ${error.message}. Proceeding with caution.`);
        }
    
        // API key check
        if (!mod.userConfig.googleApiKey) {
            takaro.log.error('[Translator Auto-Translate] Not configured: missing Google API key.');
            return;
        }
    
        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) => {
            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;
            }
    
            if (senderSteamId && recipientLinkingId === senderSteamId) {
                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()] };
    
                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];
    
                const enabledRes = await takaro.variable.variableControllerSearch({
                    filters: { key: [TRANSLATION_ENABLED_KEY], gameServerId, playerId: [receiverPlayer.id], moduleId: [mod.moduleId] }
                });
                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] }
                });
                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()) {
                    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;
                }
    
                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);
            }
            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();
  • trasnslator active

    Player cconnected

    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();
  • 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();

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