From d2474d9f455bb8471698da3305d442c633143813 Mon Sep 17 00:00:00 2001 From: Jyrki Gadinger Date: Wed, 2 Apr 2025 12:36:16 +0200 Subject: [PATCH 1/2] feat: build translated MSI installer the base translations come from WixUI itself There are still some untranslated strings in Nextcloud.wxs, we need to take care of them at a later point. As a first step this is good enough. This commit serves as a proof-of-concept, the languages I used here will change depending on what we want to support. Signed-off-by: Jyrki Gadinger --- admin/win/msi/make-msi.bat.in | 154 +++++++++++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 11 deletions(-) diff --git a/admin/win/msi/make-msi.bat.in b/admin/win/msi/make-msi.bat.in index eb14735327fdf..e721fa88cfa8b 100644 --- a/admin/win/msi/make-msi.bat.in +++ b/admin/win/msi/make-msi.bat.in @@ -2,25 +2,157 @@ set HarvestAppDir=%~1 set BuildArch=@MSI_BUILD_ARCH@ +setlocal EnableDelayedExpansion + +REM list of additional supported languages in the installer +REM since candle needs to be run for each codepage we can reuse its output for the other languages +REM IMPORTANT: end each line but the last with ;^ +REM +REM see also: https://learn.microsoft.com/en-gb/windows/win32/msi/localizing-the-error-and-actiontext-tables +REM the codepage is the value of the "ASCII code page" in the table +set languages=^ +1252:en-us de-de fr-fr;^ +1250:cs-cz + +set languages_to_id=^ +en-us:1033;^ +de-de:1031;^ +fr-fr:1036;^ +cs-cz:1029 + +set LF=^ + + +REM do NOT delete the two lines above! !LF! will contain a single newline in that case, which +REM will be used to replace the ; from the languages list with the newline in order to make +REM Batch's FOR loop happy... + if "%HarvestAppDir%" == "" ( - echo "Missing parameter: Please specify file collection source path (HarvestAppDir)." - exit 1 + echo "Missing parameter: Please specify file collection source path (HarvestAppDir)." + exit 1 ) if "%WIX%" == "" ( - echo "WiX Toolset path not set (environment variable 'WIX'). Please install the WiX Toolset." - exit 1 + echo "WiX Toolset path not set (environment variable 'WIX'). Please install the WiX Toolset." + exit 1 ) -Rem Generate collect.wxs +if "%WISUBSTG%" == "" ( + REM WISUBSTG is used only in :create_language_transform^(s^) for non-en-us locales + echo :: Did not find WiSubStg.vbs ^(environment variable 'WISUBSTG'^), will only build the English installer + set languages=1252:en-us +) else ( if "%WILANGID%" == "" ( + REM WILANGID is used to update the supported languages field in the MSI ^(Package in .wxs file -- unfortunately the Language attribute only allows one language at a time^) + REM Nextcloud.wxs specifies the language as 1033 ^(en-us^) as default, so this step can be skipped + echo :: Did not find WiLangId.vbs ^(environment variable 'WILANGID'^), the resulting MSI will be in English by default +)) + + +echo/ +echo :: Harvesting files for MSI "%WIX%\bin\heat.exe" dir "%HarvestAppDir%" -dr INSTALLDIR -sreg -srd -sfrag -ag -cg ClientFiles -var var.HarvestAppDir -platform='%BuildArch%' -t collect-transform.xsl -out collect.wxs if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% -Rem Compile en-US (https://www.firegiant.com/wix/tutorial/transforms/morphing-installers/) -"%WIX%\bin\candle.exe" -dcodepage=1252 -dPlatform=%BuildArch% -arch %BuildArch% -dHarvestAppDir="%HarvestAppDir%" -ext WixUtilExtension NCMsiHelper.wxs WinShellExt.wxs collect.wxs Nextcloud.wxs RegistryCleanupCustomAction.wxs -if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% +call :build_each_language + +call :create_language_transforms + +if NOT "%WILANGID%" == "" ( + set supported_language_ids= + for /f "tokens=1,2 eol=; delims=:" %%i in ("%languages_to_id:;=!LF!%") do ( + REM join all known language IDs with a comma + if "!supported_language_ids!" == "" ( + REM the first language in the list does not need a comma at the start + set supported_language_ids=%%j + ) else ( + set supported_language_ids=!supported_language_ids!,%%j + ) + ) + echo :: Updating supported languages in MSI Package to !supported_language_ids! + cscript "%WILANGID%" "en-us_@MSI_INSTALLER_FILENAME@" Package !supported_language_ids! + echo/ +) + +REM rename the en-US installer to a non-prefixed file name +echo :: Finalising MSI build +rename "en-us_@MSI_INSTALLER_FILENAME@" "@MSI_INSTALLER_FILENAME@" +echo/ + +echo :: Done +exit 0 + + +:build_each_language + for /f "tokens=1,* eol=; delims=:" %%l in ("%languages:;=!LF!%") do ( + set codepage=%%l + call :compile_msi_for_codepage %%l + call :link_msi_for_cultures %%m + ) + exit /b + + +:compile_msi_for_codepage codepage + set codepage=%~1 + + echo :: Compiling MSI project for codepage !codepage! + "%WIX%\bin\candle.exe" -dcodepage=!codepage! -dPlatform=%BuildArch% -arch %BuildArch% -dHarvestAppDir="%HarvestAppDir%" -ext WixUtilExtension NCMsiHelper.wxs WinShellExt.wxs collect.wxs Nextcloud.wxs RegistryCleanupCustomAction.wxs + if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% + echo/ + + exit /b + + +:link_msi_for_cultures culture1 culture2 ... + set cultures=%* + + for %%c in (%cultures: =!LF!%) do ( + set culture=%%c + + echo :: Linking MSI project for !culture! + "%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:!culture!;en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Nextcloud.wixobj RegistryCleanupCustomAction.wixobj -out "!culture!_@MSI_INSTALLER_FILENAME@" + if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% + ) + exit /b + + +:create_language_transforms + REM same loop as in :build_each_language, but we don't care about the codepage + for /f "tokens=1,* eol=; delims=:" %%l in ("%languages:;=!LF!%") do ( + call :create_language_transform %%m + ) + exit /b + + +:create_language_transform culture1 culture2 ... + set cultures=%* + + for %%c in (%cultures: =!LF!%) do ( + set culture=%%c + + if NOT "!culture!" == "en-us" ( + REM en-us is the base language for our installer, no need to create transforms for that + + echo :: Creating MSI transform for !culture! + "%WIX%\bin\torch.exe" -p -t language "en-us_@MSI_INSTALLER_FILENAME@" "!culture!_@MSI_INSTALLER_FILENAME@" -out "!culture!.mst" + if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% + + call :set_language_id_for_culture + echo :: Embedding language !culture! with id !language_id! into MSI + cscript "%WISUBSTG%" "en-us_@MSI_INSTALLER_FILENAME@" "!culture!.mst" !language_id! + + REM at this point we do not need the translated installer anymore + del "!culture!_@MSI_INSTALLER_FILENAME@" + ) + ) + exit /b -Rem Link MSI package -"%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Nextcloud.wixobj RegistryCleanupCustomAction.wixobj -out "@MSI_INSTALLER_FILENAME@" -exit %ERRORLEVEL% +:set_language_id_for_culture !culture! needs to be set already + set language_id="" + for /f "tokens=1,2 eol=; delims=:" %%i in ("%languages_to_id:;=!LF!%") do ( + if "!culture!" == "%%i" ( + set language_id=%%j + exit /b + ) + ) + exit /b From a6a17e324a32252bd62cdab11985381dbef53996 Mon Sep 17 00:00:00 2001 From: Jyrki Gadinger Date: Wed, 2 Apr 2025 13:41:03 +0200 Subject: [PATCH 2/2] feat: build installer with translations for DE, FR, IT, ES building all the translated installers takes about ~1 minute each, so keep that in mind when adding extra translations ... Signed-off-by: Jyrki Gadinger --- admin/win/msi/make-msi.bat.in | 41 +++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/admin/win/msi/make-msi.bat.in b/admin/win/msi/make-msi.bat.in index e721fa88cfa8b..453f101e946dd 100644 --- a/admin/win/msi/make-msi.bat.in +++ b/admin/win/msi/make-msi.bat.in @@ -4,21 +4,25 @@ set BuildArch=@MSI_BUILD_ARCH@ setlocal EnableDelayedExpansion -REM list of additional supported languages in the installer +REM list of additional supported languages in the installer, grouped by codepage REM since candle needs to be run for each codepage we can reuse its output for the other languages +REM +REM each additional language will add about ~1min of build time! +REM in order to be able to create transforms of the installer we must have an already-translated MSI ready, so for each language a separate temporary MSI will be built +REM REM IMPORTANT: end each line but the last with ;^ REM REM see also: https://learn.microsoft.com/en-gb/windows/win32/msi/localizing-the-error-and-actiontext-tables REM the codepage is the value of the "ASCII code page" in the table set languages=^ -1252:en-us de-de fr-fr;^ -1250:cs-cz +1252:en-us de-de fr-fr it-it es-es set languages_to_id=^ en-us:1033;^ de-de:1031;^ fr-fr:1036;^ -cs-cz:1029 +it-it:1040;^ +es-es:3082 set LF=^ @@ -55,7 +59,7 @@ if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% call :build_each_language -call :create_language_transforms +call :embed_language_transforms if NOT "%WILANGID%" == "" ( set supported_language_ids= @@ -111,37 +115,42 @@ exit 0 echo :: Linking MSI project for !culture! "%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:!culture!;en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Nextcloud.wixobj RegistryCleanupCustomAction.wixobj -out "!culture!_@MSI_INSTALLER_FILENAME@" if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% + + if NOT "!culture!" == "en-us" ( + REM en-us is the base language for our installer, no need to create transforms for that + + echo :: Creating MSI transform for !culture! + "%WIX%\bin\torch.exe" -p -t language "en-us_@MSI_INSTALLER_FILENAME@" "!culture!_@MSI_INSTALLER_FILENAME@" -out "!culture!.mst" + if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% + + REM at this point we do not need the translated installer anymore + del "!culture!_@MSI_INSTALLER_FILENAME@" + ) + ) exit /b -:create_language_transforms +:embed_language_transforms REM same loop as in :build_each_language, but we don't care about the codepage for /f "tokens=1,* eol=; delims=:" %%l in ("%languages:;=!LF!%") do ( - call :create_language_transform %%m + call :embed_language_transform %%m ) exit /b -:create_language_transform culture1 culture2 ... +:embed_language_transform culture1 culture2 ... set cultures=%* for %%c in (%cultures: =!LF!%) do ( set culture=%%c if NOT "!culture!" == "en-us" ( - REM en-us is the base language for our installer, no need to create transforms for that - - echo :: Creating MSI transform for !culture! - "%WIX%\bin\torch.exe" -p -t language "en-us_@MSI_INSTALLER_FILENAME@" "!culture!_@MSI_INSTALLER_FILENAME@" -out "!culture!.mst" - if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% + REM en-us is the base language for our installer, there is no transform for that call :set_language_id_for_culture echo :: Embedding language !culture! with id !language_id! into MSI cscript "%WISUBSTG%" "en-us_@MSI_INSTALLER_FILENAME@" "!culture!.mst" !language_id! - - REM at this point we do not need the translated installer anymore - del "!culture!_@MSI_INSTALLER_FILENAME@" ) ) exit /b