Wix 安裝部署教程(十三) -- 多語言安裝包


      這幾天摸索WIX的多語言安裝包(這里是Wix的setup 工程,不是Bundle),終於走通了,感謝網友uni的指點。WIX的多語言安裝包能夠根據系統環境自動切換界面語言,你也可以通過命令指定語言。下面我說一說步驟。共4步。

      1.設置WixLocalization文件。

      Wxl文件就相當於應用程序的資源文件。讓我們根據不同的語言來編寫不同的文本內容。

      

      例如我們新建一個WixUI_zh-cn.wxl ,來處理簡體中文。

<WixLocalization Culture="zh-cn" Codepage="936" xmlns="http://schemas.microsoft.com/wix/2006/localization">
  <String Id="WixUIBack" Overridable="yes">上一步(&amp;B)</String>
  <String Id="WixUINext" Overridable="yes">下一步(&amp;N)</String>
  <String Id="WixUICancel" Overridable="yes">取消</String>
  <String Id="WixUIFinish" Overridable="yes">完成(&amp;F)</String>
  <String Id="WixUIRetry" Overridable="yes">重試(&amp;R)</String>
  <String Id="WixUIIgnore" Overridable="yes">忽略(&amp;I)</String>
  <String Id="WixUIYes" Overridable="yes">是(&amp;Y)</String>
  <String Id="WixUINo" Overridable="yes">否(&amp;N)</String>
  <String Id="WixUIOK" Overridable="yes">確定</String>
  <String Id="WixUIPrint" Overridable="yes">打印(&amp;P)</String>
......

   這些主要是用於UI界面上的元素,我們看Wix的窗口源碼:下面的!(loc.WixUINext)和!(loc.WixUICancel) 和上面的Id為WixUINext和Id為WixUICancel的String 一一 對應。而屬性Overridable,表示可以重寫。因為默認是英文,可以重寫的意思就是最新的String會覆蓋之前的同名Id。

<Dialog Id="WelcomeDlg"  Width="370"  Title="!(loc.WelcomeDlg_Title)">
<Control Id="Next"  Type="PushButton"  X="236"  Y="243"  Width="56"Height="17"  Default="yes"   Text="!(loc.WixUINext)" />
<Control Id="Cancel" Type="PushButton" X="304" Y="243"   Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>

如果要添加自定義的內容,添加String就行,另外我們可以看見,這個wxl文件有Culture="zh-cn" Codepage="936"兩個屬性,這是和Product的Language屬性和Codepage屬性對應,而Product的Codepage屬性不能使用!loc.Id 的方式,但Wix會根據wxl文件進行重載。Package的Languages屬性和SummaryCodepage 也是一樣對應。 

 

 <String Id="Lang">2052</String>
  <String Id="Code">936</String>

下面是三種語言的Language和Codepage。

  語言                              語言-國家     Language      Codepage
English                            en-us             1033               1252
Simplified Chinese            zh-cn              2052               936
Traditional Chinese           zh-tw              1028                950

 同理我們分別添加英文和繁體的wxl。更多的源文件在源碼的下面的目錄可以找到:

 WIX\wix-3.8\src\ext\UIExtension\wixlib

 最終我們得到:

這個時候不同語言設置的工作我們已經完成。下面設置下屬性--Build--Cultures to build,生成安裝包。

 

 但是我們看到這三個安裝文件,這不是我們想要的。畢竟三個文件都是一樣的大。 我們繼續處理。將多個msi文件融合成一個。

2.生成mst文件

 這里要用到transform files(變形文件?恩,就這么翻譯吧) 一個變形文件(.mst)包含了兩個msi文件的比較內容,在做Path的時候,也會用到它。既然是比較內容,就需要有一個文件作為基礎,這次我們選英文。另外我們會用到一個wix工具:Torch.exe,它在Wix的安裝目錄bin下面可以找到。它的命令語法如下:

torch.exe [-?] [options] targetInput updatedInput -out outputFile  [@responseFile]

示例:

torch.exe -t language "en-us\MyInstaller.msi""zh-cn\MyInstaller.msi" -out "transforms\zn-cn.mst"

