|
from __future__ import annotations |
|
|
|
import os |
|
import folder_paths |
|
import glob |
|
from aiohttp import web |
|
import json |
|
import logging |
|
from functools import lru_cache |
|
|
|
from utils.json_util import merge_json_recursive |
|
|
|
|
|
|
|
EXTRA_LOCALE_FILES = [ |
|
"nodeDefs.json", |
|
"commands.json", |
|
"settings.json", |
|
] |
|
|
|
|
|
def safe_load_json_file(file_path: str) -> dict: |
|
if not os.path.exists(file_path): |
|
return {} |
|
|
|
try: |
|
with open(file_path, "r", encoding="utf-8") as f: |
|
return json.load(f) |
|
except json.JSONDecodeError: |
|
logging.error(f"Error loading {file_path}") |
|
return {} |
|
|
|
|
|
class CustomNodeManager: |
|
@lru_cache(maxsize=1) |
|
def build_translations(self): |
|
"""Load all custom nodes translations during initialization. Translations are |
|
expected to be loaded from `locales/` folder. |
|
|
|
The folder structure is expected to be the following: |
|
- custom_nodes/ |
|
- custom_node_1/ |
|
- locales/ |
|
- en/ |
|
- main.json |
|
- commands.json |
|
- settings.json |
|
|
|
returned translations are expected to be in the following format: |
|
{ |
|
"en": { |
|
"nodeDefs": {...}, |
|
"commands": {...}, |
|
"settings": {...}, |
|
...{other main.json keys} |
|
} |
|
} |
|
""" |
|
|
|
translations = {} |
|
|
|
for folder in folder_paths.get_folder_paths("custom_nodes"): |
|
|
|
for custom_node_dir in sorted(glob.glob(os.path.join(folder, "*/"))): |
|
locales_dir = os.path.join(custom_node_dir, "locales") |
|
if not os.path.exists(locales_dir): |
|
continue |
|
|
|
for lang_dir in glob.glob(os.path.join(locales_dir, "*/")): |
|
lang_code = os.path.basename(os.path.dirname(lang_dir)) |
|
|
|
if lang_code not in translations: |
|
translations[lang_code] = {} |
|
|
|
|
|
main_file = os.path.join(lang_dir, "main.json") |
|
node_translations = safe_load_json_file(main_file) |
|
|
|
|
|
for extra_file in EXTRA_LOCALE_FILES: |
|
extra_file_path = os.path.join(lang_dir, extra_file) |
|
key = extra_file.split(".")[0] |
|
json_data = safe_load_json_file(extra_file_path) |
|
if json_data: |
|
node_translations[key] = json_data |
|
|
|
if node_translations: |
|
translations[lang_code] = merge_json_recursive( |
|
translations[lang_code], node_translations |
|
) |
|
|
|
return translations |
|
|
|
def add_routes(self, routes, webapp, loadedModules): |
|
|
|
@routes.get("/workflow_templates") |
|
async def get_workflow_templates(request): |
|
"""Returns a web response that contains the map of custom_nodes names and their associated workflow templates. The ones without templates are omitted.""" |
|
files = [ |
|
file |
|
for folder in folder_paths.get_folder_paths("custom_nodes") |
|
for file in glob.glob( |
|
os.path.join(folder, "*/example_workflows/*.json") |
|
) |
|
] |
|
workflow_templates_dict = ( |
|
{} |
|
) |
|
for file in files: |
|
custom_nodes_name = os.path.basename( |
|
os.path.dirname(os.path.dirname(file)) |
|
) |
|
workflow_name = os.path.splitext(os.path.basename(file))[0] |
|
workflow_templates_dict.setdefault(custom_nodes_name, []).append( |
|
workflow_name |
|
) |
|
return web.json_response(workflow_templates_dict) |
|
|
|
|
|
for module_name, module_dir in loadedModules: |
|
workflows_dir = os.path.join(module_dir, "example_workflows") |
|
if os.path.exists(workflows_dir): |
|
webapp.add_routes( |
|
[ |
|
web.static( |
|
"/api/workflow_templates/" + module_name, workflows_dir |
|
) |
|
] |
|
) |
|
|
|
@routes.get("/i18n") |
|
async def get_i18n(request): |
|
"""Returns translations from all custom nodes' locales folders.""" |
|
return web.json_response(self.build_translations()) |
|
|