Pārlūkot izejas kodu

feat(bundler): allow setting wix language, closes #1976 (#1988)

Lucas Fernandes Nogueira 4 gadi atpakaļ
vecāks
revīzija
4791961981

+ 5 - 0
.changes/wix-bundle-language.md

@@ -0,0 +1,5 @@
+---
+"tauri-bundler": patch
+---
+
+Allow setting the Windows installer language and using project names that contains non-Unicode characters.

+ 5 - 0
.changes/wix-config-language.md

@@ -0,0 +1,5 @@
+---
+"cli.rs": patch
+---
+
+Adds `tauri > bundle > windows > wix > language` config option. See https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables.

+ 1 - 0
docs/api/config.md

@@ -160,6 +160,7 @@ It's composed of the following properties:
         { property: "certificateThumbprint", optional: true, type: "string[]", description: `Specifies the SHA1 hash of the signing certificate.` },
         { property: "timestampUrl", optional: true, type: "string[]", description: `Server to use during timestamping.` },
         { property: "wix", optional: true, type: "object", child: <Properties anchorRoot="tauri.bundle.windows.wix" rows={[
+          { property: "language", optional: true, type: "string", description: `The installer language. See https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables.` },
           { property: "template", optional: true, type: "string", description: `A custom .wxs template to use.` },
           { property: "fragmentPaths", optional: true, type: "string[]", description: `A list of paths to .wxs files with WiX fragments to use.` },
           { property: "componentGroupRefs", optional: true, type: "string[]", description: `The ComponentGroup element ids you want to reference from the fragments.` },

+ 2 - 0
tooling/bundler/src/bundle/settings.rs

@@ -175,6 +175,8 @@ pub struct MacOsSettings {
 /// Settings specific to the WiX implementation.
 #[derive(Clone, Debug, Default)]
 pub struct WixSettings {
+  /// The app language. See https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables.
+  pub language: String,
   /// By default, the bundler uses an internal template.
   /// This option allows you to define your own wix file.
   pub template: Option<PathBuf>,

+ 154 - 0
tooling/bundler/src/bundle/windows/msi/languages.json

@@ -0,0 +1,154 @@
+{
+  "ar-SA": {
+    "langId": 1025,
+    "asciiCode": 1256
+  },
+  "ca-ES": {
+    "langId": 1027,
+    "asciiCode": 1252
+  },
+  "zh-TW": {
+    "langId": 1028,
+    "asciiCode": 950
+  },
+  "zh-CN": {
+    "langId": 2052,
+    "asciiCode": 936
+  },
+  "cs-CZ": {
+    "langId": 1029,
+    "asciiCode": 1250
+  },
+  "da-DK": {
+    "langId": 1030,
+    "asciiCode": 1252
+  },
+  "de-DE": {
+    "langId": 1031,
+    "asciiCode": 1252
+  },
+  "el-GR": {
+    "langId": 1032,
+    "asciiCode": 1253
+  },
+  "en-US": {
+    "langId": 1033,
+    "asciiCode": 1252
+  },
+  "es-ES": {
+    "langId": 3082,
+    "asciiCode": 1252
+  },
+  "et-EE": {
+    "langId": 1061,
+    "asciiCode": 1257
+  },
+  "fi-FI": {
+    "langId": 1035,
+    "asciiCode": 1252
+  },
+  "fr-FR": {
+    "langId": 1036,
+    "asciiCode": 1252
+  },
+  "he-IL": {
+    "langId": 1037,
+    "asciiCode": 1255
+  },
+  "hu-HU": {
+    "langId": 1038,
+    "asciiCode": 1250
+  },
+  "it-IT": {
+    "langId": 1040,
+    "asciiCode": 1252
+  },
+  "jp-JP": {
+    "langId": 1041,
+    "asciiCode": 932
+  },
+  "ko-KO": {
+    "langId": 1042,
+    "asciiCode": 949
+  },
+  "lt-LT": {
+    "langId": 1063,
+    "asciiCode": 1257
+  },
+  "lv-LV": {
+    "langId": 1062,
+    "asciiCode": 1257
+  },
+  "nl-NL": {
+    "langId": 1043,
+    "asciiCode": 1252
+  },
+  "nb-NO": {
+    "langId": 1044,
+    "asciiCode": 1252
+  },
+  "pl-PL": {
+    "langId": 1045,
+    "asciiCode": 1250
+  },
+  "pt-BR": {
+    "langId": 1046,
+    "asciiCode": 1252
+  },
+  "pt-PT": {
+    "langId": 2070,
+    "asciiCode": 1252
+  },
+  "ro-RO": {
+    "langId": 1048,
+    "asciiCode": 1250
+  },
+  "ru-RU": {
+    "langId": 1049,
+    "asciiCode": 1251
+  },
+  "hr-HR": {
+    "langId": 1050,
+    "asciiCode": 1250
+  },
+  "sk-SK": {
+    "langId": 1051,
+    "asciiCode": 1250
+  },
+  "sv-SE": {
+    "langId": 1053,
+    "asciiCode": 1252
+  },
+  "th-TH": {
+    "langId": 1054,
+    "asciiCode": 874
+  },
+  "tr-TR": {
+    "langId": 1055,
+    "asciiCode": 1254
+  },
+  "sl-SI": {
+    "langId": 1060,
+    "asciiCode": 1250
+  },
+  "vi-VN": {
+    "langId": 1066,
+    "asciiCode": 1258
+  },
+  "eu-ES": {
+    "langId": 1069,
+    "asciiCode": 1252
+  },
+  "bg-BG": {
+    "langId": 1026,
+    "asciiCode": 1251
+  },
+  "uk-UA": {
+    "langId": 1058,
+    "asciiCode": 1251
+  },
+  "sr-Latn-CS": {
+    "langId": 2074,
+    "asciiCode": 1250
+  }
+}

+ 63 - 25
tooling/bundler/src/bundle/windows/msi/wix.rs

@@ -11,14 +11,14 @@ use crate::bundle::{
 
 use handlebars::{to_json, Handlebars};
 use regex::Regex;
-use serde::Serialize;
+use serde::{Deserialize, Serialize};
 use sha2::Digest;
 use uuid::Uuid;
 use zip::ZipArchive;
 
 use std::{
-  collections::BTreeMap,
-  fs::{create_dir_all, remove_dir_all, write, File},
+  collections::{BTreeMap, HashMap},
+  fs::{create_dir_all, remove_dir_all, rename, write, File},
   io::{Cursor, Read, Write},
   path::{Path, PathBuf},
   process::{Command, Stdio},
@@ -52,6 +52,14 @@ const UUID_NAMESPACE: [u8; 16] = [
 /// Mapper between a resource directory name and its ResourceDirectory descriptor.
 type ResourceMap = BTreeMap<String, ResourceDirectory>;
 
+#[derive(Debug, Deserialize)]
+struct LanguageMetadata {
+  #[serde(rename = "asciiCode")]
+  ascii_code: usize,
+  #[serde(rename = "langId")]
+  lang_id: usize,
+}
+
 /// A binary to bundle with WIX.
 /// External binaries or additional project binaries are represented with this data structure.
 /// This data structure is needed because WIX requires each path to have its own `id` and `guid`.
@@ -313,10 +321,10 @@ fn run_candle(
 fn run_light(
   wix_toolset_path: &Path,
   build_path: &Path,
-  wixobjs: &[&str],
+  arguments: Vec<String>,
   output_path: &Path,
   settings: &Settings,
-) -> crate::Result<PathBuf> {
+) -> crate::Result<()> {
   let light_exe = wix_toolset_path.join("light.exe");
 
   let mut args: Vec<String> = vec![
@@ -326,8 +334,8 @@ fn run_light(
     output_path.display().to_string(),
   ];
 
-  for p in wixobjs {
-    args.push((*p).to_string());
+  for p in arguments {
+    args.push(p);
   }
 
   let mut cmd = Command::new(&light_exe);
@@ -336,19 +344,16 @@ fn run_light(
     .stdout(Stdio::piped())
     .current_dir(build_path);
 
-  common::print_info(format!("running light to produce {}", output_path.display()).as_str())?;
-  common::execute_with_verbosity(&mut cmd, settings)
-    .map(|_| output_path.to_path_buf())
-    .map_err(|_| {
-      crate::Error::ShellScriptError(format!(
-        "error running light.exe{}",
-        if settings.is_verbose() {
-          ""
-        } else {
-          ", try running with --verbose to see command output"
-        }
-      ))
-    })
+  common::execute_with_verbosity(&mut cmd, settings).map_err(|_| {
+    crate::Error::ShellScriptError(format!(
+      "error running light.exe{}",
+      if settings.is_verbose() {
+        ""
+      } else {
+        ", try running with --verbose to see command output"
+      }
+    ))
+  })
 }
 
 // fn get_icon_data() -> crate::Result<()> {
@@ -406,6 +411,29 @@ pub fn build_wix_app_installer(
 
   let mut data = BTreeMap::new();
 
+  let language_map: HashMap<String, LanguageMetadata> =
+    serde_json::from_str(include_str!("./languages.json")).unwrap();
+
+  let (language, language_metadata) = if let Some(wix) = &settings.windows().wix {
+    let metadata = language_map.get(&wix.language).unwrap_or_else(|| {
+      panic!(
+        "Language {} not found. It must be one of {}",
+        wix.language,
+        language_map
+          .keys()
+          .cloned()
+          .collect::<Vec<String>>()
+          .join(", ")
+      )
+    });
+    (wix.language.clone(), metadata)
+  } else {
+    common::print_info("Wix settings not found. Using `en-US` as language.")?;
+    ("en-US".into(), language_map.get("en-US").unwrap())
+  };
+  data.insert("language_id", to_json(language_metadata.lang_id));
+  data.insert("ascii_codepage", to_json(language_metadata.ascii_code));
+
   data.insert("product_name", to_json(settings.product_name()));
   data.insert("version", to_json(settings.version_string()));
   let manufacturer = settings.bundle_identifier().to_string();
@@ -511,16 +539,26 @@ pub fn build_wix_app_installer(
     run_candle(settings, wix_toolset_path, &output_path, wxs)?;
   }
 
-  let wixobjs = vec!["*.wixobj"];
-  let target = run_light(
+  let arguments = vec![
+    format!("-cultures:{}", language.to_lowercase()),
+    "*.wixobj".into(),
+  ];
+  let msi_output_path = output_path.join("output.msi");
+  let msi_path = app_installer_dir(settings)?;
+  create_dir_all(msi_path.parent().unwrap())?;
+
+  common::print_info(format!("running light to produce {}", msi_path.display()).as_str())?;
+
+  run_light(
     wix_toolset_path,
     &output_path,
-    &wixobjs,
-    &app_installer_dir(settings)?,
+    arguments,
+    &msi_output_path,
     settings,
   )?;
+  rename(&msi_output_path, &msi_path)?;
 
-  Ok(target)
+  Ok(msi_path)
 }
 
 /// Generates the data required for the external binaries and extra binaries bundling.

+ 4 - 4
tooling/bundler/src/bundle/windows/templates/main.wxs

@@ -13,18 +13,18 @@
             Id="*"
             Name="{{{product_name}}}"
             UpgradeCode="{{{upgrade_code}}}"
-            Language="1033"
-            Codepage="1252"
+            Language="{{language_id}}"
+            Codepage="{{ascii_codepage}}"
             Manufacturer="{{{manufacturer}}}"
             Version="{{{version}}}">
 
         <Package Id="*"
                  Keywords="Installer"
                  InstallerVersion="450"
-                 Languages="1033"
+                 Languages="{{language_id}}"
                  Compressed="yes"
                  InstallScope="perMachine"
-                 SummaryCodepage="1252"/>
+                 SummaryCodepage="{{ascii_codepage}}"/>
 
          <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed."
                   MigrateFeatures="yes" />

+ 7 - 0
tooling/cli.rs/config_definition.rs

@@ -53,9 +53,16 @@ pub struct MacConfig {
   pub entitlements: Option<String>,
 }
 
+fn default_language() -> String {
+  "en-US".into()
+}
+
 #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
 pub struct WixConfig {
+  /// App language. See https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables.
+  #[serde(default = "default_language")]
+  pub language: String,
   pub template: Option<PathBuf>,
   #[serde(default)]
   pub fragment_paths: Vec<PathBuf>,

+ 5 - 0
tooling/cli.rs/schema.json

@@ -1289,6 +1289,11 @@
             "type": "string"
           }
         },
+        "language": {
+          "description": "App language. See https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables.",
+          "default": "en-US",
+          "type": "string"
+        },
         "mergeRefs": {
           "default": [],
           "type": "array",

+ 1 - 0
tooling/cli.rs/src/helpers/config.rs

@@ -16,6 +16,7 @@ pub use config_definition::*;
 impl From<WixConfig> for tauri_bundler::WixSettings {
   fn from(config: WixConfig) -> tauri_bundler::WixSettings {
     tauri_bundler::WixSettings {
+      language: config.language,
       template: config.template,
       fragment_paths: config.fragment_paths,
       component_group_refs: config.component_group_refs,