別忘了用管理員身份執行,還要帶上正確的路徑。生成的mst文件再wix的bin目錄,也就是和torch.exe的同級目錄。如下圖所示,操作正確會出現 Windows Installer XML Toolset。。。的字樣。

 同理我們生成zh-tw.mst和zh-cn.mst,要生成en-us.mst,就把中文和英文的位置換一下。 生成文件如下。

3.合並mst文件。

步驟2相當於我們拿到三個界面的資源文件,現在就是需要把他們合並成一個文件,也就是把mst嵌入到msi文件中。WIX本身沒有提供工具做這個事情,但Windows 的SDK 有幾個VBScript文件能執行數據庫(MSI本身就是一種數據庫文件)相關的任務。你需要從MSDN下載安裝SDK,這些腳本包含在Win32的示例中。你也可以從下面的鏈接了解更多內容。

https://msdn.microsoft.com/en-us/library/aa372865(VS.85).aspx

而我們感興趣的腳本是WixSubStg.vbs. win7系統,它的目錄應該是在C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\sysmgmt\msi\scripts. 中。找到之后我們復制到安裝工程的Debug目錄下。

WixSubStg.vbs:

' Windows Installer utility to add a transform or nested database as a substorage
' For use with Windows Scripting Host, CScript.exe or WScript.exe
' Copyright (c) Microsoft Corporation. All rights reserved.
' Demonstrates the use of the database _Storages table
'
Option Explicit

Const msiOpenDatabaseModeReadOnly     = 0
Const msiOpenDatabaseModeTransact     = 1
Const msiOpenDatabaseModeCreate       = 3

Const msiViewModifyInsert         = 1
Const msiViewModifyUpdate         = 2
Const msiViewModifyAssign         = 3
Const msiViewModifyReplace        = 4
Const msiViewModifyDelete         = 6

Const ForAppending = 8
Const ForReading = 1
Const ForWriting = 2
Const TristateTrue = -1

' Check arg count, and display help if argument not present or contains ?
Dim argCount:argCount = Wscript.Arguments.Count
If argCount > 0 Then If InStr(1, Wscript.Arguments(0), "?", vbTextCompare) > 0 Then argCount = 0
If (argCount = 0) Then
    Wscript.Echo "Windows Installer database substorage managment utility" &_
        vbNewLine & " 1st argument is the path to MSI database (installer package)" &_
        vbNewLine & " 2nd argument is the path to a transform or database to import" &_
        vbNewLine & " If the 2nd argument is missing, substorages will be listed" &_
        vbNewLine & " 3rd argument is optional, the name used for the substorage" &_
        vbNewLine & " If the 3rd arugment is missing, the file name is used" &_
        vbNewLine & " To remove a substorage, use /D or -D as the 2nd argument" &_
        vbNewLine & " followed by the name of the substorage to remove" &_
        vbNewLine &_
        vbNewLine & "Copyright (C) Microsoft Corporation.  All rights reserved."
    Wscript.Quit 1
End If

' Connect to Windows Installer object
On Error Resume Next
Dim installer : Set installer = Nothing
Set installer = Wscript.CreateObject("WindowsInstaller.Installer") : CheckError

