mindroot.coreplugins.l8n.mod - MindRoot 9.10.0 documentation

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for mindroot.coreplugins.l8n.mod

    import os
    import re
    import json
    from pathlib import Path

    # Try to import from lib first (for same instance), fallback to mindroot.lib
    try:
        from lib.providers.commands import command, command_manager
        print("l8n: Using lib.providers.commands (same instance)")
    except ImportError:
        from mindroot.lib.providers.commands import command, command_manager
        print("l8n: Using mindroot.lib.providers.commands (fallback)")

    from .utils import extract_plugin_root, get_localized_file_path, load_plugin_translations, get_plugin_translations_path
    from mindroot.lib.utils.debug import debug_box

    debug_box("l8n: Top of mod.py")

    from .l8n_constants import *

    def save_plugin_translations(plugin_path: str, translations: dict):
        """Save translations for a specific plugin to disk."""
        translations_file = get_plugin_translations_path(plugin_path)

        try:
            # Ensure directory exists
            translations_file.parent.mkdir(parents=True, exist_ok=True)
            with open(translations_file, 'w', encoding='utf-8') as f:
                json.dump(translations, f, indent=2, ensure_ascii=False)
            return True
        except Exception as e:
            print(f"Warning: Could not save translations to {translations_file}: {e}")
            return False

    debug_box("l8n: defining command")

    # Debug: Check if command_manager has functions
    debug_box(f"l8n: command_manager has {len(command_manager.functions)} functions before registration")
    debug_box(f"l8n: command_manager instance ID: {id(command_manager)}")

    @command()
    async def write_localized_file(original_path: str, content: str, context=None):
        """
        Write a localized version of a file with static placeholders.

        This command creates a localized version of a template or source file
        with __TRANSLATE_key__ placeholders that will be replaced with actual
        translations when the file is loaded.

        PLACEHOLDER FORMAT RULES:
        - Always use the exact format: __TRANSLATE_key_name__
        - Must start with __TRANSLATE_ and end with __
        - Use lowercase letters, numbers, and underscores only for the key name
        - Use descriptive, hierarchical key names like section_element or buttons_save
        - NO spaces, hyphens, or special characters in key names

        Args:
            original_path: Absolute path to the original file
            content: File content with __TRANSLATE_key__ placeholders
            context: Command context (optional)

        Examples:
            await write_localized_file(
                "/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2",
                "<h1>__TRANSLATE_chat_title__</h1><button>__TRANSLATE_buttons_send__</button>"
            )

            await write_localized_file(
                "/some/path/src/my_plugin/templates/dashboard.jinja2",
                "<div>__TRANSLATE_dashboard_welcome__</div>"
            )
        """
        try:
            localized_path = get_localized_file_path(original_path)

            # Create directory if it doesn't exist
            localized_path.parent.mkdir(parents=True, exist_ok=True)

            # Write the content
            with open(localized_path, 'w', encoding='utf-8') as f:
                f.write(content)

            return f"Localized file written to: {localized_path}"

        except Exception as e:
            return f"Error writing localized file: {str(e)}"

    @command()
    async def append_localized_file(original_path: str, content: str, context=None):
        """
        Append content to an existing localized file.

        Use this for large files that need to be built incrementally.
        Follow the same placeholder format rules as write_localized_file.

        Args:
            original_path: Absolute path to the original file
            content: Content to append with __TRANSLATE_key__ placeholders
            context: Command context (optional)

        Example:
            # First write the beginning
            await write_localized_file(
                "/path/to/large_template.jinja2",
                "<html><head><title>__TRANSLATE_page_title__</title></head>"
            )

            # Then append more sections
            await append_localized_file(
                "/path/to/large_template.jinja2",
                "<body><h1>__TRANSLATE_main_heading__</h1></body></html>"
            )
        """
        try:
            localized_path = get_localized_file_path(original_path)

            # Create directory if it doesn't exist
            localized_path.parent.mkdir(parents=True, exist_ok=True)

            # Append the content
            with open(localized_path, 'a', encoding='utf-8') as f:
                f.write(content)

            return f"Content appended to: {localized_path}"

        except Exception as e:
            return f"Error appending to localized file: {str(e)}"

    @command()
    async def set_translations(original_path: str, language: str, translations: dict, context=None):
        """
        Set translations for a specific language and plugin.

        This command stores the translation mappings that will be used to replace
        __TRANSLATE_key__ placeholders in localized files. Translations are stored
        per plugin based on the provided file path.

        Args:
            original_path: Absolute path to a file in the plugin (used to identify the plugin)
            language: Language code (e.g., 'en', 'es', 'fr', 'de')
            translations: Dictionary mapping translation keys to translated text
            context: Command context (optional)

        Examples:
            await set_translations(
                "/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2",
                'es',
                {
                    'chat_title': 'Interfaz de Chat',
                    'buttons_send': 'Enviar Mensaje',
                    'nav_home': 'Inicio',
                    'error_connection_failed': 'Error de conexión'
                }
            )

            await set_translations(
                "/some/path/src/my_plugin/templates/dashboard.jinja2",
                'fr',
                {
                    'dashboard_welcome': 'Bienvenue au Tableau de Bord',
                    'buttons_save': 'Enregistrer',
                    'nav_home': 'Accueil'
                }
            )
        """
        try:
            if not isinstance(translations, dict):
                return "Error: translations must be a dictionary"

            # Get the plugin-specific translations path
            plugin_key = str(get_plugin_translations_path(original_path))

            # Load existing translations for this plugin
            plugin_translations = load_plugin_translations(original_path)

            # Validate translation keys (should match placeholder format)
            invalid_keys = []
            for key in translations.keys():
                if not re.match(r'^[a-z0-9_]+$', key):
                    invalid_keys.append(key)

            if invalid_keys:
                return f"Error: Invalid translation keys (use lowercase, numbers, underscores only): {invalid_keys}"

            # Update translations for this language
            if language not in plugin_translations:
                plugin_translations[language] = {}

            plugin_translations[language].update(translations)

            # Save translations to disk
            if save_plugin_translations(original_path, plugin_translations):
                # Update cache
                TRANSLATIONS[plugin_key] = plugin_translations
                return f"Set {len(translations)} translations for language '{language}' in {Path(plugin_key).parent.name} plugin"
            else:
                return f"Error: Could not save translations"

        except Exception as e:
            return f"Error setting translations: {str(e)}"

    @command()
    async def get_translations(original_path: str = None, language: str = None, context=None):
        """
        Get translations for a specific plugin and language.

        Args:
            original_path: Absolute path to a file in the plugin (optional)
                          If not provided, returns all cached translations
            language: Language code to get translations for (optional)
            context: Command context (optional)

        Returns:
            Dictionary of translations

        Examples:
            # Get all translations for a plugin
            translations = await get_translations(
                "/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2"
            )

            # Get Spanish translations for a plugin
            spanish = await get_translations(
                "/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2",
                'es'
            )

            # Get all cached translations
            all_cached = await get_translations()
        """
        try:
            if original_path:
                # Load translations for specific plugin
                plugin_translations = load_plugin_translations(original_path)

                # Update cache
                plugin_key = str(get_plugin_translations_path(original_path))
                TRANSLATIONS[plugin_key] = plugin_translations

                if language:
                    return plugin_translations.get(language, {})
                else:
                    return plugin_translations
            else:
                # Return all cached translations
                return TRANSLATIONS

        except Exception as e:
            return f"Error getting translations: {str(e)}"

    @command()
    async def list_localized_files(context=None):
        """
        List all localized files that have been created.

        Returns:
            List of paths to localized files
        """
        try:
            localized_files = []

            if LOCALIZED_FILES_DIR.exists():
                for file_path in LOCALIZED_FILES_DIR.rglob("*.i18n.*"):
                    localized_files.append(str(file_path.relative_to(LOCALIZED_FILES_DIR)))

            return {
                "count": len(localized_files),
                "files": sorted(localized_files)
            }

        except Exception as e:
            return f"Error listing localized files: {str(e)}"

    debug_box(f"l8n: command_manager has {len(command_manager.functions)} functions after registration")

    debug_box("l8n: End of mod.py")