Extracting older file versions from DocumentRevision (with a multilingual dialog)

Hello everyone,
I’m not very good at English, so please forgive any strange wording.

While I was localizing l008com’s disk-image-creation script the other day
I suddenly wondered:
“Is it possible to make this dialog multilingual?”

After looking into it, I realized that if you want to show very specific messages,
you do need to prepare your own dictionary.
But then I thought, can’t I reuse some of the dictionaries already provided by the system?
So I tried it.

I made a small script that exports previous file versions from DocumentRevision, and I added multilingual support for the file-selection dialog and the alert shown when the selected file has no revision history.

Of course, the exact messages and button labels depend on the macOS version, but it seems that to some extent you can build simple multilingual messages by reusing existing system dictionaries.

If people think,
“Ah, this is kind of interesting,”
then I’ll be happy — that’s the only reason I’m posting this.

The script is quite verbose and definitely not “modern,” and yes, I’m fully aware of that already—
so please don’t point that part out… :sweat_smile:

Note

If you want to try displaying messages in other languages, please be aware that changing the system language in System Settings may cause FSeventsd to run heavily for a while.

I have prepared some test inputs.
If there is a language you want to try, please modify the script in the section marked “FOR TEST” and run it with your desired settings.

#言語判定 追加事項(Additional items for language)
If you have any advice for better language detection regarding this section, I would be very grateful.
Please feel free to share your suggestions.

#!/usr/bin/osascript
#coding: utf-8
----+----1----+----2----+-----3----+----4----+----5----+----6----+----7--
(*

doGetDocumentRevisions-V100.appliscript

DocumentRevisions-V100に保存されている履歴を参照して
ファイルのバージョンの取得して
取得できれば
過去のバージョンを全て保存します

Refer to the history stored in DocumentRevisions-V100 to get the version of the file.
If you can get it, 
save all past versions.

こちらの記事を参考にしました(I referred to this article)
https://www.macscripter.net/t/deleted-by-mistake-files-recovery-script-needed/68078/8
https://eclecticlight.co/?s=DocumentRevisions-V100
Shane StanleyとHoward Oakleyに感謝します
Thanks to Shane Stanley and Howard Oakley

制限事項
ダイアログメッセージはマルチリンガル対応になっていますが
内容ははmacOS26.1の内容を参照して作成しました
OSのバージョンによっては正しく表示されない可能性があります

Restrictions 
The dialog message is multilingual
but the content was created by referring 
to the contents of macOS 26.1. Depending on the OS version
it is likely not to be displayed correctly.

保存先(Save destination)
保存先は書類フォルダ
The destination is the document folder
Restore『復元』フォルダには全てのバージョン
Keep『残す』フォルダには最新か削除出来ないisDiscardableがFALSEの復元結果が保存されます
In the "Restore" folder, all versions of the "Keep" folder, the latest or undeleteable isDiscardable is TRUE recovery results are saved.

v1初回作成 
v1.1 マルチリンガル対応

License Notice
CC0 1.0 Universal (Public Domain Dedication)

com.cocolog-nifty.quicktimer.icefloe *)
----+----1----+----2----+-----3----+----4----+----5----+----6----+----7--
use AppleScript version "2.8"
use framework "Foundation"
use framework "AppKit"
use scripting additions
property refMe : a reference to current application
property appLocale : (missing value) 

####################
#地域と言語(Region and language)
set appLocale to refMe's NSLocale's currentLocale()
set ocidLocaleID to appLocale's objectForKey:(refMe's NSLocaleIdentifier)
set strLocaleID to ocidLocaleID as text
#ダイアログ用のメッセージ辞書の取得
#(Get a message dictionary for dialogs)
set ocidMsgDict to doGetDialogMsg(appLocale)

