Has anyone thought about making an automatic translation of the help file? A script extracts texts from a file line by line, and uses markup keywords (@ExampleFile, @Function, @Description) to determine whether the current line needs translation. For example, for the @Function section, it is not needed, but for the @ExampleFile section, you can translate comments. The program sends the string to Google and receives the translated text from it. This is written to a new file. Then compile the help file. Even if the translation is of poor quality, you can always fix it later, but at least you can get help in your native language.
I've already done this to translate the AutoIt3 help. Here is an example, but it uses the window automation of another program. It would be possible to send strings using the API.
Automatic translation of the help file (?)
Re: Automatic translation of the help file (?)
I haven't tried something like this, but I imagine that AI might be well suited for this type of task, without any manual scripting needed, if you formulate a good prompt (and assuming no copyright issues).
Re: Automatic translation of the help file (?)
The third version
Download
HelpTranslator.pb
ForHelpTranslator.pb
Download
HelpTranslator.pb
Code: Select all
;- TOP
; AZJIO 2026.02.02
EnableExplicit
; DebugLevel 1 ; разрешить сообщения отладчика, а чтобы можно было отключить и не мешать GUI-обработчику.
;- ● Enumeration
Enumeration
#RE_Overview
#RE_Description
#RE_Parameter
#RE_OptionalParameter
#RE_Remarks
#RE_Code
#RE_Translate
EndEnumeration
#Source = 0
#FileCache = 1
Structure TranslateData
Original.s
Translate.s
Length.i
EndStructure
XIncludeFile "ForHelpTranslator.pb"
Global NewList TranslateList.TranslateData()
;- ● Define
Define NewList Files.s()
Define Dim aTrslText.s(0)
Define Path$
Define StartTime
Define Text$
Define Preview$
Define CurPath$, CurFileName$
Define PathDone$, PathCache$, CacheFileFound, FileDone$, FileCache$
Define Length, bytes, *Buffer
Define CacheText$
Define i
Define IsTranslateGUI = 0
Define log$
; Может пусть Define будут, а в функции сделать Shared
Define hwnd, hwndBtn, hwndRTF1, hwndRTF2
;- ● ini
Define ini$
Define SourceLang$ = "en"
Define TranslateLang$ = "ru"
Define Only$
ini$ = GetPathPart(ProgramFilename()) + "HelpTranslator.ini"
If OpenPreferences(ini$)
Path$ = ReadPreferenceString("Path", "")
PathCache$ = ReadPreferenceString("PathCache", GetPathPart(Path$) + "Cache" + #PS$)
PathDone$ = ReadPreferenceString("PathDone", GetPathPart(Path$) + "Done" + #PS$)
SourceLang$ = ReadPreferenceString("SourceLang", SourceLang$)
TranslateLang$ = ReadPreferenceString("TranslateLang", TranslateLang$)
Only$ = ReadPreferenceString("Only", "")
IsTranslateGUI = ReadPreferenceInteger("IsTranslateGUI", 0)
ClosePreferences()
EndIf
; Если путь не существует, то делать нечего, завершаем работу программы
If FileSize(Path$) <> -2
Path$ = PathRequester("Open", GetCurrentDirectory())
If FileSize(Path$) <> -2
End
EndIf
EndIf
If FileSize(PathCache$) <> -2
PathCache$ = GetPathPart(Path$ + "Cache" + #PS$)
EndIf
If FileSize(PathDone$) <> -2
PathDone$ = GetPathPart(Path$ + "Done" + #PS$)
EndIf
If Right(Path$, 1) <> #PS$
Path$ + #PS$
EndIf
If Right(PathCache$, 1) <> #PS$
PathCache$ + #PS$
EndIf
If Right(PathDone$, 1) <> #PS$
PathDone$ + #PS$
EndIf
If Len(TranslateLang$) <> 2
TranslateLang$ = Left(GetOSLanguage(), 2)
If MessageRequester("Use?", "Do you want to use the translation language '" + TranslateLang$ + "'", #PB_MessageRequester_YesNo) = #PB_MessageRequester_No
End
EndIf
If Len(TranslateLang$) <> 2
MessageRequester("Program shutdown", "The translation language is not specified, for example ru")
End
EndIf
EndIf
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
IsTranslateGUI = 0 ; где бы флаг не был назначен для линукса сброс в 0.
; надо функции TranslateGUI() заключить в запрет для линукса
CompilerEndIf
;- ● Declare
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
Declare.s TranslateGUI(Text$)
If IsTranslateGUI
hwnd = WinGetHandle(0, "QTranslate", 1, #Title, 1) ;
Debug "hwnd: " + Hex(hwnd), 1
If hwnd
hwndBtn = WinGetHandle(hwnd, "Button", 9, #Class, 0) ; Кнопка "Перевести"
Debug "hwndBtn: " + Hex(hwndBtn), 1
hwndRTF1 = WinGetHandle(hwnd, "RICHEDIT50W", 1, #Class, 0) ; окно исходного текста
Debug "hwndRTF1: " + Hex(hwndRTF1), 1
hwndRTF2 = WinGetHandle(hwnd, "RICHEDIT50W", 2, #Class, 0) ; окно переведённого текста
Debug "hwndRTF2: " + Hex(hwndRTF2), 1
EndIf
If hwnd = 0 Or hwndBtn = 0 Or hwndRTF1 = 0 Or hwndRTF2 = 0
MessageRequester("Program shutdown", "Window elements were not found:" + #LF$ + #LF$ + Hex(hwnd) + " " + Hex(hwndBtn) + " " + Hex(hwndRTF1) + " " + Hex(hwndRTF2))
End
EndIf
SendMessage_(hwndRTF2, #WM_SETTEXT, 0, @"") ; очистить окно
SetForegroundWindow(hwnd)
EndIf
CompilerEndIf
StartTime = ElapsedMilliseconds()
;- ● RegularExpression
CreateRegularExpression(#RE_Overview, "^@Overview\s++\K(.+?)(?=^@(CommandList|ExampleFile|SupportedOS))", #PB_RegularExpression_DotAll | #PB_RegularExpression_MultiLine)
CreateRegularExpression(#RE_Description, "^@Description\s++\K(.+?)(?=^@(Parameter|OptionalParameter|NoReturnValue|ReturnValue|Remarks|Example|SeeAlso|SupportedOS))", #PB_RegularExpression_DotAll | #PB_RegularExpression_MultiLine)
CreateRegularExpression(#RE_Parameter, "^@Parameter[^\n]*?\n\s+\K(.+?)(?=^@(Parameter|OptionalParameter|NoReturnValue|ReturnValue|Remarks|Example|SeeAlso|SupportedOS))", #PB_RegularExpression_DotAll | #PB_RegularExpression_MultiLine)
CreateRegularExpression(#RE_OptionalParameter, "^@OptionalParameter[^\n]*?\n\s+\K(.+?)(?=^@(OptionalParameter|NoReturnValue|ReturnValue|Remarks|Example|SeeAlso|SupportedOS))", #PB_RegularExpression_DotAll | #PB_RegularExpression_MultiLine)
CreateRegularExpression(#RE_Remarks, "^@Remarks\s++\K(.+?)(?=^@(Example|SeeAlso|SupportedOS))", #PB_RegularExpression_DotAll | #PB_RegularExpression_MultiLine)
CreateRegularExpression(#RE_Code, "^@Code\s++\K(.+?)(?=^@(EndCode|SeeAlso|SupportedOS))", #PB_RegularExpression_DotAll | #PB_RegularExpression_MultiLine)
CreateRegularExpression(#RE_Translate, "(.+?)(?:\n\}—————●—————\{\n)(.+?)(?:\n\}==================================●==================================\{\n)", #PB_RegularExpression_DotAll)
If Not (ForceDirectories(Path$ + "cache") And ForceDirectories(Path$ + "done"))
MessageRequester("Program shutdown", "The cache/done folder could not be created")
End
EndIf
FileSearch(Files(), Path$, "txt", 0)
;-┌─For─┐
ForEach Files()
CacheFileFound = 1
; временно переводим только HID.txt
If Asc(Only$) And Files() <> Path$ + Only$
Continue
EndIf
; CurPath$ = GetPathPart(Files())
; CurFileName$ = GetFilePart(Files(), #PB_FileSystem_NoExtension)
CurFileName$ = GetFilePart(Files())
FileCache$ = PathCache$ + CurFileName$
FileDone$ = PathDone$ + CurFileName$
If FileSize(FileCache$) = -1
CacheFileFound = 0
If CreateFile(#FileCache, FileCache$)
CacheFileFound = -1
CloseFile(#FileCache)
EndIf
EndIf
If ReadFile(#Source, Files()) And CacheFileFound ; #PB_UTF8
; While Eof(#Source) = 0
; ; читаем строку
; ReadString(#Source)
; Wend
Length = Lof(#Source)
*Buffer = AllocateMemory(Length + 2)
If *Buffer
bytes = ReadData(#Source, *Buffer, Length)
If bytes
Text$ = PeekS(*Buffer, bytes, #PB_UTF8)
EndIf
FreeMemory(*Buffer)
EndIf
CloseFile(#Source)
ClearList(TranslateList())
If CacheFileFound = 1 ; если существует кеш, то вместо того чтобы переводить файл выполняем взятие из кеша
Debug "Cache", 1
If ReadFile(#FileCache, FileCache$)
Length = Lof(#FileCache)
*Buffer = AllocateMemory(Length + 2)
If *Buffer
bytes = ReadData(#FileCache, *Buffer, Length)
If bytes
CacheText$ = PeekS(*Buffer, bytes, #PB_UTF8)
EndIf
FreeMemory(*Buffer)
EndIf
CloseFile(#FileCache)
EndIf
If Asc(CacheText$)
; MessageRequester("Error", CacheText$)
If ExamineRegularExpression(#RE_Translate, CacheText$)
While NextRegularExpressionMatch(#RE_Translate)
If AddElement(TranslateList())
TranslateList()\Original = RegularExpressionGroup(#RE_Translate, 1)
TranslateList()\Translate = RegularExpressionGroup(#RE_Translate, 2)
EndIf
Wend
EndIf
EndIf
Else
Debug "Translate", 1
;- ● Examine
If ExamineRegularExpression(#RE_Overview, Text$)
While NextRegularExpressionMatch(#RE_Overview)
If AddElement(TranslateList())
TranslateList()\Original = RegularExpressionMatchString(#RE_Overview)
EndIf
Wend
EndIf
If ExamineRegularExpression(#RE_Description, Text$)
While NextRegularExpressionMatch(#RE_Description)
If AddElement(TranslateList())
TranslateList()\Original = RegularExpressionMatchString(#RE_Description)
EndIf
Wend
EndIf
If ExamineRegularExpression(#RE_Parameter, Text$)
While NextRegularExpressionMatch(#RE_Parameter)
If AddElement(TranslateList())
TranslateList()\Original = RegularExpressionMatchString(#RE_Parameter)
EndIf
Wend
EndIf
If ExamineRegularExpression(#RE_OptionalParameter, Text$)
While NextRegularExpressionMatch(#RE_OptionalParameter)
If AddElement(TranslateList())
TranslateList()\Original = RegularExpressionMatchString(#RE_OptionalParameter)
EndIf
Wend
EndIf
If ExamineRegularExpression(#RE_Remarks, Text$)
While NextRegularExpressionMatch(#RE_Remarks)
If AddElement(TranslateList())
TranslateList()\Original = RegularExpressionMatchString(#RE_Remarks)
EndIf
Wend
EndIf
If ExamineRegularExpression(#RE_Code, Text$)
While NextRegularExpressionMatch(#RE_Code)
; разобрать код на комментарии
GetCommentLines(RegularExpressionMatchString(#RE_Code), aTrslText())
If Not (ArraySize(aTrslText()) = 0 And Asc(aTrslText(0)) = 0) ; если код с комментариями, то
For i = 0 To ArraySize(aTrslText())
If aTrslText(i) <> ";"
If AddElement(TranslateList())
TranslateList()\Original = aTrslText(i)
EndIf
EndIf
Next
EndIf
Wend
EndIf
EndIf
; Выполняем перевод строк
Preview$ = ""
ForEach TranslateList()
TranslateList()\Original = RTrimChar(TranslateList()\Original, #CRLF$ + #TAB$ + " ")
TranslateList()\Original = LTrimChar(TranslateList()\Original, #CRLF$ + #TAB$ + " ")
TranslateList()\Length = Len(TranslateList()\Original)
; TranslateList()\Translate = TranslateList()\Original ; для теста чтобы не дёргать переводчик, проверям все ли строки захватывает
If CacheFileFound < 1 ; выполняем перевод если не было файла или создан пустой
If IsTranslateGUI
TranslateList()\Translate = TranslateGUI(TranslateList()\Original)
Else
TranslateList()\Translate = TranslateText(TranslateList()\Original, SourceLang$, TranslateLang$)
EndIf
EndIf
TranslateList()\Translate = RTrimChar(TranslateList()\Translate, #CRLF$ + #TAB$ + " ")
TranslateList()\Translate = LTrimChar(TranslateList()\Translate, #CRLF$ + #TAB$ + " ")
If Asc(TranslateList()\Translate) = 0
; Debug "Пустая строка", 1
Debug "Empty line: " + TranslateList()\Original, 1
log$ + "Empty line: " + TranslateList()\Original + #LF$
DeleteElement(TranslateList())
Continue ; если пустая строка, то замену не производить
EndIf
Preview$ + TranslateList()\Original + #LF$ + "}—————●—————{" + #LF$ + TranslateList()\Translate + #LF$ + "}==================================●=================================={" + #LF$
Next
Debug "ListSize: " + Str(ListSize(TranslateList())), 1
Unique(TranslateList())
Debug "ListSize: " + Str(ListSize(TranslateList())), 1
;- ● Sort
; Сортируем, чтобы длинные строки были в начале и не были частью других длинных строк
SortStructuredList(TranslateList(), #PB_Sort_Descending, OffsetOf(TranslateData\Length), TypeOf(TranslateData\Length))
ForEach TranslateList()
Text$ = ReplaceString(Text$, TranslateList()\Original, TranslateList()\Translate)
; Debug "Original|" + TranslateList()\Original + "|", 2
; Debug "Translate|" + TranslateList()\Translate + "|", 2
Next
; Если кеша не существовало, то записываем в файл
If CacheFileFound < 1 And OpenFile(#FileCache, FileCache$)
If Not WriteString(#FileCache, Preview$)
MessageRequester("Error", "The file could not be written to the folder 'cache': " + CurFileName$)
EndIf
CloseFile(#FileCache)
EndIf
If OpenFile(#Source, FileDone$)
; MessageRequester("Error", Text$)
If Not WriteString(#Source, Text$)
MessageRequester("Error", "The file could not be written to the 'done' folder: " + CurFileName$)
EndIf
CloseFile(#Source)
EndIf
SetClipboardText(Preview$)
EndIf
Next
Debug "Time = " + FormatNumber((ElapsedMilliseconds() - StartTime)/1000, 2, ".", "") + " s", 1
If Asc(log$)
MessageRequester("", log$)
EndIf
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
Procedure.s TranslateGUI(Text$)
Shared hwnd, hwndBtn, hwndRTF1, hwndRTF2
Shared log$
Protected length
Protected trnslt$
Protected count, *m
SendMessage_(hwndRTF1, #WM_SETTEXT, 0, @Text$) ; вставить текст для перевода
Delay(100)
SendMessage_(hwndBtn, #BM_CLICK, 0, 0) ; нажимает кнопку "Перевести"
Delay(200)
Repeat
length = SendMessage_(hwndRTF2, #WM_GETTEXTLENGTH, 0, 0)
If count > 100
Break
EndIf
If length = 0
count + 1
Delay(200)
Continue
EndIf
; trnslt$ = Space(length + 2)
*m = AllocateMemory(length*2 + 2)
If *m
SendMessage_(hwndRTF2, #WM_GETTEXT, length, *m) ; Считываем текст с окна перевода
trnslt$ = PeekS(*m)
If trnslt$ = "The language cannot be determined. Choose the language yourself." ; а вот тут эта фраза может быть на другом языке
log$ + "The language cannot be determined. Choose the language yourself." + Text$ + #LF$
trnslt$ = ""
EndIf
SendMessage_(hwndRTF2, #WM_SETTEXT, 0, @"") ; очистить окно
FreeMemory(*m)
Break
EndIf
ForEver
ProcedureReturn trnslt$
EndProcedure
CompilerEndIfForHelpTranslator.pb
Code: Select all
FileSearch
Unique
LTrimChar
RTrimChar
TranslateText
ForceDirectories
SplitA2
GetCommentLines
enumChildren
WinGetHandle
SetForegroundWindow
GetOSLanguage
Re: Automatic translation of the help file (?)
I've updated it several times, so you can try the new version. Since the code no longer fits in the message, download the archive in the first message.
1. I also exported the code comments to translate them.
2. I made the translation using the external QTranslate window, as the built-in translation API limits the size of the translated text.
3. The settings are located in the ini file.
1. I also exported the code comments to translate them.
2. I made the translation using the external QTranslate window, as the built-in translation API limits the size of the translated text.
3. The settings are located in the ini file.

