Skip to content

Commit 2948820

Browse files
amrbashirFabianLarslucasfernog
authored
feat(bundler/NSIS): allow specifying custom lang files (#6867)
* feat(bundler/NSIS): allow specifying custom lang files * add dunc as dep on all platforms * clippy * Update tooling/bundler/src/bundle/windows/nsis.rs Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de> * Update core/tauri-utils/src/config.rs Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de> * schema files --------- Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de> Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
1 parent e0c30a7 commit 2948820

File tree

11 files changed

+157
-51
lines changed

11 files changed

+157
-51
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'tauri-bundler': 'minor'
3+
'tauri-utils': 'minor'
4+
'cli.rs': 'minor'
5+
---
6+
7+
Allow specifying custom language files of Tauri's custom messages for the NSIS installer

core/tauri-config-schema/schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,16 @@
17151715
"type": "string"
17161716
}
17171717
},
1718+
"customLanguageFiles": {
1719+
"description": "A key-value pair where the key is the language and the value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\n\nSee <https://github.com/tauri-apps/tauri/blob/dev/tooling/bundler/src/bundle/windows/templates/nsis-languages/English.nsh> for an example `.nsh` file.\n\n**Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,",
1720+
"type": [
1721+
"object",
1722+
"null"
1723+
],
1724+
"additionalProperties": {
1725+
"type": "string"
1726+
}
1727+
},
17181728
"displayLanguageSelector": {
17191729
"description": "Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.",
17201730
"default": false,

core/tauri-utils/src/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,13 @@ pub struct NsisConfig {
461461
///
462462
/// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.
463463
pub languages: Option<Vec<String>>,
464+
/// A key-value pair where the key is the language and the
465+
/// value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.
466+
///
467+
/// See <https://github.com/tauri-apps/tauri/blob/dev/tooling/bundler/src/bundle/windows/templates/nsis-languages/English.nsh> for an example `.nsh` file.
468+
///
469+
/// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,
470+
pub custom_language_files: Option<HashMap<String, PathBuf>>,
464471
/// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.
465472
/// By default the OS language is selected, with a fallback to the first language in the `languages` array.
466473
#[serde(default, alias = "display-language-selector")]

tooling/bundler/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ semver = "1"
3939
sha1 = "0.10"
4040
sha2 = "0.10"
4141
zip = "0.6"
42+
dunce = "1"
4243

4344
[target."cfg(target_os = \"windows\")".dependencies]
4445
uuid = { version = "1", features = [ "v4", "v5" ] }

tooling/bundler/src/bundle/settings.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,13 @@ pub struct NsisSettings {
274274
///
275275
/// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.
276276
pub languages: Option<Vec<String>>,
277+
/// An key-value pair where the key is the language and the
278+
/// value is the path to a custom `.nsi` file that holds the translated text for tauri's custom messages.
279+
///
280+
/// See <https://github.com/tauri-apps/tauri/blob/dev/tooling/bundler/src/bundle/windows/templates/nsis-languages/English.nsh> for an example `.nsi` file.
281+
///
282+
/// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,
283+
pub custom_language_files: Option<HashMap<String, PathBuf>>,
277284
/// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.
278285
/// By default the OS language is selected, with a fallback to the first language in the `languages` array.
279286
pub display_language_selector: bool,

tooling/bundler/src/bundle/windows/nsis.rs

Lines changed: 103 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use crate::{
88
bundle::{
99
common::CommandExt,
1010
windows::util::{
11-
download, download_and_verify, extract_zip, remove_unc_lossy, HashAlgorithm,
12-
NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME, WEBVIEW2_BOOTSTRAPPER_URL,
13-
WEBVIEW2_X64_INSTALLER_GUID, WEBVIEW2_X86_INSTALLER_GUID,
11+
download, download_and_verify, extract_zip, HashAlgorithm, NSIS_OUTPUT_FOLDER_NAME,
12+
NSIS_UPDATER_OUTPUT_FOLDER_NAME, WEBVIEW2_BOOTSTRAPPER_URL, WEBVIEW2_X64_INSTALLER_GUID,
13+
WEBVIEW2_X86_INSTALLER_GUID,
1414
},
1515
},
1616
Settings,
@@ -26,7 +26,7 @@ use tauri_utils::{
2626
};
2727

2828
use std::{
29-
collections::BTreeMap,
29+
collections::{BTreeMap, HashMap},
3030
fs::{copy, create_dir_all, remove_dir_all, rename, write},
3131
path::{Path, PathBuf},
3232
process::Command,
@@ -189,35 +189,31 @@ fn build_nsis_app_installer(
189189
let mut install_mode = NSISInstallerMode::CurrentUser;
190190
let mut languages = vec!["English".into()];
191191
let mut custom_template_path = None;
192+
let mut custom_language_files = None;
192193
if let Some(nsis) = &settings.windows().nsis {
193194
custom_template_path = nsis.template.clone();
195+
custom_language_files = nsis.custom_language_files.clone();
194196
install_mode = nsis.install_mode;
195197
if let Some(langs) = &nsis.languages {
196198
languages.clear();
197199
languages.extend_from_slice(langs);
198200
}
199201
if let Some(license) = &nsis.license {
200-
data.insert(
201-
"license",
202-
to_json(remove_unc_lossy(license.canonicalize()?)),
203-
);
202+
data.insert("license", to_json(dunce::canonicalize(license)?));
204203
}
205204
if let Some(installer_icon) = &nsis.installer_icon {
206205
data.insert(
207206
"installer_icon",
208-
to_json(remove_unc_lossy(installer_icon.canonicalize()?)),
207+
to_json(dunce::canonicalize(installer_icon)?),
209208
);
210209
}
211210
if let Some(header_image) = &nsis.header_image {
212-
data.insert(
213-
"header_image",
214-
to_json(remove_unc_lossy(header_image.canonicalize()?)),
215-
);
211+
data.insert("header_image", to_json(dunce::canonicalize(header_image)?));
216212
}
217213
if let Some(sidebar_image) = &nsis.sidebar_image {
218214
data.insert(
219215
"sidebar_image",
220-
to_json(remove_unc_lossy(sidebar_image.canonicalize()?)),
216+
to_json(dunce::canonicalize(sidebar_image)?),
221217
);
222218
}
223219

@@ -234,7 +230,28 @@ fn build_nsis_app_installer(
234230
NSISInstallerMode::Both => "both",
235231
}),
236232
);
233+
234+
let languages_data = languages
235+
.iter()
236+
.filter_map(|lang| {
237+
if let Some(data) = get_lang_data(lang, custom_language_files.as_ref()) {
238+
Some(data)
239+
} else {
240+
log::warn!("Custom tauri messages for {lang} are not translated.\nIf it is a valid language listed on <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files>, please open a Tauri feature request\n or you can provide a custom language file for it in `tauri.conf.json > tauri > bundle > windows > nsis > custom_language_files`");
241+
None
242+
}
243+
}).collect::<Vec<_>>();
244+
237245
data.insert("languages", to_json(languages.clone()));
246+
data.insert(
247+
"language_files",
248+
to_json(
249+
languages_data
250+
.iter()
251+
.map(|d| d.0.clone())
252+
.collect::<Vec<_>>(),
253+
),
254+
);
238255

239256
let main_binary = settings
240257
.binaries()
@@ -380,17 +397,12 @@ fn build_nsis_app_installer(
380397
.0,
381398
)?;
382399

383-
for lang in languages {
384-
if let Some((data, encoding)) = get_lang_data(&lang) {
400+
for (lang, data) in languages_data.iter() {
401+
if let Some((content, encoding)) = data {
385402
write(
386403
output_path.join(lang).with_extension("nsh"),
387-
encoding.encode(data).0,
404+
encoding.encode(content).0,
388405
)?;
389-
} else {
390-
return Err(
391-
anyhow::anyhow!("Language {lang} not implemented. If it is a valid language listed on <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files>, please open a Tauri feature request")
392-
.into()
393-
);
394406
}
395407
}
396408

@@ -451,7 +463,7 @@ fn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {
451463

452464
for src in settings.resource_files() {
453465
let src = src?;
454-
let resource_path = remove_unc_lossy(cwd.join(&src).canonicalize()?);
466+
let resource_path = dunce::canonicalize(cwd.join(&src))?;
455467

456468
// In some glob resource paths like `assets/**/*` a file might appear twice
457469
// because the `tauri_utils::resources::ResourcePaths` iterator also reads a directory
@@ -485,7 +497,7 @@ fn generate_binaries_data(settings: &Settings) -> crate::Result<BinariesMap> {
485497

486498
for src in settings.external_binaries() {
487499
let src = src?;
488-
let binary_path = remove_unc_lossy(cwd.join(&src).canonicalize()?);
500+
let binary_path = dunce::canonicalize(cwd.join(&src))?;
489501
let dest_filename = src
490502
.file_name()
491503
.expect("failed to extract external binary filename")
@@ -511,42 +523,90 @@ fn generate_binaries_data(settings: &Settings) -> crate::Result<BinariesMap> {
511523
Ok(binaries)
512524
}
513525

514-
fn get_lang_data(lang: &str) -> Option<(&'static str, &'static encoding_rs::Encoding)> {
526+
fn get_lang_data(
527+
lang: &str,
528+
custom_lang_files: Option<&HashMap<String, PathBuf>>,
529+
) -> Option<(
530+
String,
531+
Option<(&'static str, &'static encoding_rs::Encoding)>,
532+
)> {
515533
use encoding_rs::*;
534+
535+
if let Some(path) = custom_lang_files.and_then(|h| h.get(lang)) {
536+
return Some((
537+
dunce::canonicalize(path)
538+
.unwrap()
539+
.to_string_lossy()
540+
.to_string(),
541+
None,
542+
));
543+
}
544+
545+
let lang_file = format!("{lang}.nsh");
516546
match lang.to_lowercase().as_str() {
517547
"arabic" => Some((
518-
include_str!("./templates/nsis-languages/Arabic.nsh"),
519-
UTF_16LE,
548+
lang_file,
549+
Some((
550+
include_str!("./templates/nsis-languages/Arabic.nsh"),
551+
UTF_16LE,
552+
)),
553+
)),
554+
"dutch" => Some((
555+
lang_file,
556+
Some((include_str!("./templates/nsis-languages/Dutch.nsh"), UTF_8)),
520557
)),
521-
"dutch" => Some((include_str!("./templates/nsis-languages/Dutch.nsh"), UTF_8)),
522558
"english" => Some((
523-
include_str!("./templates/nsis-languages/English.nsh"),
524-
UTF_8,
559+
lang_file,
560+
Some((
561+
include_str!("./templates/nsis-languages/English.nsh"),
562+
UTF_8,
563+
)),
525564
)),
526565
"japanese" => Some((
527-
include_str!("./templates/nsis-languages/Japanese.nsh"),
528-
UTF_8,
566+
lang_file,
567+
Some((
568+
include_str!("./templates/nsis-languages/Japanese.nsh"),
569+
UTF_8,
570+
)),
529571
)),
530572
"portuguesebr" => Some((
531-
include_str!("./templates/nsis-languages/PortugueseBR.nsh"),
532-
UTF_8,
573+
lang_file,
574+
Some((
575+
include_str!("./templates/nsis-languages/PortugueseBR.nsh"),
576+
UTF_8,
577+
)),
533578
)),
534579
"tradchinese" => Some((
535-
include_str!("./templates/nsis-languages/TradChinese.nsh"),
536-
UTF_8,
580+
lang_file,
581+
Some((
582+
include_str!("./templates/nsis-languages/TradChinese.nsh"),
583+
UTF_8,
584+
)),
537585
)),
538586
"simpchinese" => Some((
539-
include_str!("./templates/nsis-languages/SimpChinese.nsh"),
540-
UTF_8,
587+
lang_file,
588+
Some((
589+
include_str!("./templates/nsis-languages/SimpChinese.nsh"),
590+
UTF_8,
591+
)),
592+
)),
593+
"french" => Some((
594+
lang_file,
595+
Some((include_str!("./templates/nsis-languages/French.nsh"), UTF_8)),
541596
)),
542-
"french" => Some((include_str!("./templates/nsis-languages/French.nsh"), UTF_8)),
543597
"spanish" => Some((
544-
include_str!("./templates/nsis-languages/Spanish.nsh"),
545-
UTF_8,
598+
lang_file,
599+
Some((
600+
include_str!("./templates/nsis-languages/Spanish.nsh"),
601+
UTF_8,
602+
)),
546603
)),
547604
"spanishinternational" => Some((
548-
include_str!("./templates/nsis-languages/SpanishInternational.nsh"),
549-
UTF_8,
605+
lang_file,
606+
Some((
607+
include_str!("./templates/nsis-languages/SpanishInternational.nsh"),
608+
UTF_8,
609+
)),
550610
)),
551611
_ => None,
552612
}

tooling/bundler/src/bundle/windows/templates/installer.nsi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,8 @@ FunctionEnd
272272
!insertmacro MUI_LANGUAGE "{{this}}"
273273
{{/each}}
274274
!insertmacro MUI_RESERVEFILE_LANGDLL
275-
{{#each languages}}
276-
!include "{{this}}.nsh"
275+
{{#each language_files}}
276+
!include "{{this}}"
277277
{{/each}}
278278

279279
Function .onInit

tooling/bundler/src/bundle/windows/util.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use std::{
66
fs::{create_dir_all, File},
77
io::{Cursor, Read, Write},
8-
path::{Path, PathBuf},
8+
path::Path,
99
};
1010

1111
use log::info;
@@ -74,7 +74,7 @@ fn verify(data: &Vec<u8>, hash: &str, mut hasher: impl Digest) -> crate::Result<
7474
}
7575

7676
#[cfg(target_os = "windows")]
77-
pub fn try_sign(file_path: &PathBuf, settings: &Settings) -> crate::Result<()> {
77+
pub fn try_sign(file_path: &std::path::PathBuf, settings: &Settings) -> crate::Result<()> {
7878
use tauri_utils::display_path;
7979

8080
if let Some(certificate_thumbprint) = settings.windows().certificate_thumbprint.as_ref() {
@@ -134,7 +134,3 @@ pub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {
134134

135135
Ok(())
136136
}
137-
138-
pub fn remove_unc_lossy<P: AsRef<Path>>(p: P) -> PathBuf {
139-
PathBuf::from(p.as_ref().to_string_lossy().replacen(r"\\?\", "", 1))
140-
}

tooling/cli/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tooling/cli/schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,16 @@
17151715
"type": "string"
17161716
}
17171717
},
1718+
"customLanguageFiles": {
1719+
"description": "A key-value pair where the key is the language and the value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\n\nSee <https://github.com/tauri-apps/tauri/blob/dev/tooling/bundler/src/bundle/windows/templates/nsis-languages/English.nsh> for an example `.nsh` file.\n\n**Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,",
1720+
"type": [
1721+
"object",
1722+
"null"
1723+
],
1724+
"additionalProperties": {
1725+
"type": "string"
1726+
}
1727+
},
17181728
"displayLanguageSelector": {
17191729
"description": "Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.",
17201730
"default": false,

0 commit comments

Comments
 (0)