Skip to content

Commit

Permalink
WIP(nsis): translate custom strings
Browse files Browse the repository at this point in the history
  • Loading branch information
develar committed Apr 6, 2017
1 parent d58d323 commit 3d25690
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 73 deletions.
4 changes: 4 additions & 0 deletions docs/Donations.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Support us with a donation and help us continue our activities. Donation can be

[Donate with PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W6V79R2RGCCHL)

## Credit Card

[Donate with Gratipay](https://gratipay.com/electron-builder/)

## Bitcoin

[Donate with Bitcoin](https://cex.io/#/modal/donation/pid/DP100018863)
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"ajv": "^5.0.4-beta.2",
"ajv-keywords": "^2.0.1-beta.2",
"archiver": "^1.3.0",
"aws-sdk": "^2.37.0",
"aws-sdk": "^2.39.0",
"bluebird-lst": "^1.0.2",
"chalk": "^1.1.3",
"chromium-pickle-js": "^0.2.0",
Expand All @@ -44,7 +44,7 @@
"ini": "^1.3.4",
"is-ci": "^1.0.10",
"isbinaryfile": "^3.0.2",
"js-yaml": "^3.8.2",
"js-yaml": "^3.8.3",
"mime": "^1.3.4",
"minimatch": "^3.0.3",
"node-emoji": "^1.5.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"hosted-git-info": "^2.4.1",
"is-ci": "^1.0.10",
"isbinaryfile": "^3.0.2",
"js-yaml": "^3.8.2",
"js-yaml": "^3.8.3",
"minimatch": "^3.0.3",
"node-forge": "^0.7.1",
"normalize-package-data": "^2.3.6",
Expand Down
142 changes: 88 additions & 54 deletions packages/electron-builder/src/targets/nsis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getBinFromBintray } from "electron-builder-util/out/binDownload"
import { copyFile } from "electron-builder-util/out/fs"
import { log, subTask, warn } from "electron-builder-util/out/log"
import { readFile, unlink } from "fs-extra-p"
import { safeLoad } from "js-yaml"
import * as path from "path"
import sanitizeFileName from "sanitize-filename"
import { v5 as uuid5 } from "uuid-1345"
Expand All @@ -14,9 +15,12 @@ import { getSignVendorPath } from "../windowsCodeSign"
import { WinPackager } from "../winPackager"
import { archive } from "./archive"

// noinspection SpellCheckingInspection
const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3"

// noinspection SpellCheckingInspection
const nsisPathPromise = getBinFromBintray("nsis", "3.0.1.10", "302a8adebf0b553f74cddd494154a586719ff9d4767e94d8a76547a9bb06200c")
// noinspection SpellCheckingInspection
const nsisResourcePathPromise = getBinFromBintray("nsis-resources", "3.0.0", "cde0e77b249e29d74250bf006aa355d3e02b32226e1c6431fb48facae41d8a7e")

const USE_NSIS_BUILT_IN_COMPRESSOR = false
Expand Down Expand Up @@ -59,6 +63,7 @@ export default class NsisTarget extends Target {
return await archive(packager.config.compression, format, archiveFile, appOutDir, true)
}

// noinspection JSUnusedGlobalSymbols
async finishBuild(): Promise<any> {
log("Building NSIS installer")
const filesToDelete: Array<string> = []
Expand Down Expand Up @@ -365,63 +370,30 @@ export default class NsisTarget extends Target {
scriptHeader += `!addplugindir /${pluginArch} "${path.join(packager.buildResourcesDir, pluginArch)}"\n`

// http://stackoverflow.com/questions/997456/nsis-license-file-based-on-language-selection
let licensePage: Array<string> | null
const license = await packager.getResource(this.options.license, "license.rtf", "license.txt", "eula.rtf", "eula.txt", "LICENSE.rtf", "LICENSE.txt", "EULA.rtf", "EULA.txt", "LICENSE.RTF", "LICENSE.TXT", "EULA.RTF", "EULA.TXT")
if (license == null) {
const licenseFiles = (await packager.resourceList)
.filter(it => {
const name = it.toLowerCase()
return (name.startsWith("license_") || name.startsWith("eula_")) && (name.endsWith(".rtf") || name.endsWith(".txt"))
})

if (licenseFiles.length === 0) {
licensePage = null
const licensePage = await this.computeLicensePage()
if (licensePage != null) {
scriptHeader += createMacro("licensePage", licensePage)
}

const messages = safeLoad(await readFile(path.join(__dirname, "..", "..", "templates", "nsis", "messages.yml"), "utf-8"))
const langs: Array<string> = []
for (const messageId of Object.keys(messages)) {
const langToTranslations = messages[messageId]
const unspecifiedLangs = new Set(bundledLanguages)
for (const lang of Object.keys(langToTranslations)) {
const langWithRegion = toLangWithRegion(lang)
langs.push(`LangString ${messageId} ${lcid[langWithRegion]} "${langToTranslations[lang].replace(/\n/g, "$\\r$\\n")}"`)
unspecifiedLangs.delete(langWithRegion)
}
else {
licensePage = []
const unspecifiedLangs = new Set(bundledLanguages)

let defaultFile: string | null = null
const sortedFiles = licenseFiles.sort((a, b) => {
const aW = a.includes("_en") ? 0 : 100
const bW = b.includes("_en") ? 0 : 100
return aW === bW ? a.localeCompare(b) : aW - bW
})
for (const file of sortedFiles) {
let lang = file.match(/_([^.]+)\./)![1]
let langWithRegion
if (lang.includes("_")) {
langWithRegion = lang
}
else {
lang = lang.toLowerCase()
langWithRegion = langToLangWithRegion.get(lang)
if (langWithRegion == null) {
langWithRegion = `${lang}_${lang.toUpperCase()}`
}
}

unspecifiedLangs.delete(langWithRegion)
const fullFile = path.join(packager.buildResourcesDir, file)
if (defaultFile == null) {
defaultFile = fullFile
}
licensePage.push(`LicenseLangString MUILicense ${lcid[langWithRegion] || lang} "${fullFile}"`)
}

for (const l of unspecifiedLangs) {
licensePage.push(`LicenseLangString MUILicense ${lcid[l]} "${defaultFile}"`)
}

licensePage.push('!insertmacro MUI_PAGE_LICENSE "$(MUILicense)"')

const defaultTranslation = langToTranslations["en"].replace(/\n/g, "$\\r$\\n")
for (const langWithRegion of unspecifiedLangs) {
langs.push(`LangString ${messageId} ${lcid[langWithRegion]} "${defaultTranslation}"`)
}
}
else {
licensePage = [`!insertmacro MUI_PAGE_LICENSE "${license}"`]
}

if (licensePage != null) {
scriptHeader += createMacro("licensePage", licensePage)

if (langs.length > 0) {
scriptHeader += "\n" + langs.join("\n") + "\n\n"
}

if (this.isPortable) {
Expand Down Expand Up @@ -476,6 +448,68 @@ export default class NsisTarget extends Target {

return scriptHeader + originalScript
}

private async computeLicensePage(): Promise<Array<string> | null> {
const packager = this.packager

const license = await packager.getResource(this.options.license, "license.rtf", "license.txt", "eula.rtf", "eula.txt", "LICENSE.rtf", "LICENSE.txt", "EULA.rtf", "EULA.txt", "LICENSE.RTF", "LICENSE.TXT", "EULA.RTF", "EULA.TXT")
if (license != null) {
return [`!insertmacro MUI_PAGE_LICENSE "${license}"`]
}

const licenseFiles = (await packager.resourceList)
.filter(it => {
const name = it.toLowerCase()
return (name.startsWith("license_") || name.startsWith("eula_")) && (name.endsWith(".rtf") || name.endsWith(".txt"))
})

if (licenseFiles.length === 0) {
return null
}

const licensePage: Array<string> = []
const unspecifiedLangs = new Set(bundledLanguages)

let defaultFile: string | null = null
const sortedFiles = licenseFiles.sort((a, b) => {
const aW = a.includes("_en") ? 0 : 100
const bW = b.includes("_en") ? 0 : 100
return aW === bW ? a.localeCompare(b) : aW - bW
})
for (const file of sortedFiles) {
let lang = file.match(/_([^.]+)\./)![1]
let langWithRegion
if (lang.includes("_")) {
langWithRegion = lang
}
else {
lang = lang.toLowerCase()
langWithRegion = toLangWithRegion(lang)
}

unspecifiedLangs.delete(langWithRegion)
const fullFile = path.join(packager.buildResourcesDir, file)
if (defaultFile == null) {
defaultFile = fullFile
}
licensePage.push(`LicenseLangString MUILicense ${lcid[langWithRegion] || lang} "${fullFile}"`)
}

for (const l of unspecifiedLangs) {
licensePage.push(`LicenseLangString MUILicense ${lcid[l]} "${defaultFile}"`)
}

licensePage.push('!insertmacro MUI_PAGE_LICENSE "$(MUILicense)"')
return licensePage
}
}

function toLangWithRegion(lang: string): string {
let langWithRegion = langToLangWithRegion.get(lang)
if (langWithRegion == null) {
langWithRegion = `${lang}_${lang.toUpperCase()}`
}
return langWithRegion
}

function createMacro(name: string, lines: Array<string>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@
launch:
!macroend

!macro CHECK_APP_RUNNING MODE
!macro CHECK_APP_RUNNING
${GetProcessInfo} 0 $0 $1 $2 $3 $4
${if} $3 != "${APP_EXECUTABLE_FILENAME}"
${nsProcess::FindProcess} "${APP_EXECUTABLE_FILENAME}" $R0
${If} $R0 == 0
${if} ${Updated}
Goto doStopProcess
${endif}
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "${PRODUCT_NAME} is running. $\r$\nClick OK to close it and continue with ${MODE}." /SD IDOK IDOK doStopProcess
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "$(appRunning)" /SD IDOK IDOK doStopProcess
Quit
doStopProcess:
DetailPrint "Closing running ${PRODUCT_NAME} ..."
Expand Down
4 changes: 2 additions & 2 deletions packages/electron-builder/templates/nsis/common.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Name "${PRODUCT_NAME}"

!macro check64BitAndSetRegView
${IfNot} ${AtLeastWin7}
MessageBox MB_OK "Windows 7 and above is required"
MessageBox MB_OK "$(win7Required)"
Quit
${EndIf}

Expand All @@ -23,7 +23,7 @@ Name "${PRODUCT_NAME}"
SetRegView 64
${Else}
!ifndef APP_32
MessageBox MB_OK|MB_ICONEXCLAMATION "64-bit Windows is required"
MessageBox MB_OK|MB_ICONEXCLAMATION "$(x64WinRequired)"
Quit
!endif
${EndIf}
Expand Down
4 changes: 2 additions & 2 deletions packages/electron-builder/templates/nsis/installSection.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ ${IfNot} ${Silent}
${endif}

!ifdef ONE_CLICK
!insertmacro CHECK_APP_RUNNING "install"
!insertmacro CHECK_APP_RUNNING
!else
${IfNot} ${UAC_IsInnerInstance}
!insertmacro CHECK_APP_RUNNING "install"
!insertmacro CHECK_APP_RUNNING
${endif}
!endif

Expand Down
9 changes: 9 additions & 0 deletions packages/electron-builder/templates/nsis/messages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
win7Required:
en: "Windows 7 and above is required"
ru: "Требуется Windows 7 и выше"
x64WinRequired:
en: "64-bit Windows is required"
ru: "Требуется Windows 64-bit"
appRunning:
en: "${PRODUCT_NAME} is running.\nClick OK to close it."
ru: "Приложение ${PRODUCT_NAME} уже запущено.\nНажмите OK для закрытия."
4 changes: 2 additions & 2 deletions packages/electron-builder/templates/nsis/uninstaller.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Function un.onInit
Quit

# one-click installer executes uninstall section in the silent mode, but we must show message dialog if silent mode was not explicitly set by user (using /S flag)
!insertmacro CHECK_APP_RUNNING "uninstall"
!insertmacro CHECK_APP_RUNNING
SetSilent silent
!endif
${endIf}
Expand All @@ -22,7 +22,7 @@ FunctionEnd
Section "un.install"
!ifndef ONE_CLICK
# for boring installer we check it here to show progress
!insertmacro CHECK_APP_RUNNING "uninstall"
!insertmacro CHECK_APP_RUNNING
!endif

!insertmacro setLinkVars
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-publisher-s3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
],
"dependencies": {
"fs-extra-p": "^4.1.0",
"aws-sdk": "^2.37.0",
"aws-sdk": "^2.39.0",
"mime": "^1.3.4",
"electron-publish": "~0.0.0-semantic-release",
"electron-builder-util": "~0.0.0-semantic-release"
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-updater/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"dependencies": {
"bluebird-lst": "^1.0.2",
"fs-extra-p": "^4.1.0",
"js-yaml": "^3.8.2",
"js-yaml": "^3.8.3",
"semver": "^5.3.0",
"source-map-support": "^0.4.14",
"electron-builder-http": "~0.0.0-semantic-release"
Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"

aws-sdk@^2.37.0:
version "2.38.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.38.0.tgz#b2f153e504b1f197cb15f997a24a2bf8ef5bb70d"
aws-sdk@^2.39.0:
version "2.39.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.39.0.tgz#b8c7e7b17c5b807a529ca1c3a3b059f371e0a866"
dependencies:
buffer "4.9.1"
crypto-browserify "1.0.9"
Expand Down Expand Up @@ -1923,9 +1923,9 @@ js-tokens@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"

js-yaml@^3.4.2, js-yaml@^3.7.0, js-yaml@^3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.2.tgz#02d3e2c0f6beab20248d412c352203827d786721"
js-yaml@^3.4.2, js-yaml@^3.7.0, js-yaml@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.3.tgz#33a05ec481c850c8875929166fe1beb61c728766"
dependencies:
argparse "^1.0.7"
esprima "^3.1.1"
Expand Down

0 comments on commit 3d25690

Please sign in to comment.