mirror of
https://github.com/blender/blender-addons.git
synced 2025-07-25 16:05:20 +00:00
257 lines
11 KiB
Python
257 lines
11 KiB
Python
# SPDX-FileCopyrightText: 2013-2023 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
if "bpy" in locals():
|
|
import importlib
|
|
importlib.reload(settings)
|
|
importlib.reload(utils_i18n)
|
|
importlib.reload(utils_languages_menu)
|
|
else:
|
|
import bpy
|
|
from bpy.types import Operator
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
EnumProperty,
|
|
)
|
|
from . import settings
|
|
from bl_i18n_utils import utils as utils_i18n
|
|
from bl_i18n_utils import utils_languages_menu
|
|
|
|
import concurrent.futures
|
|
import io
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
|
|
|
|
# Operators ###################################################################
|
|
|
|
def i18n_updatetranslation_work_repo_callback(pot, lng, settings):
|
|
if not lng['use']:
|
|
return
|
|
if os.path.isfile(lng['po_path']):
|
|
po = utils_i18n.I18nMessages(uid=lng['uid'], kind='PO', src=lng['po_path'], settings=settings)
|
|
po.update(pot)
|
|
else:
|
|
po = pot
|
|
po.write(kind="PO", dest=lng['po_path'])
|
|
print("{} PO written!".format(lng['uid']))
|
|
|
|
|
|
class UI_OT_i18n_updatetranslation_work_repo(Operator):
|
|
"""Update i18n working repository (po files)"""
|
|
bl_idname = "ui.i18n_updatetranslation_work_repo"
|
|
bl_label = "Update I18n Work Repository"
|
|
|
|
use_skip_pot_gen: BoolProperty(
|
|
name="Skip POT",
|
|
description="Skip POT file generation",
|
|
default=False,
|
|
)
|
|
|
|
def execute(self, context):
|
|
if not hasattr(self, "settings"):
|
|
self.settings = settings.settings
|
|
i18n_sett = context.window_manager.i18n_update_settings
|
|
self.settings.FILE_NAME_POT = i18n_sett.pot_path
|
|
|
|
context.window_manager.progress_begin(0, len(i18n_sett.langs) + 1)
|
|
context.window_manager.progress_update(0)
|
|
if not self.use_skip_pot_gen:
|
|
env = os.environ.copy()
|
|
env["ASAN_OPTIONS"] = "exitcode=0:" + os.environ.get("ASAN_OPTIONS", "")
|
|
# Generate base pot from RNA messages (we use another blender instance here, to be able to perfectly
|
|
# control our environment (factory startup, specific addons enabled/disabled...)).
|
|
# However, we need to export current user settings about this addon!
|
|
cmmd = (
|
|
bpy.app.binary_path,
|
|
"--background",
|
|
"--factory-startup",
|
|
"--python",
|
|
os.path.join(os.path.dirname(utils_i18n.__file__), "bl_extract_messages.py"),
|
|
"--",
|
|
"--no_checks",
|
|
"--settings",
|
|
self.settings.to_json(),
|
|
)
|
|
# Not working (UI is not refreshed...).
|
|
#self.report({'INFO'}, "Extracting messages, this will take some time...")
|
|
context.window_manager.progress_update(1)
|
|
ret = subprocess.run(cmmd, env=env)
|
|
if ret.returncode != 0:
|
|
self.report({'ERROR'}, "Message extraction process failed!")
|
|
context.window_manager.progress_end()
|
|
return {'CANCELLED'}
|
|
|
|
# Now we should have a valid POT file, we have to merge it in all languages po's...
|
|
with concurrent.futures.ProcessPoolExecutor() as exctr:
|
|
pot = utils_i18n.I18nMessages(kind='PO', src=self.settings.FILE_NAME_POT, settings=self.settings)
|
|
num_langs = len(i18n_sett.langs)
|
|
for progress, _ in enumerate(exctr.map(i18n_updatetranslation_work_repo_callback,
|
|
(pot,) * num_langs,
|
|
[dict(lng.items()) for lng in i18n_sett.langs],
|
|
(self.settings,) * num_langs,
|
|
chunksize=4)):
|
|
context.window_manager.progress_update(progress + 2)
|
|
context.window_manager.progress_end()
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
wm = context.window_manager
|
|
return wm.invoke_props_dialog(self)
|
|
|
|
|
|
def i18n_cleanuptranslation_work_repo_callback(lng, settings):
|
|
if not lng['use']:
|
|
print("Skipping {} language ({}).".format(lng['name'], lng['uid']))
|
|
return
|
|
po = utils_i18n.I18nMessages(uid=lng['uid'], kind='PO', src=lng['po_path'], settings=settings)
|
|
errs = po.check(fix=True)
|
|
cleanedup_commented = po.clean_commented()
|
|
po.write(kind="PO", dest=lng['po_path'])
|
|
print("Processing {} language ({}).\n"
|
|
"Cleaned up {} commented messages.\n".format(lng['name'], lng['uid'], cleanedup_commented) +
|
|
("Errors in this po, solved as best as possible!\n\t" + "\n\t".join(errs) if errs else "") + "\n")
|
|
|
|
|
|
class UI_OT_i18n_cleanuptranslation_work_repo(Operator):
|
|
"""Clean up i18n working repository (po files)"""
|
|
bl_idname = "ui.i18n_cleanuptranslation_work_repo"
|
|
bl_label = "Clean up I18n Work Repository"
|
|
|
|
def execute(self, context):
|
|
if not hasattr(self, "settings"):
|
|
self.settings = settings.settings
|
|
i18n_sett = context.window_manager.i18n_update_settings
|
|
# 'DEFAULT' and en_US are always valid, fully-translated "languages"!
|
|
stats = {"DEFAULT": 1.0, "en_US": 1.0}
|
|
|
|
context.window_manager.progress_begin(0, len(i18n_sett.langs) + 1)
|
|
context.window_manager.progress_update(0)
|
|
with concurrent.futures.ProcessPoolExecutor() as exctr:
|
|
num_langs = len(i18n_sett.langs)
|
|
for progress, _ in enumerate(exctr.map(i18n_cleanuptranslation_work_repo_callback,
|
|
[dict(lng.items()) for lng in i18n_sett.langs],
|
|
(self.settings,) * num_langs,
|
|
chunksize=4)):
|
|
context.window_manager.progress_update(progress + 1)
|
|
|
|
context.window_manager.progress_end()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
def i18n_updatetranslation_blender_repo_callback(lng, settings):
|
|
reports = []
|
|
if lng['uid'] in settings.IMPORT_LANGUAGES_SKIP:
|
|
reports.append("Skipping {} language ({}), edit settings if you want to enable it.".format(lng['name'], lng['uid']))
|
|
return lng['uid'], 0.0, reports
|
|
if not lng['use']:
|
|
reports.append("Skipping {} language ({}).".format(lng['name'], lng['uid']))
|
|
return lng['uid'], 0.0, reports
|
|
po = utils_i18n.I18nMessages(uid=lng['uid'], kind='PO', src=lng['po_path'], settings=settings)
|
|
errs = po.check(fix=True)
|
|
reports.append("Processing {} language ({}).\n"
|
|
"Cleaned up {} commented messages.\n".format(lng['name'], lng['uid'], po.clean_commented()) +
|
|
("Errors in this po, solved as best as possible!\n\t" + "\n\t".join(errs) if errs else ""))
|
|
if lng['uid'] in settings.IMPORT_LANGUAGES_RTL:
|
|
po.rtl_process()
|
|
po.write(kind="PO_COMPACT", dest=lng['po_path_blender'])
|
|
po.update_info()
|
|
return lng['uid'], po.nbr_trans_msgs / po.nbr_msgs, reports
|
|
|
|
|
|
class UI_OT_i18n_updatetranslation_blender_repo(Operator):
|
|
"""Update i18n data (po files) in Blender source code repository"""
|
|
bl_idname = "ui.i18n_updatetranslation_blender_repo"
|
|
bl_label = "Update I18n Blender Repository"
|
|
|
|
def execute(self, context):
|
|
if not hasattr(self, "settings"):
|
|
self.settings = settings.settings
|
|
i18n_sett = context.window_manager.i18n_update_settings
|
|
# 'DEFAULT' and en_US are always valid, fully-translated "languages"!
|
|
stats = {"DEFAULT": 1.0, "en_US": 1.0}
|
|
|
|
context.window_manager.progress_begin(0, len(i18n_sett.langs) + 1)
|
|
context.window_manager.progress_update(0)
|
|
with concurrent.futures.ProcessPoolExecutor() as exctr:
|
|
num_langs = len(i18n_sett.langs)
|
|
for progress, (lng_uid, stats_val, reports) in enumerate(exctr.map(i18n_updatetranslation_blender_repo_callback,
|
|
[dict(lng.items()) for lng in i18n_sett.langs],
|
|
(self.settings,) * num_langs,
|
|
chunksize=4)):
|
|
context.window_manager.progress_update(progress + 1)
|
|
stats[lng_uid] = stats_val
|
|
print("".join(reports) + "\n")
|
|
|
|
print("Generating languages' menu...")
|
|
context.window_manager.progress_update(progress + 2)
|
|
languages_menu_lines = utils_languages_menu.gen_menu_file(stats, self.settings)
|
|
with open(os.path.join(self.settings.BLENDER_I18N_ROOT, self.settings.LANGUAGES_FILE), 'w', encoding="utf8") as f:
|
|
f.write("\n".join(languages_menu_lines))
|
|
context.window_manager.progress_end()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class UI_OT_i18n_updatetranslation_statistics(Operator):
|
|
"""Create or extend a 'i18n_info.txt' Text datablock"""
|
|
"""(it will contain statistics and checks about current working repository PO files)"""
|
|
bl_idname = "ui.i18n_updatetranslation_statistics"
|
|
bl_label = "Update I18n Statistics"
|
|
|
|
report_name = "i18n_info.txt"
|
|
|
|
def execute(self, context):
|
|
if not hasattr(self, "settings"):
|
|
self.settings = settings.settings
|
|
i18n_sett = context.window_manager.i18n_update_settings
|
|
|
|
buff = io.StringIO()
|
|
lst = [(lng, lng.po_path) for lng in i18n_sett.langs]
|
|
|
|
context.window_manager.progress_begin(0, len(lst))
|
|
context.window_manager.progress_update(0)
|
|
for progress, (lng, path) in enumerate(lst):
|
|
context.window_manager.progress_update(progress + 1)
|
|
if not lng.use:
|
|
print("Skipping {} language ({}).".format(lng.name, lng.uid))
|
|
continue
|
|
buff.write("Processing {} language ({}, {}).\n".format(lng.name, lng.uid, path))
|
|
po = utils_i18n.I18nMessages(uid=lng.uid, kind='PO', src=path, settings=self.settings)
|
|
po.print_info(prefix=" ", output=buff.write)
|
|
errs = po.check(fix=False)
|
|
if errs:
|
|
buff.write(" WARNING! Po contains following errors:\n")
|
|
buff.write(" " + "\n ".join(errs))
|
|
buff.write("\n")
|
|
buff.write("\n\n")
|
|
|
|
text = None
|
|
if self.report_name not in bpy.data.texts:
|
|
text = bpy.data.texts.new(self.report_name)
|
|
else:
|
|
text = bpy.data.texts[self.report_name]
|
|
data = text.as_string()
|
|
data = data + "\n" + buff.getvalue()
|
|
text.from_string(data)
|
|
self.report({'INFO'}, "Info written to %s text datablock!" % self.report_name)
|
|
context.window_manager.progress_end()
|
|
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
wm = context.window_manager
|
|
return wm.invoke_props_dialog(self)
|
|
|
|
|
|
classes = (
|
|
UI_OT_i18n_updatetranslation_work_repo,
|
|
UI_OT_i18n_cleanuptranslation_work_repo,
|
|
UI_OT_i18n_updatetranslation_blender_repo,
|
|
UI_OT_i18n_updatetranslation_statistics,
|
|
)
|