فهرست منبع

Add tools for translating apps.

Cixo Develop 4 ماه پیش
والد
کامیت
a3e86b5428
3فایلهای تغییر یافته به همراه543 افزوده شده و 5 حذف شده
  1. 4 4
      source/languages.js
  2. 1 1
      source/phrasebook.js
  3. 538 0
      tools/cx_libtranslate_searcher.py

+ 4 - 4
source/languages.js

@@ -113,8 +113,8 @@ class languages {
         }
 
         const response = await this.#load_index(index);
-        const result = new languages(this.#path, this.#local);
-        
+        this.#libs.clear();
+
         Object.keys(response).forEach(name => {
             if (typeof(name) !== "string") {
                 console.error("Name of the language must be string.");
@@ -130,10 +130,10 @@ class languages {
                 return;
             }
 
-            result.add(name, response[name]);
+            this.add(name, response[name]);
         });
 
-        return result;
+        return this;
     }
 
     /**

+ 1 - 1
source/phrasebook.js

@@ -73,7 +73,7 @@ class phrasebook {
      * @param {string} phrase - Phrase to translate. 
      * @returns {translation} - Translated phrase.
      */
-    get(phrase) {
+    tr(phrase) {
         return this.translate(phrase);
     }
 

+ 538 - 0
tools/cx_libtranslate_searcher.py

@@ -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)