' Evaluate command-line arguments and set open and update modes
Dim databasePath:databasePath = Wscript.Arguments(0)
Dim openMode    : If argCount = 1 Then openMode = msiOpenDatabaseModeReadOnly Else openMode = msiOpenDatabaseModeTransact
Dim updateMode  : If argCount > 1 Then updateMode = msiViewModifyAssign  'Either insert or replace existing row
Dim importPath  : If argCount > 1 Then importPath = Wscript.Arguments(1)
Dim storageName : If argCount > 2 Then storageName = Wscript.Arguments(2)
If storageName = Empty And importPath <> Empty Then storageName = Right(importPath, Len(importPath) - InStrRev(importPath, "\",-1,vbTextCompare))
If UCase(importPath) = "/D" Or UCase(importPath) = "-D" Then updateMode = msiViewModifyDelete : importPath = Empty 'substorage will be deleted if no input data

' Open database and create a view on the _Storages table
Dim sqlQuery : Select Case updateMode
    Case msiOpenDatabaseModeReadOnly: sqlQuery = "SELECT `Name` FROM _Storages"
    Case msiViewModifyAssign:         sqlQuery = "SELECT `Name`,`Data` FROM _Storages"
    Case msiViewModifyDelete:         sqlQuery = "SELECT `Name` FROM _Storages WHERE `Name` = ?"
End Select
Dim database : Set database = installer.OpenDatabase(databasePath, openMode) : CheckError
Dim view     : Set view = database.OpenView(sqlQuery)
Dim record

If openMode = msiOpenDatabaseModeReadOnly Then 'If listing storages, simply fetch all records
    Dim message, name
    view.Execute : CheckError
    Do
        Set record = view.Fetch
        If record Is Nothing Then Exit Do
        name = record.StringData(1)
        If message = Empty Then message = name Else message = message & vbNewLine & name
    Loop
    Wscript.Echo message
Else 'If adding a storage, insert a row, else if removing a storage, delete the row
    Set record = installer.CreateRecord(2)
    record.StringData(1) = storageName
    view.Execute record : CheckError
    If importPath <> Empty Then  'Insert storage - copy data into stream
        record.SetStream 2, importPath : CheckError
    Else  'Delete storage, fetch first to provide better error message if missing
        Set record = view.Fetch
        If record Is Nothing Then Wscript.Echo "Storage not present:", storageName : Wscript.Quit 2
    End If
    view.Modify updateMode, record : CheckError
    database.Commit : CheckError
    Set view = Nothing
    Set database = Nothing
    CheckError
End If

Sub CheckError
    Dim message, errRec
    If Err = 0 Then Exit Sub
    message = Err.Source & " " & Hex(Err) & ": " & Err.Description
    If Not installer Is Nothing Then
        Set errRec = installer.LastErrorRecord
        If Not errRec Is Nothing Then message = message & vbNewLine & errRec.FormatText
    End If
    Wscript.Echo message
    Wscript.Quit 2
End Sub
View Code

打開cmd 執行下面的命令:

 WiSubStg.vbs "en-us\DIAViewSetup.msi" "transforms\zh-cn.mst" 2052
WiSubStg.vbs "en-us\DIAViewSetup.msi" "transforms\zh-tw.mst" 1028

 等於是在英文的安裝包里面嵌入了簡體和繁體的語言變形。 所以我們不必嵌入英文的變形。 完成了這個步驟還不夠。我們還需要改變安裝包Packages的Languages屬性,告訴它支持的語言類型有哪些。而這需要用到另外一個腳本文件

WiLangId.vbs:也需要放於Debug目錄下面。

' Windows Installer utility to report the language and codepage for a package
' For use with Windows Scripting Host, CScript.exe or WScript.exe
' Copyright (c) Microsoft Corporation. All rights reserved.
' Demonstrates the access of language and codepage values                 
'
Option Explicit

Const msiOpenDatabaseModeReadOnly     = 0
Const msiOpenDatabaseModeTransact     = 1
Const ForReading = 1
Const ForWriting = 2
Const TristateFalse = 0

Const msiViewModifyInsert         = 1
Const msiViewModifyUpdate         = 2
Const msiViewModifyAssign         = 3
Const msiViewModifyReplace        = 4
Const msiViewModifyDelete         = 6

Dim argCount:argCount = Wscript.Arguments.Count
If argCount > 0 Then If InStr(1, Wscript.Arguments(0), "?", vbTextCompare) > 0 Then argCount = 0
If (argCount = 0) Then
    message = "Windows Installer utility to manage language and codepage values for a package." &_
        vbNewLine & "The package language is a summary information property that designates the" &_
        vbNewLine & " primary language and any language transforms that are available, comma delim." &_
        vbNewLine & "The ProductLanguage in the database Property table is the language that is" &_
        vbNewLine & " registered for the product and determines the language used to load resources." &_
        vbNewLine & "The codepage is the ANSI codepage of the database strings, 0 if all ASCII data," &_
        vbNewLine & " and must represent the text data to avoid loss when persisting the database." &_
        vbNewLine & "The 1st argument is the path to MSI database (installer package)" &_
        vbNewLine & "To update a value, the 2nd argument contains the keyword and the 3rd the value:" &_
        vbNewLine & "   Package  {base LangId optionally followed by list of language transforms}" &_
        vbNewLine & "   Product  {LangId of the product (could be updated by language transforms)}" &_
        vbNewLine & "   Codepage {ANSI codepage of text data (use with caution when text exists!)}" &_
        vbNewLine &_
        vbNewLine & "Copyright (C) Microsoft Corporation.  All rights reserved."
    Wscript.Echo message
    Wscript.Quit 1
End If

' Connect to Windows Installer object
On Error Resume Next
Dim installer : Set installer = Nothing
Set installer = Wscript.CreateObject("WindowsInstaller.Installer") : CheckError


' Open database
Dim databasePath:databasePath = Wscript.Arguments(0)
Dim openMode : If argCount >= 3 Then openMode = msiOpenDatabaseModeTransact Else openMode = msiOpenDatabaseModeReadOnly
Dim database : Set database = installer.OpenDatabase(databasePath, openMode) : CheckError

' Update value if supplied
If argCount >= 3 Then
    Dim value:value = Wscript.Arguments(2)
    Select Case UCase(Wscript.Arguments(1))
        Case "PACKAGE"  : SetPackageLanguage database, value
        Case "PRODUCT"  : SetProductLanguage database, value
        Case "CODEPAGE" : SetDatabaseCodepage database, value
        Case Else       : Fail "Invalid value keyword"
    End Select
    CheckError
End If

' Extract language info and compose report message
Dim message:message = "Package language = "         & PackageLanguage(database) &_
                    ", ProductLanguage = " & ProductLanguage(database) &_
                    ", Database codepage = "        & DatabaseCodepage(database)
database.Commit : CheckError  ' no effect if opened ReadOnly
Set database = nothing
Wscript.Echo message
Wscript.Quit 0

' Get language list from summary information
Function PackageLanguage(database)
    On Error Resume Next
    Dim sumInfo  : Set sumInfo = database.SummaryInformation(0) : CheckError
    Dim template : template = sumInfo.Property(7) : CheckError
    Dim iDelim:iDelim = InStr(1, template, ";", vbTextCompare)
    If iDelim = 0 Then template = "Not specified!"
    PackageLanguage = Right(template, Len(template) - iDelim)
    If Len(PackageLanguage) = 0 Then PackageLanguage = "0"
End Function

' Get ProductLanguge property from Property table
Function ProductLanguage(database)
    On Error Resume Next
    Dim view : Set view = database.OpenView("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductLanguage'")
    view.Execute : CheckError
    Dim record : Set record = view.Fetch : CheckError
    If record Is Nothing Then ProductLanguage = "Not specified!" Else ProductLanguage = record.IntegerData(1)
End Function

' Get ANSI codepage of database text data
Function DatabaseCodepage(database)
    On Error Resume Next
    Dim WshShell : Set WshShell = Wscript.CreateObject("Wscript.Shell") : CheckError
    Dim tempPath:tempPath = WshShell.ExpandEnvironmentStrings("%TEMP%") : CheckError
    database.Export "_ForceCodepage", tempPath, "codepage.idt" : CheckError
    Dim fileSys : Set fileSys = CreateObject("Scripting.FileSystemObject") : CheckError
    Dim file : Set file = fileSys.OpenTextFile(tempPath & "\codepage.idt", ForReading, False, TristateFalse) : CheckError
    file.ReadLine ' skip column name record
    file.ReadLine ' skip column defn record
    DatabaseCodepage = file.ReadLine
    file.Close
    Dim iDelim:iDelim = InStr(1, DatabaseCodepage, vbTab, vbTextCompare)
    If iDelim = 0 Then Fail "Failure in codepage export file"
    DatabaseCodepage = Left(DatabaseCodepage, iDelim - 1)
    fileSys.DeleteFile(tempPath & "\codepage.idt")
End Function

' Set ProductLanguge property in Property table
Sub SetProductLanguage(database, language)
    On Error Resume Next
    If Not IsNumeric(language) Then Fail "ProductLanguage must be numeric"
    Dim view : Set view = database.OpenView("SELECT `Property`,`Value` FROM `Property`")
    view.Execute : CheckError
    Dim record : Set record = installer.CreateRecord(2)
    record.StringData(1) = "ProductLanguage"
    record.StringData(2) = CStr(language)
    view.Modify msiViewModifyAssign, record : CheckError
End Sub

' Set ANSI codepage of database text data
Sub SetDatabaseCodepage(database, codepage)
    On Error Resume Next
    If Not IsNumeric(codepage) Then Fail "Codepage must be numeric"
    Dim WshShell : Set WshShell = Wscript.CreateObject("Wscript.Shell") : CheckError
    Dim tempPath:tempPath = WshShell.ExpandEnvironmentStrings("%TEMP%") : CheckError
    Dim fileSys : Set fileSys = CreateObject("Scripting.FileSystemObject") : CheckError
    Dim file : Set file = fileSys.OpenTextFile(tempPath & "\codepage.idt", ForWriting, True, TristateFalse) : CheckError
    file.WriteLine ' dummy column name record
    file.WriteLine ' dummy column defn record
    file.WriteLine codepage & vbTab & "_ForceCodepage"
    file.Close : CheckError
    database.Import tempPath, "codepage.idt" : CheckError
    fileSys.DeleteFile(tempPath & "\codepage.idt")
End Sub     

' Set language list in summary information
Sub SetPackageLanguage(database, language)
    On Error Resume Next
    Dim sumInfo  : Set sumInfo = database.SummaryInformation(1) : CheckError
    Dim template : template = sumInfo.Property(7) : CheckError
    Dim iDelim:iDelim = InStr(1, template, ";", vbTextCompare)
    Dim platform : If iDelim = 0 Then platform = ";" Else platform = Left(template, iDelim)
    sumInfo.Property(7) = platform & language
    sumInfo.Persist : CheckError
End Sub

Sub CheckError
    Dim message, errRec
    If Err = 0 Then Exit Sub
    message = Err.Source & " " & Hex(Err) & ": " & Err.Description
    If Not installer Is Nothing Then
        Set errRec = installer.LastErrorRecord
        If Not errRec Is Nothing Then message = message & vbNewLine & errRec.FormatText
    End If
    Fail message
End Sub

Sub Fail(message)
    Wscript.Echo message
    Wscript.Quit 2
End Sub
View Code

語法:

WiLangId.vbs "en-us\TestInstaller.msi" Package 1033,1028,2052

執行成功會出現下面的提示框:

這個時候,我們再點擊英文的安裝包。出現的是中文界面了。因為它已經根據我的系統環境自動做了選擇。測試了繁體系統也是ok的。

 

4.用命令選擇語言。

     但是,這都還不是完整的多語言。有時候用戶需要選擇,比如他想再繁體系統中使用簡體安裝界面,而且我們的安裝界面的多語言需要和我們應用程序的多語言同步起來。基於這個需求,我們還是需要在msi文件上包一個殼,先讓用戶去選擇一門語言。那么我們怎么用命令來執行呢?

      msiexec有命令:

msiexec /i  en-us\DIAViewSetup.msi TRANSFORMS=transforms\zh-tw.mst

 指定在簡體系統中使用繁體界面:

 

 這樣在我們的C++外殼中就可以根據用戶的選擇也觸發不同的界面。如果用戶自己手動去安裝,也起碼可以根據他當前的系統自動切換語言。

 

  小結:本文沒有提供完整的安裝包多語言解決方法。僅僅為大家提供了一種我走通了的方法,另外還有許可證書的部分,不同的語言,不同的許可證書,但路徑又是不能通過loc的方式來處理的。需要自定義窗口來處理,后面有新的發現我會繼續更新。希望對苦逼的WIX學習者帶來幫助。

 

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM