Automatic translation of the help file (?)

Just starting out? Need help? Post your questions and find answers here.
AZJIO
Addict
Addict
Posts: 2278
Joined: Sun May 14, 2017 1:48 am

Automatic translation of the help file (?)

Post by AZJIO »

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.
User avatar
Nudgy
User
User
Posts: 32
Joined: Mon May 27, 2024 8:11 pm

Re: Automatic translation of the help file (?)

Post by Nudgy »

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).
AZJIO
Addict
Addict
Posts: 2278
Joined: Sun May 14, 2017 1:48 am

Re: Automatic translation of the help file (?)

Post by AZJIO »

The third version
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

CompilerEndIf

ForHelpTranslator.pb

Code: Select all

FileSearch
Unique
LTrimChar
RTrimChar
TranslateText
ForceDirectories
SplitA2
GetCommentLines
enumChildren
WinGetHandle
SetForegroundWindow
GetOSLanguage
AZJIO
Addict
Addict
Posts: 2278
Joined: Sun May 14, 2017 1:48 am

Re: Automatic translation of the help file (?)

Post by AZJIO »

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.
Post Reply