maraskanuser
New Member
- Mar 23, 2020
- 2
- 0
- 14
I thought I'd share an ahk script I wrote. It can be used for several things:
- it can merge several translation files into one, taking translations from lower priority files if they don't exist in the preferred file.
- it resorts translation files' keys to be in the same order as in the untranslated key dump. The resulting files can more easily be compared in tools like WinMerge etc. They get only written when bCreateDebug=1.
- it drops keys that aren't part of the clean dump from the translation files (removes translation lines for text that isn't in the game anymore. (just to be sure, a clean dump is what you get when you have an empty RJ162718.json be recreated be setting config.json to update. It contains keys for all lines of japanese text in the game; at least if you haven't installed any patched scene files, that is. The scene files of the patch for some reason are partially translated)
Code:
#SingleInstance,Force
#NoEnv
SetBatchLines,-1
;###############################################################################################################################
; Path to dump of untranslated lines (by setting update -> file in config.json)
sKeyDumpRawPath := "D:\D\5\AHKVNBrowser\RE\r55217\Setup\Dictionaries\Dump_2.5.2.json"
; Omit keys in dump that only contain ascii (no reason to translate ascii). Boolean. 1 = 'true', 0 = 'false'
; Keys in the dump's "graphic" section are never omitted, even though they're all ascii.
bOmitAsciiKeys := 1
; json files containing translations for various keys in order of prefered usage (sDictionary1 has highest priority)
; if a key has no translation in the dictionary 1, it will check dictionary 2, then 3.
sDictionary1Path := "D:\D\5\AHKVNBrowser\RE\r55217\Setup\Dictionaries\Dictionary_MINE.json"
sDictionary2Path := "D:\D\5\AHKVNBrowser\RE\r55217\Setup\Dictionaries\Dictionary_2.5.2_Variant B.json"
sDictionary3Path := "D:\D\5\AHKVNBrowser\RE\r55217\Setup\Dictionaries\Dictionary_2.5.2_Variant A.json"
; File path to write the new merged dictionary to. Should usually point to the game's ".\data\others\translate\assets\RJ162718.json" file
; This is ordered just like the original dump, so the line order of the game's ks files is conserved
; (important to understand the meaning of multi-line sentences while translating)
sOutputFile := "D:\D\5\AHKVNBrowser\RE\r55217\Setup\[patch] TransFix_v5.2_r5.3.1m\Translate Strings\data\others\translate\assets\RJ162718.json"
; Create a couple of other json files in the script's folder for debug and translation comparison purposes. Boolean.
; Useful to compare different translations with tools like WinMerge, since the output is all in the exact same order (the order of the original dump)
bCreateDebug := 1
;###############################################################################################################################
Dictionary1_obj := ReadDictionary(sDictionary1Path)
Dictionary2_obj := ReadDictionary(sDictionary2Path)
Dictionary3_obj := ReadDictionary(sDictionary3Path)
sOutputString := ProcessKeyDump(sKeyDumpRawPath,Dictionary1_obj,Dictionary2_obj,Dictionary3_obj,bOmitAsciiKeys)
FileDelete,%sOutputFile%
FileAppend,%sOutputString%,*%sOutputFile%,UTF-8-RAW
if (bCreateDebug=1)
{
if (sDictionary1Path != "") && (IsObject(Dictionary1_obj))
{
sOutputString := ProcessKeyDump(sKeyDumpRawPath,Dictionary1_obj,"","",bOmitAsciiKeys)
SplitPath,sDictionary1Path,,,FExt,FNameNoExt
FileDelete,%A_ScriptDir%\[Debug] %FNameNoExt%.%FExt%
FileAppend,%sOutputString%,*%A_ScriptDir%\[Debug] %FNameNoExt%.%FExt%,UTF-8-RAW
}
if (sDictionary2Path != "") && (IsObject(Dictionary2_obj))
{
sOutputString := ProcessKeyDump(sKeyDumpRawPath,Dictionary2_obj,"","",bOmitAsciiKeys)
SplitPath,sDictionary2Path,,,FExt,FNameNoExt
FileDelete,%A_ScriptDir%\[Debug] %FNameNoExt%.%FExt%
FileAppend,%sOutputString%,*%A_ScriptDir%\[Debug] %FNameNoExt%.%FExt%,UTF-8-RAW
}
if (sDictionary3Path != "") && (IsObject(Dictionary3_obj))
{
sOutputString := ProcessKeyDump(sKeyDumpRawPath,Dictionary3_obj,"","",bOmitAsciiKeys)
SplitPath,sDictionary3Path,,,FExt,FNameNoExt
FileDelete,%A_ScriptDir%\[Debug] %FNameNoExt%.%FExt%
FileAppend,%sOutputString%,*%A_ScriptDir%\[Debug] %FNameNoExt%.%FExt%,UTF-8-RAW
}
}
ExitApp
;##################################################################################
;##################################################################################
;##################################################################################
ReadDictionary(sFilePath)
{
FileRead,sJSON1_old,*P65001 %sFilePath%
iLevel:=0
sSection:=""
iCount:=0
obj_return := {}
loop,parse,sJSON1_old,`n,`r
{
sText:=Trim(A_LoopField)
if (sText="{")
{
iLevel++
iFoundKey:=0
iFoundLang:=0
continue
}
else if (sText="},")||(sText="}")
{
if (iLevel=3)
{
if !IsObject(obj_return[sSection])
{
clipboard:=A_Index
msgbox,,Error A,Section %sSection% not yet part of return object.
}
PairObj:={}
obj_return[sSection][sEntryJP] := sEntryEN
}
iLevel--
continue
}
if (iLevel=1)
{
if RegExMatch(sText,"^""([a-z]*)"": \[$",sKey)
{
iLevel++
if (sKey1!="text")&&(sKey1!="chara")&&(sKey1!="hint")&&(sKey1!="graphic")&&(sKey1!="eval")&&(sKey1!="branch")
{
clipboard:=A_Index
msgbox,,Error B,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
sSection:=sKey1
if !IsObject(obj_return[sSection])
obj_return[sSection] := {}
continue
}
else
{
clipboard:=A_Index
msgbox,,Error C,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
}
else if (iLevel=2)
{
if (sText="],")||(sText="]")
{
iLevel--
continue
}
else if (sText="")
{
}
else
{
clipboard:=A_Index
msgbox,,Error D,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
}
else if (iLevel=3)
{
pos1:=instr(sText,"""key"":",0,1)
pos2:=instr(sText,"""en-US"":",0,1)
if (pos1=1)
{
if (iFoundKey=1)
{
clipboard:=A_Index
msgbox,,Error E,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
else
{
iFoundKey=1
sVal:=substr(sText,7)
sVal:=Trim(sVal," ")
sFirst:=substr(sVal,1,1)
sLast:=substr(sVal,strlen(sVal),1)
sPreLast:=substr(sVal,strlen(sVal)-1,1)
if (iFoundLang=1) ; language entry "en-US" was first -> this "key" entry is second -> no comma
{
if (sFirst!="""")||(sLast!="""")
{
clipboard:=A_Index
msgbox,,Error F,Bad formating in %sText%
}
else
{
sEntryJP:=substr(sVal,2,strlen(sVal)-2)
}
}
else ; language entry "en-US" still missing -> this "key" goes first -> has comma
{
if (sFirst!="""")||(sPreLast!="""")||(sLast!=",")
{
clipboard:=A_Index
msgbox,,Error G,Bad formating in %sText%
}
else
{
sEntryJP:=substr(sVal,2,strlen(sVal)-3)
}
}
}
}
else if (pos2=1)
{
if (iFoundLang=1)
{
clipboard:=A_Index
msgbox,,Error H,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
else
{
iFoundLang=1
sVal:=substr(sText,9)
sVal:=Trim(sVal," ")
sFirst:=substr(sVal,1,1)
sLast:=substr(sVal,strlen(sVal),1)
sPreLast:=substr(sVal,strlen(sVal)-1,1)
if (iFoundKey=0) ; "key" entry still missing -> this language entry goes first -> has comma
{
if (sVal="null,")
sEntryEN:="null"
else
{
if (sFirst!="""")||(sPreLast!="""")||(sLast!=",")
{
clipboard:=A_Index
msgbox,,Error I,Bad formating in %sText%
}
else
{
sEntryEN:=substr(sVal,2,strlen(sVal)-3)
}
}
}
else ; "key" entry already exists -> this language entry is second -> no comma
{
if (sVal="null")
sEntryEN:=sVal
else
{
if (sFirst!="""")||(sLast!="""")
{
clipboard:=A_Index
msgbox,,Error J,Bad formating in %sText%
}
else
{
sEntryEN:=substr(sVal,2,strlen(sVal)-2)
}
}
}
}
}
else
{
clipboard:=A_Index
msgbox,,Error K,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
}
else
{
clipboard:=A_Index
msgbox,,Error L,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
}
return obj_return
}
ProcessKeyDump(sFilePath,dictobj1,dictobj2,dictobj3,bOmitAsciiKeys)
{
if !FileExist(sFilePath)
{
msgbox,,Error,ProcessKeyDump() can't find file:`n%sFilePath%
ExitApp
}
FileRead,sJSON1_old,*P65001 %sFilePath%
iLevel:=0
sSection:=""
iCount:=0
obj_return := {}
sReturn := ""
loop,parse,sJSON1_old,`n,`r
{
sText:=Trim(A_LoopField)
if (sText="{")
{
iLevel++
iFoundKey:=0
iFoundLang:=0
if (iLevel!=3)
sReturn .= A_LoopField "`n"
continue
}
else if (sText="},")||(sText="}")
{
if (iLevel=3)
{
if !IsObject(obj_return[sSection])
{
clipboard:=A_Index
msgbox,,Error A,Section %sSection% not yet part of return object.
}
obj_return[sSection][sEntryJP] := sEntryEN
if (sSection="graphic")
{
; Since this sections contains only ascii image pathes for buttons and ascii translated Tooltip texts, we need to get it this way
bNonAscii=1
}
else
{
bNonAscii=0
if (bOmitAsciiKeys=1)
{
loop,parse,sEntryJP
{
k_unicode:=asc(A_LoopField)
if (k_unicode>256) ;(k_unicode>=0x2000)
{
bNonAscii=1
break
}
}
}
}
if (bNonAscii=1)||(bOmitAsciiKeys=0)
{
sReturn .= " {`n ""key"": """ sEntryJP """,`n"
;-----------------------------------------------------
; This is where the translations are actually applied
if IsObject(dictobj1)
{
sEntryEN1 := dictobj1[sSection][sEntryJP]
if (sEntryEN1!="") && (sEntryEN1!="null")
sEntryEN := sEntryEN1
}
if IsObject(dictobj2)
{
sEntryEN2 := dictobj2[sSection][sEntryJP]
if ((sEntryEN="") || (sEntryEN="null")) && (sEntryEN2!="") && (sEntryEN2!="null")
sEntryEN := sEntryEN2
}
if IsObject(dictobj3)
{
sEntryEN3 := dictobj3[sSection][sEntryJP]
if ((sEntryEN="") || (sEntryEN="null")) && (sEntryEN3!="")&&(sEntryEN3!="null")
sEntryEN := sEntryEN3
}
;-----------------------------------------------------
; We could do some formating with string replace at this point if necessary.
sEntryEN := StrReplace(sEntryEN, " [p]", "[p]")
sEntryEN := StrReplace(sEntryEN, " [r]", "[r]")
sEntryEN := StrReplace(sEntryEN, " [l]", "[l]")
sEntryEN := StrReplace(sEntryEN, " [lr]", "[lr]")
sEntryEN := StrReplace(sEntryEN," [lr_]","[lr_]")
;-----------------------------------------------------
if (sEntryEN="null")
sReturn .= " ""en-US"": null`n " sText "`n"
else
sReturn .= " ""en-US"": """ sEntryEN """`n " sText "`n"
}
}
else
sReturn .= A_LoopField "`n"
iLevel--
continue
}
if (iLevel=1)
{
if RegExMatch(sText,"^""([a-z]*)"": \[$",sKey)
{
iLevel++
if (sKey1!="text")&&(sKey1!="chara")&&(sKey1!="hint")&&(sKey1!="graphic")&&(sKey1!="eval")&&(sKey1!="branch")
{
clipboard:=A_Index
msgbox,,Error B,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
sSection:=sKey1
if !IsObject(obj_return[sSection])
obj_return[sSection] := {}
sReturn .= A_LoopField "`n"
continue
}
else
{
clipboard:=A_Index
msgbox,,Error C,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
}
else if (iLevel=2)
{
if (sText="],")||(sText="]")
{
iLevel--
sReturn .= A_LoopField "`n"
continue
}
else if (sText="")
{
}
else
{
clipboard:=A_Index
msgbox,,Error D,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n_%sText%_
}
}
else if (iLevel=3)
{
pos1:=instr(sText,"""key"":",0,1)
pos2:=instr(sText,"""en-US"":",0,1)
if (pos1=1)
{
if (iFoundKey=1)
{
clipboard:=A_Index
msgbox,,Error E,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
else
{
iFoundKey=1
sVal:=substr(sText,7)
sVal:=Trim(sVal," ")
sFirst:=substr(sVal,1,1)
sLast:=substr(sVal,strlen(sVal),1)
sPreLast:=substr(sVal,strlen(sVal)-1,1)
if (iFoundLang=1) ; language entry "en-US" was first -> this "key" entry is second -> no comma
{
if (sFirst!="""")||(sLast!="""")
{
clipboard:=A_Index
msgbox,,Error F,Bad formating in %sText%
}
else
{
sEntryJP:=substr(sVal,2,strlen(sVal)-2)
}
}
else ; language entry "en-US" still missing -> this "key" goes first -> has comma
{
if (sFirst!="""")||(sPreLast!="""")||(sLast!=",")
{
clipboard:=A_Index
msgbox,,Error G,Bad formating in %sText%
}
else
{
sEntryJP:=substr(sVal,2,strlen(sVal)-3)
}
}
}
}
else if (pos2=1)
{
if (iFoundLang=1)
{
clipboard:=A_Index
msgbox,,Error H,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
else
{
iFoundLang=1
sVal:=substr(sText,9)
sVal:=Trim(sVal," ")
sFirst:=substr(sVal,1,1)
sLast:=substr(sVal,strlen(sVal),1)
sPreLast:=substr(sVal,strlen(sVal)-1,1)
if (iFoundKey=0) ; "key" entry still missing -> this language entry goes first -> has comma
{
if (sVal="null,")
sEntryEN:="null"
else
{
if (sFirst!="""")||(sPreLast!="""")||(sLast!=",")
{
clipboard:=A_Index
msgbox,,Error I,Bad formating in %sText%
}
else
{
sEntryEN:=substr(sVal,2,strlen(sVal)-3)
}
}
}
else ; "key" entry already exists -> this language entry is second -> no comma
{
if (sVal="null")
sEntryEN:=sVal
else
{
if (sFirst!="""")||(sLast!="""")
{
clipboard:=A_Index
msgbox,,Error J,Bad formating in %sText%
}
else
{
sEntryEN:=substr(sVal,2,strlen(sVal)-2)
}
}
}
}
}
else
{
clipboard:=A_Index
msgbox,,Error K,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
}
else
{
clipboard:=A_Index
msgbox,,Error L,Level %iLevel% / Key %sSection% / Line %A_Index%`n`n"%sText%"
}
}
sReturn:=RTrim(sReturn,"`r`n")
; Remove unnecessary comma when retransitioning from CJK-> Ascii keys before the end of a section
sReturn := StrReplace(sReturn,"`n },`n ],`n","`n }`n ],`n")
return sReturn
}