localized strings from js resource files

The tool delivered to localize strings apply only on files named xxx.strings but, as nothing is really simple with Apple, some strings are stored in files named yyy.js.
In High Sierra there are several sets of resources using this format:

Macintosh HD:Applications:Safari.app: table:localizedStrings.js
Macintosh HD:Library:Widgets:Calculator.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:Calendar.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:Contacts.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:Dictionary.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:Stickies.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:Stocks.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:Unit Converter.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:Weather.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:Web Clip.wdgt: table:localizedStrings.js
Macintosh HD:Library:Widgets:World Clock.wdgt: table:localizedStrings.js
Macintosh HD:System:Library:CoreServices:HelpViewer.app: table:localizedStrings.js
Macintosh HD:System:Library:Frameworks:WebKit.framework:Versions:A:Frameworks:WebCore.framework: table:mediaControlsLocalizedStrings.js
Macintosh HD:System:Library:Frameworks:WebKit.framework:Versions:A:Frameworks:WebCore.framework: table:modern-media-controls-localized-strings.js
Macintosh HD:System:Library:PrivateFrameworks:Safari.framework: table:localizedStrings.js
Macintosh HD:System:Library:PrivateFrameworks:SafariShared.framework: table:WBSLocalizedStrings.js don’t ask what need for this ‘special’ name
Macintosh HD:System:Library:PrivateFrameworks:WebInspectorUI.framework: table:localizedStrings.js

Macintosh HD:Library:Widgets:Movies.wdgt: table:localizedStrings.js
For this late one, in High Sierra there is only an English file so it’s not really useful.

As imagination of Apple engineers has no limit, they use (at least) four different syntaxes and two encodings utf8 or ut16.

Below is a script supposed to extract localized strings from these different formats.

----------------------------------------------------------------
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
----------------------------------------------------------------

-- localize strings defined in yyy.js files

-- use quote & ":"
set key_loc1 to my shared((path to library folder from system domain as text) & "Frameworks:WebKit.framework:Versions:A:Frameworks:WebCore.framework:Versions:A:Resources:", "Display Picture in Picture")
-- use  "':"
set key_loc2 to my shared((path to library folder from system domain as text) & "Frameworks:WebKit.framework:Versions:A:Frameworks:WebCore.framework:Versions:A:Resources:", "This video is playing in picture in picture.")
-- use  quote & "]"
set key_loc3 to my shared((path to applications folder as text) & "Safari.app:Contents:Resources:", "Updates are available for one or more of your extensions. To install an update, click its Update button.")
-- use   "']"
set key_loc4 to my shared((path to library folder from local domain as text) & "Widgets:Contacts.wdgt:", "No Matching Cards")
-- use   "']"
set key_loc5 to my shared((path to library folder from local domain as text) & "Widgets:Contacts.wdgt:", "McCoy Tyner")

{key_loc1, key_loc2, key_loc3, key_loc4, key_loc5}

#=====

on shared(begPath, theKey)
	
	if begPath contains ":WebKit.framework:" then
		set tableNames to {"modern-media-controls-localized-strings.js", "mediaControlsLocalizedStrings.js"}
	else if begPath contains ":Safari.framework:" then
		set tableNames to {"WBSLocalizedStrings.js"}
	else
		set tableNames to {"localizedStrings.js"}
	end if
	
	repeat with tableName in tableNames
		set key_loc to (my localize:theKey fromTable:tableName inBundle:begPath)
		if key_loc is not missing value then exit repeat
	end repeat
	if key_loc is missing value then set key_loc to "the key “" & theKey & "” is unavaible in this file!"
	return key_loc
end shared

#=====

