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 *
[docs]
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)}")
[docs]
@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)}"
[docs]
@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)}"
[docs]
@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)}"
[docs]
@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)}"
[docs]
@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")