####################
#ダイアログ用のメッセージ(Messages for dialogs)
set strNoprevious to (ocidMsgDict's objectForKey:("No previous versions available")) as text
set strOK to (ocidMsgDict's objectForKey:("OK")) as text
set strCancel to (ocidMsgDict's objectForKey:("Cancel")) as text
set strChooseaFile to (ocidMsgDict's objectForKey:("Choose a File")) as text
set strNoResults to (ocidMsgDict's objectForKey:("No Results")) as text
set strChoose to (ocidMsgDict's objectForKey:("Choose")) as text
set strLoadversion to (ocidMsgDict's objectForKey:("Load version")) as text
set strUnable to (ocidMsgDict's objectForKey:("Unable to open version")) as text
set strRestore to (ocidMsgDict's objectForKey:("Restore")) as text
set strKeep to (ocidMsgDict's objectForKey:("Keep")) as text
#	log ocidMsgDict's allKeys() as list

####################
#ダイアログ(Dialogue) 
tell application "Finder"
	set aliasDefaultLocation to (path to desktop folder from user domain) as alias
end tell
set listUTI to {"public.item"} as list

set strPrompt to ("" & return & strChoose & return & strLoadversion & strChooseaFile & return & "") as text
try
	tell application "SystemUIServer"
		activate
		set aliasFilePath to (choose file strChoose with prompt strPrompt default location (aliasDefaultLocation) of type listUTI with invisibles and showing package contents without multiple selections allowed) as alias
	end tell
on error strErrMes number numErrNo
	log strErrMes & numErrNo
	return false
end try
#パス
set strFilePath to (POSIX path of aliasFilePath) as text
set ocidFilePathStr to refMe's NSString's stringWithString:(strFilePath)
set ocidFilePath to ocidFilePathStr's stringByStandardizingPath()
set ocidFilePathURL to refMe's NSURL's fileURLWithPath:(ocidFilePath) isDirectory:(false)
#拡張子やファイル名
#	set ocidExtensionName to ocidFilePathURL's pathExtension()
set ocidFileName to ocidFilePathURL's lastPathComponent()
set ocidBaseFileName to ocidFileName's stringByDeletingPathExtension()

####################
#保存先は書類フォルダ(The destination is the document folder)
set appFileManager to refMe's NSFileManager's defaultManager()
set ocidURLsArray to (appFileManager's URLsForDirectory:(refMe's NSDocumentDirectory) inDomains:(refMe's NSUserDomainMask))
set ocidDocumentDirPathURL to ocidURLsArray's firstObject()

#フォルダ作成オプションchmod=700(make dir  option)
set ocidAttrDict to refMe's NSMutableDictionary's alloc()'s init()
ocidAttrDict's setValue:(448) forKey:(refMe's NSFilePosixPermissions)

####################
#バージョン取得(Version acquisition)
set ocidVerSionArray to refMe's NSFileVersion's otherVersionsOfItemAtURL:(ocidFilePathURL)
#数を数えて(Count the number)
set numCntArray to ocidVerSionArray's |count|()
#取得できなければアラート出して終了(If you can't get it, send an alert and end it.)
if numCntArray = 0 or ocidVerSionArray = (missing value) then
	set strMsg to ("" & strNoprevious & return & strUnable & "") as text
	tell application "System Events"
		activate
		display alert strMsg buttons {strCancel} default button strCancel cancel button strCancel as critical giving up after 5
		return strMsg
	end tell
end if

####################
#取得できたバージョンを順番に(The versions data that were acquired in order)
repeat with ocidVerSionItem in ocidVerSionArray
	#各種値をとって(Take various values)
	set ocidItemURL to ocidVerSionItem's |URL|()
	set ocidItemName to ocidVerSionItem's |localizedName|()
	set ocidItemPID to ocidVerSionItem's |persistentIdentifier|()
	set ocidItemDeviceName to ocidVerSionItem's |localizedNameOfSavingComputer|()
	set ocidItemModDate to ocidVerSionItem's |modificationDate|()
	#保存先フォルダ名用の日付時間 (Date and time for the destination folder name)
	set strFormatStrings to ("yyyyMMdd_hhmmss") as text
	set strDateNo to doGetDateNo(ocidItemModDate, strFormatStrings)
	#履歴の廃棄可能判断(Evaluation of the history’s deletability)
	set boolDiscard to ocidVerSionItem's isDiscardable() as boolean
	#保存先フォルダ作成(Create a destination folder)
	if boolDiscard is true then
		set strSaveDir to ("FileVersion/" & ocidBaseFileName & "/" & strRestore & "/" & strDateNo & "") as text
	else if boolDiscard is false then
		set strSaveDir to ("FileVersion/" & ocidBaseFileName & "/" & strKeep & "/" & strDateNo & "") as text
	end if
	set ocidSaveDirPathURL to (ocidDocumentDirPathURL's URLByAppendingPathComponent:(strSaveDir) isDirectory:(true))
	set listDone to (appFileManager's createDirectoryAtURL:(ocidSaveDirPathURL) withIntermediateDirectories:(true) attributes:(ocidAttrDict) |error|:(reference))
	#ファイル保存先(File dist path)
	set ocidSaveFilePathURL to (ocidSaveDirPathURL's URLByAppendingPathComponent:(ocidItemName) isDirectory:(false))
	#コピー(Copy)
	set listDone to (appFileManager's copyItemAtURL:(ocidItemURL) toURL:(ocidSaveFilePathURL) |error|:(reference))
end repeat


####################
#終了時に(To open at the end)
set ocidSaveDirPathURL to ocidDocumentDirPathURL's URLByAppendingPathComponent:("FileVersion/" & ocidBaseFileName & "") isDirectory:(true)
#保存先を開く(Open the save destination)
set appWorkspace to refMe's NSWorkspace's sharedWorkspace()
set boolDone to appWorkspace's openURL:(ocidSaveDirPathURL)

return boolDone


####################
#修正日を日付時間数字にする(Set the revision date to the date and time number)
to doGetDateNo(argDateData, argFormatStrings)
	#日付のフォーマットを定義(Define the date format)
	set appFormatter to refMe's NSDateFormatter's alloc()'s init()
	appFormatter's setLocale:(appLocale)
	appFormatter's setDateFormat:(argFormatStrings)
	set ocidDateAndTime to appFormatter's stringFromDate:(argDateData)
	set strDateAndTime to ocidDateAndTime as text
	#テキスト形式で戻す(Return to text format)
	return strDateAndTime
end doGetDateNo

####################
#ローカライズメッセージ取得(Get Localize message)
to doGetDialogMsg(argLocale)
	set ocidLanguageCode to argLocale's objectForKey:(refMe's NSLocaleLanguageCode)
	set strLanguageCode to ocidLanguageCode as text
	set ocidLocaleID to argLocale's objectForKey:(refMe's NSLocaleIdentifier)
	set strLocaleID to ocidLocaleID as text
	log strLocaleID
	log strLanguageCode
	#LPROJフォルダ名(LPROJ name)
	set listLproj to {"ar", "ca", "cs", "da", "de", "el", "en_AU", "en_GB", "en", "es_419", "es", "fi", "fr_CA", "fr", "he", "hi", "hr", "hu", "id", "it", "ja", "ko", "ms", "nl", "no", "pl", "pt_BR", "pt_PT", "ro", "ru", "sk", "sl", "sv", "th", "tr", "uk", "vi", "zh_CN", "zh_HK", "zh_TW"} as list
	#その中でラングエージコードになっている言語(使ってないけど)
	set listLangID to {"ar", "ca", "cs", "da", "de", "el", "en", "es", "fi", "fr", "he", "hi", "hr", "hu", "id", "it", "ja", "ko", "ms", "nl", "no", "pl", "ro", "ru", "sk", "sl", "sv", "th", "tr", "uk", "vi"} as list
	#その中でロケールIDになっている言語(Among them, the language that is the locale ID)
	set listLocalID_Lproj to {"zh_CN", "zh_HK", "zh_TW", "pt_BR", "pt_PT", "en_AU", "en_GB", "fr_CA", "es_419"} as list
	########
	#FOR TEST
	#	set strLanguageCode to ("nn") as text
	#	set strLanguageCode to ("cy") as text
	#	set strLanguageCode to ("fr") as text
	#	set strLanguageCode to ("pt") as text
	#	set strLanguageCode to ("rm") as text
	#	set strLanguageCode to ("zh") as text
	#	set strLanguageCode to ("zh_TW") as text
	
	#	set strLocaleID to ("zh_Hant") as text
	#	set strLanguageCode to ("zh") as text
	
	#	set strLocaleID to ("no_NO") as text
	#	set strLanguageCode to ("nn") as text
	
	
	#言語判定 追加事項(Additional items for language)
	if listLproj does not contain strLanguageCode then
		#ランゲージCodeがLPROJのフォルダ名に含まれていない場合
		#If the language Code is not included in the folder name of LPROJ
		if strLanguageCode is "br" then
			set strSetUIlangID to ("fr") as text
		else if strLanguageCode is "pt" then
			set strSetUIlangID to ("pt_PT") as text
		else if strLanguageCode is "gd" or strLanguageCode is "cy" then
			set strSetUIlangID to ("en_GB") as text
		else if strLanguageCode is "gl" or strLanguageCode is "eu" then
			set strSetUIlangID to ("en_GB") as text
		else if strLanguageCode is "ga" then
			set strSetUIlangID to ("en_GB") as text
		else if strLanguageCode is "nb" or strLanguageCode is "nn" then
			set strSetUIlangID to ("no") as text
		else if strLanguageCode is "is" then
			set strSetUIlangID to ("sv") as text
		else if strLanguageCode is "mt" then
			set strSetUIlangID to ("it") as text
		else if strLanguageCode is "tl" then
			set strSetUIlangID to ("es") as text
		else if strLanguageCode is "lo" then
			set strSetUIlangID to ("th") as text
		else if strLanguageCode is "rm" then
			set strSetUIlangID to ("de") as text
		else if strLanguageCode is "zh" then
			if strLocaleID contains "zh_Hans" then
				set strSetUIlangID to ("zh_CN") as text
			else if strLocaleID contains "zh_CN" then
				set strSetUIlangID to ("zh_CN") as text
			else if strLocaleID contains "zh_Hant" then
				set strSetUIlangID to ("zh_TW") as text
			else if strLocaleID contains "zh_TW" then
				set strSetUIlangID to ("zh_TW") as text
			else if strLocaleID contains "zh_HK" then
				set strSetUIlangID to ("zh_HK") as text
			end if
		else
			#ロケールIDがLPROJのフォルダ名の場合
			#If the locale ID is the folder name of LPROJ
			if listLocalID_Lproj contains strLocaleID then
				set strSetUIlangID to (strLocaleID) as text
			else
				#追加判定にない場合はen表示
				#If there is no additional judgment, there is a fallover to en
				set strSetUIlangID to ("en") as text
			end if
		end if
	else
		if listLocalID_Lproj contains strLocaleID then
			set strSetUIlangID to (strLocaleID) as text
		else
			set strSetUIlangID to (strLanguageCode) as text
		end if
	end if
	#辞書のマージ用(For dictionary merge)
	set ocidMergeDict to refMe's NSMutableDictionary's alloc()'s init()
	#翻訳辞書を開く(Open the translation dictionary)
	#ScriptingAdditionsの辞書を使用(Use the dictionary of ScriptingAdditions)
	set strFilePath to ("/System/Library/ScriptingAdditions/StandardAdditions.osax/Contents/Resources/Localizable.loctable") as text
	set ocidFilePathStr to refMe's NSString's stringWithString:(strFilePath)
	set ocidFilePath to ocidFilePathStr's stringByStandardizingPath()
	set ocidFilePathURL to refMe's NSURL's fileURLWithPath:(ocidFilePath) isDirectory:(false)
	#loctableを開く(Open loctable file)
	set listResponse to refMe's NSMutableDictionary's alloc()'s initWithContentsOfURL:(ocidFilePathURL) |error|:(reference)
	set ocidLocTableDict to (first item of listResponse)
	#対象の言語の翻訳を取得(Get the translation of the target language)
	set ocidStandardAdditionsDict to ocidLocTableDict's objectForKey:(strSetUIlangID)
	#マージ merge
	ocidMergeDict's addEntriesFromDictionary:(ocidStandardAdditionsDict)
	
	#翻訳辞書を開く(Open the translation dictionary)
	#AppKit.frameworkの辞書を使用(Use the dictionary of AppKit.framework)
	set strFilePath to ("/System/Library/Frameworks/AppKit.framework/Versions/C/Resources/Revisions.loctable") as text
	set ocidFilePathStr to refMe's NSString's stringWithString:(strFilePath)
	set ocidFilePath to ocidFilePathStr's stringByStandardizingPath()
	set ocidFilePathURL to refMe's NSURL's fileURLWithPath:(ocidFilePath) isDirectory:(false)
	#loctableを開く(Open loctable file)
	set listResponse to refMe's NSMutableDictionary's alloc()'s initWithContentsOfURL:(ocidFilePathURL) |error|:(reference)
	set ocidLocTableDict to (first item of listResponse)
	#対象の言語の翻訳を取得(Get the translation of the target language)
	set ocidSpotlightDict to ocidLocTableDict's objectForKey:(strSetUIlangID)
	#マージ merge
	ocidMergeDict's addEntriesFromDictionary:(ocidSpotlightDict)
	
	
	return ocidMergeDict
end doGetDialogMsg