on localize:the_Key fromTable:theTable inBundle:begPath
	set thisLocale to current application's NSLocale's currentLocale()
	set langX to thisLocale's localeIdentifier as string --> "fr_FR"
	set lang2 to text 1 thru 2 of langX
	if langX starts with "zh_Hans" then # "zh-Hans"
		set path2lproj to begPath & "zh_CN.lproj"
	else if langX starts with "zh_Hant" then # "zh-Hant"
		set path2lproj to begPath & "zh_TW.lproj"
	else if langX starts with "pt_PT" then -- "pt_PT"
		set path2lproj to begPath & "pt_PT.lproj"
	else if langX starts with "pt_BR" then -- "pt_BR"
		set path2lproj to begPath & "pt_BR.lproj"
	else if lang2 is in {"ar", "ca", "cs", "da", "el", "fi", "he", "hr", "hu", "id", "ko", "ms", "pl", "pt", "ro", "ru", "sk", "sv", "th", "tr", "uk", "vi"} then
		set path2lproj to begPath & lang2 & ".lproj"
	else if lang2 is "de" then # {"de", "de-AT", "de-CH"}
		set path2lproj to begPath & "German.lproj"
	else if lang2 is "en" then # {"en", "en_AU", "en_CA", "en_GB", "en_US"}
		set path2lproj to begPath & "English.lproj"
	else if lang2 is "es" then # {"es", "es_419", "es_ES", "es_MX"}
		set path2lproj to begPath & "Spanish.lproj"
	else if lang2 is "fr" then # {"fr_FR", "fr_CA", "fr_CH"}
		tell application "System Events"
			if exists folder (begPath & "French.lproj") then
				set path2lproj to begPath & "French.lproj"
			else
				set path2lproj to begPath & "fr.lproj"
			end if
		end tell
	else if lang2 is "it" then
		set path2lproj to begPath & "Italian.lproj"
	else if lang2 is "ja" then
		set path2lproj to begPath & "Japanese.lproj"
	else if lang2 is "nb" then
		set path2lproj to begPath & "no.lproj"
	else if lang2 is "nl" then # {"nl", "nl_BE"}
		set path2lproj to begPath & "Dutch.lproj"
	end if
	
	set theFile to (path2lproj & ":" & theTable) as «class furl»
	
	try
		set itsText to read theFile as «class utf8»
	on error
		set itsText to read theFile as «class ut16»
	end try
	
	set theTable to contents of theTable -- must deref
	if theTable is "modern-media-controls-localized-strings.js" then
		set theDelim to quote & the_Key & quote & ":"
	else if theTable is "mediaControlsLocalizedStrings.js" then
		set theDelim to "'" & the_Key & "':"
	else
		if itsText contains "';" then
			set theDelim to "['" & the_Key & "']"
		else
			set theDelim to "[" & quote & the_Key & quote & "]"
		end if
	end if
	-- For info, in front of theDelim we may have "localizedStrings", "localizedControls","localizedFonts", "localizedContinentNames" or "localizedCityNames"
	
	if itsText does not contain theDelim then return missing value
	
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, theDelim}
	set l to text items of itsText
	set AppleScript's text item delimiters to oTIDs
	set key_loc to paragraph 1 of (item 2 of l)
	
	if theTable is "modern-media-controls-localized-strings.js" then
		set dels to {" " & quote, quote & ","}
	else if theTable is "mediaControlsLocalizedStrings.js" then
		set dels to {" '", "',"}
	else
		if key_loc contains "';" then
			set dels to {" '", "';"}
		else
			set dels to {" " & quote, quote & ";"}
		end if
	end if
	
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, dels}
	set l to text items of key_loc
	set AppleScript's text item delimiters to oTIDs
	return (item 2 of l) as string
end localize:fromTable:inBundle:

#=====

Maybe, one day, I would convert this code in a library so it would be easier to use it in our scripts.

Maybe too, I would scan Mojave and/or Catalina to check if they use the yyy.js format in other lprojs.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 10 mars 2020 17:48:07

After collecting infos about Mojave and Catalina, I updated my script.
As far as I know, at this time the only remaining problem is the way to search in the correct Chinese resources.

----------------------------------------------------------------
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
----------------------------------------------------------------

