|
@@ -0,0 +1,538 @@
|
|
|
|
|
+#!/usr/bin/env python3
|
|
|
|
|
+
|
|
|
|
|
+import pathlib
|
|
|
|
|
+import typing
|
|
|
|
|
+
|
|
|
|
|
+class file:
|
|
|
|
|
+ """ This class parse single file, and returns all phrases to translate.
|
|
|
|
|
+
|
|
|
|
|
+ This open file, and trying to find all phrases to translate in the file.
|
|
|
|
|
+ Process is separated to few steps, first run filter() to get all lines
|
|
|
|
|
+ when translate functions exists. Then run parse(), which would get
|
|
|
|
|
+ phrases and append it to list. On the end, could get results as list by
|
|
|
|
|
+ result() function, which return list with all phrases, or phrases()
|
|
|
|
|
+ generator, which iterate on all of them.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, target: pathlib.Path) -> None:
|
|
|
|
|
+ """ It creates new file file parser.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ target : pathlib.Path
|
|
|
|
|
+ It is path to the file in the filesystem to parse.
|
|
|
|
|
+
|
|
|
|
|
+ Raises
|
|
|
|
|
+ ------
|
|
|
|
|
+ TypeError
|
|
|
|
|
+ When target is not afile or not exists.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ if not target.is_file():
|
|
|
|
|
+ raise TypeError("Target \"" + str(target) + "\" not exists.")
|
|
|
|
|
+
|
|
|
|
|
+ self.__to_parse = None
|
|
|
|
|
+ self.__phrases = None
|
|
|
|
|
+
|
|
|
|
|
+ with target.open() as handle:
|
|
|
|
|
+ self.__content = handle.read().split("\n")
|
|
|
|
|
+
|
|
|
|
|
+ def __targets(self) -> list:
|
|
|
|
|
+ """ This return targets function which libtranslate use to translates.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ list
|
|
|
|
|
+ List with targets to search for in the code.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ return [ "_(", ".translate(", ".tr(", ".phrase ", ".phrase=" ]
|
|
|
|
|
+
|
|
|
|
|
+ def __string(self) -> list:
|
|
|
|
|
+ """ This returns javascript string openers and closers.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ list
|
|
|
|
|
+ List chars with open and close strings in js.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ return [ '"', "'", "`" ]
|
|
|
|
|
+
|
|
|
|
|
+ def __has_phrase(self, line: str) -> bool:
|
|
|
|
|
+ """ This check that given line of code has translation instructions.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ line : str
|
|
|
|
|
+ Line of code to search for instructions in.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ bool
|
|
|
|
|
+ True when line has translations or False when not.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ for target in self.__targets():
|
|
|
|
|
+ if line.find(target) != -1:
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ def filter(self) -> object:
|
|
|
|
|
+ """ This filter file contents for lines with translation content.
|
|
|
|
|
+
|
|
|
|
|
+ This search for translate instructions in all file content. When
|
|
|
|
|
+ line contain translation instructions, then it append it to new
|
|
|
|
|
+ list, which would be processed in the next step.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ file
|
|
|
|
|
+ Own instanct for chain loading.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ self.__to_parse = list()
|
|
|
|
|
+
|
|
|
|
|
+ for line in self.__content:
|
|
|
|
|
+ if self.__has_phrase(line):
|
|
|
|
|
+ self.__to_parse.append(line)
|
|
|
|
|
+
|
|
|
|
|
+ return self
|
|
|
|
|
+
|
|
|
|
|
+ def __get_next(self, line: str, start: int) -> int:
|
|
|
|
|
+ """ This return next speech of the translation instruction.
|
|
|
|
|
+
|
|
|
|
|
+ This function trying to find next speech of any from translation
|
|
|
|
|
+ instructions. When it found, then return position of the end of
|
|
|
|
|
+ that function, but when could not found anything, then return -1.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ line : str
|
|
|
|
|
+ Line to found instruction in.
|
|
|
|
|
+
|
|
|
|
|
+ start : int
|
|
|
|
|
+ This is position when start to searching for.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ int
|
|
|
|
|
+ Position of the end of found instruction or -1 when not found.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ result = -1
|
|
|
|
|
+
|
|
|
|
|
+ for target in self.__targets():
|
|
|
|
|
+ position = line.find(target, start)
|
|
|
|
|
+
|
|
|
|
|
+ if position == -1:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ position = position + len(target)
|
|
|
|
|
+
|
|
|
|
|
+ if result == -1:
|
|
|
|
|
+ result = position
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ if result > position:
|
|
|
|
|
+ result = position
|
|
|
|
|
+
|
|
|
|
|
+ return result
|
|
|
|
|
+
|
|
|
|
|
+ def __get_string_pos(self, line: str, start: int) -> int:
|
|
|
|
|
+ """ This return next position of the string in the line.
|
|
|
|
|
+
|
|
|
|
|
+ This trying to search string in the line, when found it, then return
|
|
|
|
|
+ it position, or return -1 when not found anything. It return position
|
|
|
|
|
+ of the start string char, in opposition to __get_next.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ line : str
|
|
|
|
|
+ Line to search for string in.
|
|
|
|
|
+
|
|
|
|
|
+ start : int
|
|
|
|
|
+ Position to start searching in.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ int
|
|
|
|
|
+ Position of the string, or -1.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ for target in self.__string():
|
|
|
|
|
+ position = line.find(target, start)
|
|
|
|
|
+
|
|
|
|
|
+ if position == -1:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ return position
|
|
|
|
|
+
|
|
|
|
|
+ return -1
|
|
|
|
|
+
|
|
|
|
|
+ def __cut_text(self, line: str, position: int) -> str|None:
|
|
|
|
|
+ """ This get string from given line next to given position.
|
|
|
|
|
+
|
|
|
|
|
+ This trying to find string next to given position. When found it,
|
|
|
|
|
+ then get it, and return its content. When not found anything, then
|
|
|
|
|
+ return None.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ line : str
|
|
|
|
|
+ Line to search for string in.
|
|
|
|
|
+
|
|
|
|
|
+ position : int
|
|
|
|
|
+ Position to search for string on.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ str
|
|
|
|
|
+ Content of the found string.
|
|
|
|
|
+
|
|
|
|
|
+ None
|
|
|
|
|
+ When not found any string.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ start = self.__get_string_pos(line, position)
|
|
|
|
|
+
|
|
|
|
|
+ if start == -1:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ char = line[start]
|
|
|
|
|
+ start = start + 1
|
|
|
|
|
+ end = line.find(char, start)
|
|
|
|
|
+
|
|
|
|
|
+ if end == -1:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ return line[start:end]
|
|
|
|
|
+
|
|
|
|
|
+ def __parse_line(self, line: str) -> typing.Iterator[str]:
|
|
|
|
|
+ """ This parse single line, and return generator with found phrases.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ line : str
|
|
|
|
|
+ Line to parse.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ Iterator[str]
|
|
|
|
|
+ All found phrases in the line.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ current = 0
|
|
|
|
|
+
|
|
|
|
|
+ while True:
|
|
|
|
|
+ current = self.__get_next(line, current)
|
|
|
|
|
+
|
|
|
|
|
+ if current == -1:
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ text = self.__cut_text(line, current)
|
|
|
|
|
+
|
|
|
|
|
+ if text is None:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ current = current + len(text)
|
|
|
|
|
+ yield text
|
|
|
|
|
+
|
|
|
|
|
+ def parse(self) -> object:
|
|
|
|
|
+ """ This parse all lines in the file.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ file
|
|
|
|
|
+ Own instance to chain loading.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ if self.__to_parse is None:
|
|
|
|
|
+ raise RuntimeError("Run filter() first.")
|
|
|
|
|
+
|
|
|
|
|
+ self.__phrases = list()
|
|
|
|
|
+
|
|
|
|
|
+ for line in self.__to_parse:
|
|
|
|
|
+ for count in self.__parse_line(line):
|
|
|
|
|
+ if not count in self.__phrases:
|
|
|
|
|
+ self.__phrases.append(count)
|
|
|
|
|
+
|
|
|
|
|
+ return self
|
|
|
|
|
+
|
|
|
|
|
+ def result(self) -> list:
|
|
|
|
|
+ """ This return all founded phrases as list.
|
|
|
|
|
+
|
|
|
|
|
+ Raises
|
|
|
|
|
+ ------
|
|
|
|
|
+ RuntimeError
|
|
|
|
|
+ When not run parse() before.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ list
|
|
|
|
|
+ List with all found phrases.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ if self.__phrases is None:
|
|
|
|
|
+ raise RuntimeError("Run parse() first.")
|
|
|
|
|
+
|
|
|
|
|
+ return self.__phrases
|
|
|
|
|
+
|
|
|
|
|
+ def phrases(self) ->typing.Iterator[str]:
|
|
|
|
|
+ """ This returns generator with all phrases.
|
|
|
|
|
+
|
|
|
|
|
+ Raises
|
|
|
|
|
+ ------
|
|
|
|
|
+ RuntimeError
|
|
|
|
|
+ When not run parse() before.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ Iterator[str]
|
|
|
|
|
+ Generator with all phrases.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ for phrase in self.result():
|
|
|
|
|
+ yield phrase
|
|
|
|
|
+
|
|
|
|
|
+class directory:
|
|
|
|
|
+ """ This open all Javascript files in the directory and search phrases.
|
|
|
|
|
+
|
|
|
|
|
+ This trying to open all Javascript files and all Javascript in the
|
|
|
|
|
+ subdirectories. Then parsing all that files, and adding phrases from
|
|
|
|
|
+ its to the list. On the end that phrases could be returned as list, or
|
|
|
|
|
+ loaded from Iterator.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, target: pathlib.Path) -> None:
|
|
|
|
|
+ """ This create new directory instance.
|
|
|
|
|
+
|
|
|
|
|
+ Raises
|
|
|
|
|
+ ------
|
|
|
|
|
+ TypeError
|
|
|
|
|
+ When target path is not directory.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ target : pathlib.Path
|
|
|
|
|
+ Directory to work in.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ if not target.is_dir():
|
|
|
|
|
+ raise TypeError("Target \"" + str(target) + "\" is not dir.")
|
|
|
|
|
+
|
|
|
|
|
+ self.__target = target
|
|
|
|
|
+ self.__phrases = list()
|
|
|
|
|
+
|
|
|
|
|
+ def __append(self, phrases: list) -> None:
|
|
|
|
|
+ """ This append new phrase to phrases list.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ phrases : list
|
|
|
|
|
+ List of phrases to add.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ for phrase in phrases:
|
|
|
|
|
+ if phrase in self.__phrases:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ self.__phrases.append(phrase)
|
|
|
|
|
+
|
|
|
|
|
+ def process(self) -> object:
|
|
|
|
|
+ """ This process given directory.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ directory
|
|
|
|
|
+ Own instance to chain loading.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ self.__process_directory(self.__target)
|
|
|
|
|
+ return self
|
|
|
|
|
+
|
|
|
|
|
+ def result(self) -> list:
|
|
|
|
|
+ """ This return list with result.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ list
|
|
|
|
|
+ List with phrases from the files.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ return self.__phrases
|
|
|
|
|
+
|
|
|
|
|
+ def phrases(self) -> typing.Iterator[str]:
|
|
|
|
|
+ """ This return all phrases as iterator.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ Iterator[str]
|
|
|
|
|
+ All phrases from files.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ for phrase in self.__phrases:
|
|
|
|
|
+ yield phrase
|
|
|
|
|
+
|
|
|
|
|
+ def __js_extensions(self) -> list:
|
|
|
|
|
+ """ This return all extensions for js files.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ list
|
|
|
|
|
+ All js files extensions.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ return [ "js", "mjs", "ts" ]
|
|
|
|
|
+
|
|
|
|
|
+ def __process_directory(self, directory: pathlib.Path) -> None:
|
|
|
|
|
+ """ This process given directory.
|
|
|
|
|
+
|
|
|
|
|
+ This process given directory, when in directory exists any diretory,
|
|
|
|
|
+ then it would be processed by that function recursive. When found
|
|
|
|
|
+ file, then parse it, and add phrases from it to the list.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ directory : pathlib.Path
|
|
|
|
|
+ Directory to work on
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ for count in directory.iterdir():
|
|
|
|
|
+ if count.is_dir():
|
|
|
|
|
+ self.__process_directory(count)
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ if count.is_file():
|
|
|
|
|
+ self.__process_file(count)
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ def __process_file(self, target: pathlib.Path) -> None:
|
|
|
|
|
+ """ This process single file.
|
|
|
|
|
+
|
|
|
|
|
+ This process single file. When file is not Javasocript file, then ti
|
|
|
|
|
+ skip it, but when file is Javascript source code, then it trying to
|
|
|
|
|
+ extract all phrases from it and adds it to phrases list.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ target : pathlib.Path
|
|
|
|
|
+ Target file to process.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ suffix = target.suffix[1:]
|
|
|
|
|
+
|
|
|
|
|
+ if not suffix in self.__js_extensions():
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ self.__append(file(target).filter().parse().result())
|
|
|
|
|
+
|
|
|
|
|
+class dictionary:
|
|
|
|
|
+ """ This create new sample dictionary with phrases.
|
|
|
|
|
+
|
|
|
|
|
+ This class create sample dictionary file from phrases list.
|
|
|
|
|
+ It could return result as string, or save it to file.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, phrases: list) -> None:
|
|
|
|
|
+ """ This create new dictionary instance.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ phrases : list
|
|
|
|
|
+ List of phrases to prepare dictionary from.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ self.__phrases = phrases
|
|
|
|
|
+
|
|
|
|
|
+ def __process_single_phrase(self, phrase: str) -> str:
|
|
|
|
|
+ """ This process single phrase to line in the dictionary.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ phrase : str
|
|
|
|
|
+ Phrase to make line from.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ str
|
|
|
|
|
+ Phrase as line in dictionary.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ return (" " * 4) + "\"" + phrase + "\": \"\""
|
|
|
|
|
+
|
|
|
|
|
+ @property
|
|
|
|
|
+ def result(self) -> str:
|
|
|
|
|
+ """ It process all phrases to the dictionary file.
|
|
|
|
|
+
|
|
|
|
|
+ Returns
|
|
|
|
|
+ -------
|
|
|
|
|
+ str
|
|
|
|
|
+ Parsed dictionary.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ lines = []
|
|
|
|
|
+ start = "{\n"
|
|
|
|
|
+ stop = "\n}\n"
|
|
|
|
|
+
|
|
|
|
|
+ for phrase in self.__phrases:
|
|
|
|
|
+ lines.append(self.__process_single_phrase(phrase))
|
|
|
|
|
+
|
|
|
|
|
+ return start + str(",\n").join(lines) + stop
|
|
|
|
|
+
|
|
|
|
|
+ def write(self, target: pathlib.Path) -> None:
|
|
|
|
|
+ """ It write dictionary to the file.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters
|
|
|
|
|
+ ----------
|
|
|
|
|
+ target : pathlib.Path
|
|
|
|
|
+ Target file to write dictionary into.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ with target.open("w+") as handle:
|
|
|
|
|
+ handle.write(self.result)
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ import argparse
|
|
|
|
|
+
|
|
|
|
|
+ parser = argparse.ArgumentParser(
|
|
|
|
|
+ description = "This script helps to create phrasebook from scripts."
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ parser.add_argument(
|
|
|
|
|
+ "source",
|
|
|
|
|
+ type = pathlib.Path,
|
|
|
|
|
+ help = "This is source file or directory to parse from."
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ parser.add_argument(
|
|
|
|
|
+ "--output",
|
|
|
|
|
+ type = pathlib.Path,
|
|
|
|
|
+ default = "output.json",
|
|
|
|
|
+ help = "This is output phrasebook file, to save into."
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ arguments = parser.parse_args()
|
|
|
|
|
+
|
|
|
|
|
+ if not arguments.source.exists():
|
|
|
|
|
+ print("Source \"" + str(arguments.source) + "\" not exists.")
|
|
|
|
|
+ exit(127)
|
|
|
|
|
+
|
|
|
|
|
+ if arguments.source.is_file():
|
|
|
|
|
+ target = file(arguments.source).filter().parse().result()
|
|
|
|
|
+ elif arguments.source.is_dir():
|
|
|
|
|
+ target = directory(arguments.source).process().result()
|
|
|
|
|
+ else:
|
|
|
|
|
+ print("Source is not file or directory.")
|
|
|
|
|
+ exit(126)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ dictionary(target).write(arguments.output)
|
|
|
|
|
+ print("Processed successfull.")
|
|
|
|
|
+ print("Processed: " + str(len(target)) + " phrases.")
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as error:
|
|
|
|
|
+ print(str(error))
|
|
|
|
|
+ print("Can not save \"" + str(arguments.output) + "\" output file.")
|
|
|
|
|
+ exit(125)
|