chore: update the generator of json
- add PO file extractor for BME prototypes. - add validator for BME prototypes but not finished. - update json builder script.
This commit is contained in:
		
							
								
								
									
										230
									
								
								bbp_ng/tools/bme_relatives.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								bbp_ng/tools/bme_relatives.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,230 @@
 | 
			
		||||
import typing
 | 
			
		||||
import collections
 | 
			
		||||
import simple_po
 | 
			
		||||
 | 
			
		||||
#region Translation Constant
 | 
			
		||||
 | 
			
		||||
## TODO:
 | 
			
		||||
#  This translation context string prefix is cpoied from UTIL_translation.py.
 | 
			
		||||
#  If the context string of translation changed, please synchronize it.
 | 
			
		||||
 | 
			
		||||
CTX_TRANSLATION: str = 'BBP/BME'
 | 
			
		||||
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
#region BME Tokens
 | 
			
		||||
 | 
			
		||||
## TODO:
 | 
			
		||||
#  These token are copied from UTIL_bme.py.
 | 
			
		||||
#  If anything changed, such as BME standard, these tokens should be synchronized between these 2 modules.
 | 
			
		||||
 | 
			
		||||
TOKEN_IDENTIFIER: str = 'identifier'
 | 
			
		||||
 | 
			
		||||
TOKEN_SHOWCASE: str = 'showcase'
 | 
			
		||||
TOKEN_SHOWCASE_TITLE: str = 'title'
 | 
			
		||||
TOKEN_SHOWCASE_ICON: str = 'icon'
 | 
			
		||||
TOKEN_SHOWCASE_TYPE: str = 'type'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS: str = 'cfgs'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_FIELD: str = 'field'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_TYPE: str = 'type'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_TITLE: str = 'title'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_DESC: str = 'desc'
 | 
			
		||||
TOKEN_SHOWCASE_CFGS_DEFAULT: str = 'default'
 | 
			
		||||
 | 
			
		||||
TOKEN_SKIP: str = 'skip'
 | 
			
		||||
 | 
			
		||||
TOKEN_PARAMS: str = 'params'
 | 
			
		||||
TOKEN_PARAMS_FIELD: str = 'field'
 | 
			
		||||
TOKEN_PARAMS_DATA: str = 'data'
 | 
			
		||||
 | 
			
		||||
TOKEN_VARS: str = 'vars'
 | 
			
		||||
TOKEN_VARS_FIELD: str = 'field'
 | 
			
		||||
TOKEN_VARS_DATA: str = 'data'
 | 
			
		||||
 | 
			
		||||
TOKEN_VERTICES: str = 'vertices'
 | 
			
		||||
TOKEN_VERTICES_SKIP: str = 'skip'
 | 
			
		||||
TOKEN_VERTICES_DATA: str = 'data'
 | 
			
		||||
 | 
			
		||||
TOKEN_FACES: str = 'faces'
 | 
			
		||||
TOKEN_FACES_SKIP: str = 'skip'
 | 
			
		||||
TOKEN_FACES_TEXTURE: str = 'texture'
 | 
			
		||||
TOKEN_FACES_INDICES: str = 'indices'
 | 
			
		||||
TOKEN_FACES_UVS: str = 'uvs'
 | 
			
		||||
TOKEN_FACES_NORMALS: str = 'normals'
 | 
			
		||||
 | 
			
		||||
TOKEN_INSTANCES: str = 'instances'
 | 
			
		||||
TOKEN_INSTANCES_IDENTIFIER: str = 'identifier'
 | 
			
		||||
TOKEN_INSTANCES_SKIP: str = 'skip'
 | 
			
		||||
TOKEN_INSTANCES_PARAMS: str = 'params'
 | 
			
		||||