-- localize strings defined in yyy.js files
property testEnglish : true
-- true = force the scanned language to English
-- false = scan the current language

-- use quote & ":"
set key_loc1 to my shared((path to library folder from system domain as text) & "Frameworks:WebKit.framework:Versions:A:Frameworks:WebCore.framework:Versions:A:Resources:", "Display Picture in Picture")

-- use  "':"
set key_loc2 to my shared((path to library folder from system domain as text) & "Frameworks:WebKit.framework:Versions:A:Frameworks:WebCore.framework:Versions:A:Resources:", "This video is playing in picture in picture.")

-- use  quote & "]"
set key_loc3 to my shared((path to applications folder as text) & "Safari.app:Contents:Resources:", "Updates are available for one or more of your extensions. To install an update, click its Update button.")

-- use   "']"
set key_loc4 to my shared((path to library folder from local domain as text) & "Widgets:Contacts.wdgt:", "No Matching Cards")

-- use   "']"
set key_loc5 to my shared((path to library folder from local domain as text) & "Widgets:Contacts.wdgt:", "McCoy Tyner")

{key_loc1, key_loc2, key_loc3, key_loc4, key_loc5}

#=====

on shared(begPath, theKey)
	
	if begPath contains ":WebKit.framework:" then
		set tableNames to {"modern-media-controls-localized-strings.js", "mediaControlsLocalizedStrings.js"}
	else if begPath contains ":Safari.framework:" then
		set tableNames to {"WBSLocalizedStrings.js"} -- what need for this 'special' name ?
	else
		set tableNames to {"localizedStrings.js"}
	end if
	
	repeat with tableName in tableNames
		set key_loc to (my localize:theKey fromTable:tableName inBundle:begPath)
		if key_loc is not missing value then exit repeat
	end repeat
	if key_loc is missing value then set key_loc to "the key “" & theKey & "” is unavaible in this file!"
	return key_loc
end shared

#=====

on localize:the_Key fromTable:theTable inBundle:begPath
	-- set languageAvailables to current application's NSLocale's availableLocaleIdentifiers() as list
	set thisLocale to current application's NSLocale's currentLocale()
	set langX to thisLocale's localeIdentifier as string --> "fr_FR"
	set lang2 to thisLocale's languageCode as text --> "fr"
	if testEnglish then
		set langX to "en_GB" -- used for tests
		set lang2 to "en"
	end if
	
	tell application "System Events"
		if lang2 is in {"ar", "ca", "cs", "da", "el", "fi", "he", "hi", "hr", "hu", "id", "ko", "ms", "pl", "ro", "ru", "sk", "sv", "th", "tr", "uk", "vi"} then
			set path2lproj to begPath & lang2 & ".lproj"
			
		else if lang2 is "de" then
			if (langX starts with "de_AT") and (exists folder (begPath & "de_AT.lproj")) then
				set path2lproj to begPath & "de_AT.lproj"
			else if (langX starts with "de_CH") and (exists folder (begPath & "de_CH.lproj")) then
				set path2lproj to begPath & "de_CH.lproj"
			else if exists folder (begPath & "de.lproj") then
				set path2lproj to begPath & "dl.lproj"
			else
				set path2lproj to begPath & "German.lproj" -- used in High Sierra or Mojave, not used in Catalina
			end if
			
		else if lang2 is "en" then
			if (langX starts with "en_AU") and (exists folder (begPath & "en_AU.lproj")) then
				set path2lproj to begPath & "en_AU.lproj.lproj"
			else if (langX starts with "en_CA") and (exists folder (begPath & "en_CA.lproj")) then
				set path2lproj to begPath & "en_CA.lproj"
			else if (langX starts with "en_GB") and (exists folder (begPath & "en_GB.lproj")) then
				set path2lproj to begPath & "en_GB.lproj"
			else if (langX starts with "en_US") and (exists folder (begPath & "en_US.lproj")) then
				set path2lproj to begPath & "en_US.lproj"
			else if exists folder (begPath & "en.lproj") then
				set path2lproj to begPath & "en.lproj"
			else
				set path2lproj to begPath & "English.lproj" -- used in High Sierra or Mojave, not used in Catalina
			end if

		else if lang2 is "es" then
			if (langX starts with "es_41") and (exists folder (begPath & "es_419.lproj")) then
				set path2lproj to begPath & "es_419.lproj"
			else if (langX starts with "es_ES") and (exists folder (begPath & "es_ES.lproj")) then
				set path2lproj to begPath & "es_ES.lproj"
			else if (langX starts with "es_MX") and (exists folder (begPath & "es_MX.lproj")) then
				set path2lproj to begPath & "es_MX.lproj"
			else if exists folder (begPath & "es.lproj") then
				set path2lproj to begPath & "es.lproj"
			else
				set path2lproj to begPath & "Spanish.lproj" -- used in High Sierra or Mojave, not used in Catalina
			end if

		else if lang2 is "fr" then
			if (langX starts with "fr_CA") and (exists folder (begPath & "fr_CA.lproj")) then
				set path2lproj to begPath & "fr_CA.lproj"
			else if (langX starts with "fr_CH") and (exists folder (begPath & "fr_CH.lproj")) then
				set path2lproj to begPath & "fr_CH.lproj"
			else if exists folder (begPath & "fr.lproj") then
				set path2lproj to begPath & "fr.lproj"
			else
				set path2lproj to begPath & "French.lproj" -- used in High Sierra or Mojave, not used in Catalina
			end if

		else if lang2 is "it" then
			if exists folder (begPath & "it.lproj") then
				set path2lproj to begPath & "it.lproj"
			else
				set path2lproj to begPath & "Italian.lproj" -- used in High Sierra or Mojave, not used in Catalina
			end if

		else if lang2 is "ja" then
			if exists folder (begPath & "ja.lproj") then
				set path2lproj to begPath & "ja.lproj"
			else
				set path2lproj to begPath & "Japanese.lproj" -- used in High Sierra or Mojave, not used in Catalina
			end if

		else if ((lang2 is "nb") or (lang2 is "no")) then -- I'm not sure that we may get lang2 = "no"
			set path2lproj to begPath & "no.lproj"

		else if lang2 is "nl" then # {"nl", "nl_BE"}
			if (langX starts with "nl_BE") and (exists folder (begPath & "nl_BE.lproj")) then
				set path2lproj to begPath & "nl_BE.lproj"
			else if exists folder (begPath & "nl.lproj") then
				set path2lproj to begPath & "nl.lproj"
			else
				set path2lproj to begPath & "Dutch.lproj" -- used in High Sierra or Mojave, not used in Catalina
			end if

		else if lang2 is "pt" then
			if (langX starts with "pt_PT") and (exists folder (begPath & "pt_PT.lproj")) then
				set path2lproj to begPath & "pt_PT.lproj"
			else if (langX starts with "pt_BR") and (exists folder (begPath & "pt_BR.lproj")) then
				set path2lproj to begPath & "pt_BR.lproj"
			else
				set path2lproj to begPath & "pt.lproj"
			end if

		else if lang2 is "yue" then
			if langX starts with "yue_Hans" and (exists folder (begPath & "yue_CN.lproj")) then
				set path2lproj to begPath & "yue_CN.lproj"
			else if langX starts with "yue_Hant" and (exists folder (begPath & "yue_CN.lproj")) then
				set path2lproj to begPath & "yue_CN.lproj"
			else
				error "Which lproj is linked to the locale " & langX & " with your System ?"
			end if

		else if lang2 is "zh" then
			if langX contains "_CN" then
				set path2lproj to begPath & "zh_CN.lproj" -- used in High Sierra, Mojave or Catalina
			else if langX is "zh_Hant_TW" then
				set path2lproj to begPath & "zh_TW.lproj" -- used in High Sierra, Mojave or Catalina
			else if langX is "zh_Hans_HK" then
				if (exists folder (begPath & "zh_HK.lproj")) then
					set path2lproj to begPath & "zh_HK.lproj" -- not used in High Sierra, used in Mojave or Catalina 
				else
					error "Which lproj is linked to the locale " & langX & " with High Sierra ?"
				end if
			else if langX is "zh_Hant_HK" then
				if (exists folder (begPath & "zh_HK.lproj")) then
					set path2lproj to begPath & "zh_HK.lproj" -- not used in High Sierra, used in Mojave or Catalina
				else
					error "Which lproj is linked to the locale " & langX & " with High Sierra ?"
				end if
			else if langX is "zh_Hans" then
				error "Which lproj is linked to the locale " & langX & " with your System ?"
			else if langX is "zh_Hans_HK" then
				error "Which lproj is linked to the locale " & langX & " with your System ?"
			else if langX is "zh_Hans_MO" then
				error "Which lproj is linked to the locale " & langX & " with your System ?"
			else if langX is "zh_Hans_SG" then
				error "Which lproj is linked to the locale " & langX & " with your System ?"
			else if langX is "zh_Hant" then
				error "Which lproj is linked to the locale " & langX & " with your System ?"
			else if langX is "zh_Hant_MO" then
				error "Which lproj is linked to the locale " & langX & " with your System ?"
			end if
		else
			-- 'unidentified' language, default to English
			if exists folder (begPath & "en.lproj") then
				set path2lproj to begPath & "en.lproj"
			else
				set path2lproj to begPath & "English.lproj" -- used in High Sierra or Mojave, not used in Catalina
			end if
		end if
	end tell
	set theFile to (path2lproj & ":" & theTable) as «class furl»
	
	try
		set itsText to read theFile as «class utf8»
	on error
		set itsText to read theFile as «class ut16»
	end try
	(*
	-- As far as I know there is no multilines string in the js resources
	*)
	if itsText does not contain the_Key then return missing value -- no need to search more if the_Key is not available, possibly as a substring of an item
	set delims to {quote & the_Key & quote, "'" & the_Key & "'"}
	set l to my decoupe(itsText, delims)
	set cntl to count l
	if cntl = 1 then
		set key_loc to missing value -- no match
	else if cntl = 3 then
		set key_loc to the_Key -- we are running on a system using an English idiom
		log ">>>>>>>>>>>> " & lang2
	else
		set maybe to item 2 of l
		set delims to {quote, "'", quote & ",", "',", quote & ";", "';"}
		try
			set key_loc to item 2 of my decoupe(maybe, delims)
		on error errMsg number errNbr
			set key_loc to the_Key & " issued the error #" & errNbr & tab & errMsg
		end try
	end if
	return key_loc
end localize:fromTable:inBundle:

#=====

on decoupe(t, d)
	local oTIDs, l
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set l to text items of t
	set AppleScript's text item delimiters to oTIDs
	return l
end decoupe

#=====

As you may see, I am unable to link correctly the available lprojs - zh_CN.lproj, zh_HK.lproj and zh_TW.lproj under Mojave and Catalina but only zh_CN.lproj and zh_TW.lproj under High Sierra - and the known locale identifiers.
Under High Sierra, these identifiers are : “zh”, “zh_Hans”, “zh_Hans_CN”, “zh_Hans_HK”, “zh_Hans_MO”, “zh_Hans_SG”, “zh_Hant”, “zh_Hant_HK”, “zh_Hant_MO”, “zh_Hant_TW”
and maybe they are other ones in Mojave or Catalina.

Same kind of problem with the locales “yue”, “yue_Hans”, “yue_Hans_CN”, “yue_Hant”, “yue_Hant_HK”.
When I don’t have a logical link, the script issue an error.

Of course I would be interested to learn the way identifiers are distributed among the lprojs.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) samedi 14 mars 2020 16:19:12