TOKEN_INSTANCES_TRANSFORM: str = 'transform'
 | 
			
		||||
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
class Reporter():
 | 
			
		||||
    """
 | 
			
		||||
    General reporter commonly used by BME validator.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def __report(self, type: str, msg: str, context: str | None) -> None:
 | 
			
		||||
        strl: str = f'[{type}]'
 | 
			
		||||
        if context is not None:
 | 
			
		||||
            strl += f'[{context}]'
 | 
			
		||||
        strl += ' ' + msg
 | 
			
		||||
        print(strl)
 | 
			
		||||
 | 
			
		||||
    def error(self, msg: str, context: str | None = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Report an error.
 | 
			
		||||
        @param[in] msg The message to show.
 | 
			
		||||
        @param[in] context The context of this message, e.g. the file path. None if no context.
 | 
			
		||||
        """
 | 
			
		||||
        self.__report('Error', msg, context)
 | 
			
		||||
 | 
			
		||||
    def warning(self, msg: str, context: str | None = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Report a warning.
 | 
			
		||||
        @param[in] msg The message to show.
 | 
			
		||||
        @param[in] context The context of this message, e.g. the file path. None if no context.
 | 
			
		||||
        """
 | 
			
		||||
        self.__report('Warning', msg, context)
 | 
			
		||||
 | 
			
		||||
    def info(self, msg: str, context: str | None = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Report a info.
 | 
			
		||||
        @param[in] msg The message to show.
 | 
			
		||||
        @param[in] context The context of this message, e.g. the file path. None if no context.
 | 
			
		||||
        """
 | 
			
		||||
        self.__report('Info', msg, context)
 | 
			
		||||
 | 
			
		||||
class Hierarchy():
 | 
			
		||||
    """
 | 
			
		||||
    The hierarchy builder for BME validator to build context string representing the location where error happen.
 | 
			
		||||
    And it can be utilized by BME extractor to generate the context of translation.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __mStack: collections.deque[str]
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.__mStack = collections.deque()
 | 
			
		||||
 | 
			
		||||
    def push(self, item: str) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Add an item into this hierarchy.
 | 
			
		||||
        @param[in] item New added item.
 | 
			
		||||
        """
 | 
			
		||||
        self.__mStack.append(item)
 | 
			
		||||
 | 
			
		||||
    def push_index(self, index: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Add an integral index into this hierarchy.
 | 
			
		||||
        @details
 | 
			
		||||
        The difference between this and normal push function is that added item is integral index.
 | 
			
		||||
        This function will automatically convert it to string with a special format first, then push it into hierarchy.
 | 
			
		||||
        @param[in] item New added index.
 | 
			
		||||
        """
 | 
			
		||||
        self.__mStack.append(f'[{index}]')
 | 
			
		||||
 | 
			
		||||
    def pop(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Remove the top item from hierarchy
 | 
			
		||||
        """
 | 
			
		||||
        self.__mStack.pop()
 | 
			
		||||
 | 
			
		||||
    def build_hierarchy_string(self) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        Build the string which can represent this hierarchy.
 | 
			
		||||
        @return The built string representing this hierarchy.
 | 
			
		||||
        """
 | 
			
		||||
        return '/'.join(self.__mStack)
 | 
			
		||||
 | 
			
		||||
class BMEValidator():
 | 
			
		||||
    """
 | 
			
		||||
    The validator for BME prototype declarartions.
 | 
			
		||||
    This validator will validate given prototype declaration JSON structure,
 | 
			
		||||
    to check then whether have all essential fields BME standard required and whether have any unknown fields.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __mPrototypeSet: set[str]
 | 
			
		||||
    __mHierarchy: Hierarchy
 | 
			
		||||
    __mReporter: Reporter
 | 
			
		||||
 | 
			
		||||
    def __init__(self, reporter: Reporter):
 | 
			
		||||
        self.__mPrototypeSet = set()
 | 
			
		||||
        self.__mHierarchy = Hierarchy()
 | 
			
		||||
        self.__mReporter = reporter
 | 
			
		||||
 | 
			
		||||
    def validate(self, assoc_file: str, prototypes: typing.Any) -> None:
 | 
			
		||||
        self.__mHierarchy.push(assoc_file)
 | 
			
		||||
 | 
			
		||||
        self.__mHierarchy.pop()
 | 
			
		||||
 | 
			
		||||
class BMEExtractor():
 | 
			
		||||
    """
 | 
			
		||||
    A GetText extractor for BME prototype declarations.
 | 
			
		||||
    This extractor can extract all UI infomations which will be shown on Blender first.
 | 
			
		||||
    Then write them into caller given PO file. So that translator can translate them.
 | 
			
		||||
 | 
			
		||||
    Blender default I18N plugin can not recognise these dynamic loaded content,
 | 
			
		||||
    so that's the reason why this class invented.
 | 
			
		||||
 | 
			
		||||
    Please note all data should be validate first, then pass to this class.
 | 
			
		||||
    Otherwise it is undefined behavior.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __mAssocFile: str
 | 
			
		||||
    __mHierarchy: Hierarchy
 | 
			
		||||
    __mPoWriter: simple_po.PoWriter
 | 
			
		||||
 | 
			
		||||
    def __init__(self, po_writer: simple_po.PoWriter):
 | 
			
		||||
        self.__mAssocFile = ''
 | 
			
		||||
        self.__mHierarchy = Hierarchy()
 | 
			
		||||
        self.__mPoWriter = po_writer
 | 
			
		||||
 | 
			
		||||
    def extract(self, assoc_file: str, prototypes: list[dict[str, typing.Any]]) -> None:
 | 
			
		||||
        self.__mAssocFile = assoc_file
 | 
			
		||||
        for prototype in prototypes:
 | 
			
		||||
            self.__extract_prototype(prototype)
 | 
			
		||||
 | 
			
		||||
    def __add_translation(self, strl: str) -> None:
 | 
			
		||||
        self.__mPoWriter.add_entry(
 | 
			
		||||
            strl,
 | 
			
		||||
            CTX_TRANSLATION + '/' + self.__mHierarchy.build_hierarchy_string(),
 | 
			
		||||
            self.__mAssocFile
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __extract_prototype(self, prototype: dict[str, typing.Any]) -> None:
 | 
			
		||||
        # get identifier first
 | 
			
		||||
        identifier: str = prototype[TOKEN_IDENTIFIER]
 | 
			
		||||
        self.__mHierarchy.push(identifier)
 | 
			
		||||
 | 
			
		||||
        # get showcase node and only write PO file if it is not template prototype
 | 
			
		||||
        showcase: dict[str, typing.Any] | None = prototype[TOKEN_SHOWCASE]
 | 
			
		||||
        if showcase is not None:
 | 
			
		||||
            self.__extract_showcase(showcase)
 | 
			
		||||
 | 
			
		||||
        self.__mHierarchy.pop()
 | 
			
		||||
 | 
			
		||||
    def __extract_showcase(self, showcase: dict[str, typing.Any]) -> None:
 | 
			
		||||
        # export self name first
 | 
			
		||||
        self.__add_translation(showcase[TOKEN_SHOWCASE_TITLE])
 | 
			
		||||
 | 
			
		||||
        # iterate cfgs
 | 
			
		||||
        cfgs: list[dict[str, typing.Any]] = showcase[TOKEN_SHOWCASE_CFGS]
 | 
			
		||||
        for cfg_index, cfg in enumerate(cfgs):
 | 
			
		||||
            self.__extract_showcase_cfg(cfg_index, cfg)
 | 
			
		||||
 | 
			
		||||
    def __extract_showcase_cfg(self, index: int, cfg: dict[str, typing.Any]) -> None:
 | 
			
		||||
        self.__mHierarchy.push_index(index)
 | 
			
		||||
 | 
			
		||||
        # extract field title and description
 | 
			
		||||
        title: str = cfg[TOKEN_SHOWCASE_CFGS_TITLE]
 | 
			
		||||
        desc: str = cfg[TOKEN_SHOWCASE_CFGS_DESC]
 | 
			
		||||
 | 
			
		||||
        # and export them respectively
 | 
			
		||||
        self.__add_translation(title)
 | 
			
		||||
        self.__add_translation(desc)
 | 
			
		||||
 | 
			
		||||
        self.__mHierarchy.pop()
 | 
			
		||||
@ -1,17 +1,7 @@
 | 
			
		||||
import os, json
 | 
			
		||||
import os, json, typing
 | 
			
		||||
import bme_relatives, simple_po
 | 
			
		||||
import common
 | 
			
		||||
 | 
			
		||||
def compress_json(src_file: str, dst_file: str) -> None:
 | 
			
		||||
    with open(src_file, 'r', encoding = 'utf-8') as fr:
 | 
			
		||||
        with open(dst_file, 'w', encoding = 'utf-8') as fw:
 | 
			
		||||
            json.dump(
 | 
			
		||||
                json.load(fr),  # load from src file
 | 
			
		||||
                fw,
 | 
			
		||||
                indent = None,  # no indent. the most narrow style.
 | 
			
		||||
                separators = (',', ':'),    # also for narrow style.
 | 
			
		||||
                sort_keys = False,  # do not sort key
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
def create_compressed_jsons() -> None:
 | 
			
		||||
    # get folder path
 | 
			
		||||
    root_folder: str = common.get_plugin_folder()
 | 
			
		||||
@ -38,6 +28,83 @@ def create_compressed_jsons() -> None:
 | 
			
		||||
 | 
			
		||||
    print('Done.')
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    create_compressed_jsons()
 | 
			
		||||
class JsonCompressor():
 | 
			
		||||
 | 
			
		||||
    __mReporter: bme_relatives.Reporter
 | 
			
		||||
    __mPoWriter: simple_po.PoWriter
 | 
			
		||||
    __mValidator: bme_relatives.BMEValidator
 | 
			
		||||
    __mExtractor: bme_relatives.BMEExtractor
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.__mReporter = bme_relatives.Reporter()
 | 
			
		||||
        self.__mPoWriter = simple_po.PoWriter(
 | 
			
		||||
            os.path.join(common.get_plugin_folder(), 'i18n', 'bme.pot'), 
 | 
			
		||||
            'BME Prototypes'
 | 
			
		||||
        )
 | 
			
		||||
        self.__mValidator = bme_relatives.BMEValidator(self.__mReporter)
 | 
			
		||||
        self.__mExtractor = bme_relatives.BMEExtractor(self.__mPoWriter)
 | 
			
		||||
 | 
			
		||||
    def __enter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
    
 | 
			
		||||
    def __exit__(self, exc_type, exc_value, traceback):
 | 
			
		||||
        self.close()
 | 
			
		||||
    
 | 
			
		||||
    def close(self) -> None:
 | 
			
		||||
        self.__mPoWriter.close()
 | 
			
		||||
 | 
			
		||||
    def run(self) -> None:
 | 
			
		||||
        self.__compress_jsons()
 | 
			
		||||
 | 
			
		||||
    def __compress_jsons(self) -> None:
 | 
			
		||||
        # get folder path
 | 
			
		||||
        root_folder: str = common.get_plugin_folder()
 | 
			
		||||
 | 
			
		||||
        # prepare handler
 | 
			
		||||
        def folder_handler(src_folder: str, dst_folder: str) -> None:
 | 
			
		||||
            # just create folder
 | 
			
		||||
            self.__mReporter.info(f'Creating Folder: {src_folder} -> {dst_folder}')
 | 
			
		||||
            os.makedirs(dst_folder, exist_ok = True)
 | 
			
		||||
        def file_handler(src_file: str, dst_file: str) -> None:
 | 
			
		||||
            # skip non-json
 | 
			
		||||
            if not src_file.endswith('.json'): return
 | 
			
		||||
            # call compress func
 | 
			
		||||
            self.__mReporter.info(f'Processing Json: {src_file} -> {dst_file}')
 | 
			
		||||
            self.__compress_json(src_file, dst_file)
 | 
			
		||||
 | 
			
		||||
        # call common processor
 | 
			
		||||
        common.common_file_migrator(
 | 
			
		||||
            os.path.join(root_folder, 'raw_jsons'),
 | 
			
		||||
            os.path.join(root_folder, 'jsons'),
 | 
			
		||||
            folder_handler,
 | 
			
		||||
            file_handler
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.__mReporter.info('Done.')
 | 
			
		||||
 | 
			
		||||
    def __compress_json(self, src_file: str, dst_file: str) -> None:
 | 
			
		||||
        # load data first
 | 
			
		||||
        loaded_prototypes: typing.Any
 | 
			
		||||
        with open(src_file, 'r', encoding = 'utf-8') as fr:
 | 
			
		||||
            loaded_prototypes = json.load(fr)
 | 
			
		||||
 | 
			
		||||
        # validate loaded data
 | 
			
		||||
        self.__mValidator.validate(os.path.basename(src_file), loaded_prototypes)
 | 
			
		||||
 | 
			
		||||
        # extract translation
 | 
			
		||||
        self.__mExtractor.extract(os.path.basename(src_file), loaded_prototypes)
 | 
			
		||||
 | 
			
		||||
        # save result
 | 
			
		||||
        with open(dst_file, 'w', encoding = 'utf-8') as fw:
 | 
			
		||||
            json.dump(
 | 
			
		||||
                loaded_prototypes,  # loaded data
 | 
			
		||||
                fw,
 | 
			
		||||
                indent = None,  # no indent. the most narrow style.
 | 
			
		||||
                separators = (',', ':'),    # also for narrow style.
 | 
			
		||||
                sort_keys = False,  # do not sort key
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    with JsonCompressor() as json_compressor:
 | 
			
		||||
        json_compressor.run()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										99
									
								
								bbp_ng/tools/simple_po.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								bbp_ng/tools/simple_po.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
			
		||||
import typing
 | 
			
		||||
import io
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
class PoWriter():
 | 
			
		||||
    """
 | 
			
		||||
    The simple PO file writer.
 | 
			
		||||
    This class is just served for writing POT files.
 | 
			
		||||
    It may be convenient when exporting PO file for thoese whose format can not be parsed by formal tools.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __cEscapeCharsDict: typing.ClassVar[dict[str, str]] = {
 | 
			
		||||
        '\\': '\\\\',
 | 
			
		||||
        '"': '\\"',
 | 
			
		||||
        '\n': '\\n',
 | 
			
		||||
        '\t': '\\t',
 | 
			
		||||
    }
 | 
			
		||||
    __cEscapeCharsTable: typing.ClassVar[dict] = str.maketrans(__cEscapeCharsDict)
 | 
			
		||||
    __mPoFile: io.TextIOWrapper
 | 
			
		||||
 | 
			
		||||
    def __init__(self, po_file_path: str, project_name: str):
 | 
			
		||||
        # open file
 | 
			
		||||
        self.__mPoFile = open(po_file_path, 'w', encoding = 'utf-8')
 | 
			
		||||
        # add default header
 | 
			
		||||
        self.__add_header(project_name)
 | 
			
		||||
 | 
			
		||||
    def __enter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
    
 | 
			
		||||
    def __exit__(self, exc_type, exc_value, traceback):
 | 
			
		||||
        self.close()
 | 
			
		||||
    
 | 
			
		||||
    def close(self) -> None:
 | 
			
		||||
        self.__mPoFile.close()
 | 
			
		||||
 | 
			
		||||
    def __write_line(self, val: str) -> None:
 | 
			
		||||
        self.__mPoFile.write(val)
 | 
			
		||||
        self.__mPoFile.write('\n')
 | 
			
		||||
 | 
			
		||||
    def __escape_str(self, val: str) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        This function escapes a given string to make it safe to use as a C++ string literal.
 | 
			
		||||
        @param[in] val Original string
 | 
			
		||||
        @return Escaped string
 | 
			
		||||
        """
 | 
			
		||||
        return val.translate(PoWriter.__cEscapeCharsTable)
 | 
			
		||||
 | 
			
		||||
    def __add_header(self, project_name: str) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Add default header for PO file.
 | 
			
		||||
        @param[in] project_name The project name written in file.
 | 
			
		||||
        """
 | 
			
		||||
        now_datetime = datetime.datetime.now()
 | 
			
		||||
        self.__write_line('# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.')
 | 
			
		||||
        self.__write_line('msgid ""')
 | 
			
		||||
        self.__write_line('msgstr ""')
 | 
			
		||||
        self.__write_line(f'"Project-Id-Version: {self.__escape_str(project_name)}\\n"')
 | 
			
		||||
        self.__write_line(f'"POT-Creation-Date: {now_datetime.strftime("%Y-%m-%d %H:%M%Z")}\\n"')
 | 
			
		||||
        self.__write_line('"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"')
 | 
			
		||||
        self.__write_line('"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"')
 | 
			
		||||
        self.__write_line('"Language-Team: LANGUAGE <LL@li.org>\\n"')
 | 
			
		||||
        self.__write_line('"Language: __POT__\\n"')
 | 
			
		||||
        self.__write_line('"MIME-Version: 1.0\\n"')
 | 
			
		||||
        self.__write_line('"Content-Type: text/plain; charset=UTF-8\\n"')
 | 
			
		||||
        self.__write_line('"Content-Transfer-Encoding: 8bit\\n"')
 | 
			
		||||
        self.__write_line('"X-Generator: simple_po.PoWriter\\n"')
 | 
			
		||||
 | 
			
		||||
    def add_entry(self, msg: str, msg_context: str | None = None, extracted_comment: str | None = None, reference: str | None = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        @brief Write an entry into PO file with given arguments.
 | 
			
		||||
        @details
 | 
			
		||||
        Please note this function will NOT check whether there already is a duplicated entry which has been written.
 | 
			
		||||
        You must check this on your own.
 | 
			
		||||
        @param[in] msg The message string need to be translated.
 | 
			
		||||
        @param[in] msg_context The context of this message.
 | 
			
		||||
        @param[in] extracted_comment The extracted comment of this message. None if no reference. Line breaker is not allowed.
 | 
			
		||||
        @param[in] reference The code refernece of this message. None if no reference. Line breaker is not allowed.
 | 
			
		||||
        """
 | 
			
		||||
        # empty string will not be translated
 | 
			
		||||
        if msg == '': return
 | 
			
		||||
 | 
			
		||||
        # write blank line first
 | 
			
		||||
        self.__write_line('')
 | 
			
		||||
        if extracted_comment:
 | 
			
		||||
            self.__write_line(f'#. {extracted_comment}')
 | 
			
		||||
        if reference:
 | 
			
		||||
            self.__write_line(f'#: {reference}')
 | 
			
		||||
        if msg_context:
 | 
			
		||||
            self.__write_line(f'msgctxt "{self.__escape_str(msg_context)}"')
 | 
			
		||||
        self.__write_line(f'msgid "{self.__escape_str(msg)}"')
 | 
			
		||||
        self.__write_line('msgstr ""')
 | 
			
		||||
 | 
			
		||||
    def build_code_reference(self, code_file_path: str, code_line_number: int) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        A convenient function to build code reference string used when adding entry.
 | 
			
		||||
        @param[in] code_file_path The path to associated code file.
 | 
			
		||||
        @param[in] code_line_number The line number of associated code within given file.
 | 
			
		||||
        """
 | 
			
		||||
        return f'{code_file_path}:{code_line_number}'
 | 
			
		||||
		Reference in New Issue
	
	Block a user