From 3c70ffd927a26261a221e6f9a2e5cf4d07f2c6df Mon Sep 17 00:00:00 2001 From: natural-harmonia-gropius <50797982+natural-harmonia-gropius@users.noreply.github.com> Date: Mon, 24 Apr 2023 17:37:58 +0800 Subject: [PATCH] feat: internationalization (#518) Adds `languages` option to specify localization language priority. Built in languages can be found in `scripts/uosc_shared/intl`. --- script-opts/uosc.conf | 6 +++ scripts/uosc.lua | 49 ++++++++++-------- scripts/uosc_shared/elements/Controls.lua | 2 +- scripts/uosc_shared/elements/Menu.lua | 2 +- scripts/uosc_shared/intl/zh-hans.json | 50 +++++++++++++++++++ scripts/uosc_shared/lib/intl.lua | 61 +++++++++++++++++++++++ scripts/uosc_shared/lib/menus.lua | 20 ++++---- 7 files changed, 156 insertions(+), 34 deletions(-) create mode 100644 scripts/uosc_shared/intl/zh-hans.json create mode 100644 scripts/uosc_shared/lib/intl.lua diff --git a/script-opts/uosc.conf b/script-opts/uosc.conf index c6dfe0a..ab5df32 100644 --- a/script-opts/uosc.conf +++ b/script-opts/uosc.conf @@ -219,3 +219,9 @@ chapter_ranges=openings:30abf964,endings:30abf964,ads:c54e4e80 # Add alternative lua patterns to identify beginnings of simple chapter ranges (except for `ads`) # Syntax: `{type}:{pattern}[,{patternN}][;{type}:{pattern}[,{patternN}]]` chapter_range_patterns=openings:オープニング;endings:エンディング + +# Localization language priority from highest to lowest. +# Built in languages can be found in `uosc_shared/intl`. +# `slang` is a keyword to inherit values from `--slang` mpv config. +# Supports paths to custom json files: `languages=~~/custom.json,slang,en` +languages=slang,en diff --git a/scripts/uosc.lua b/scripts/uosc.lua index 3b5c380..1ea83f5 100644 --- a/scripts/uosc.lua +++ b/scripts/uosc.lua @@ -109,6 +109,7 @@ defaults = { adjust_osd_margins = true, chapter_ranges = 'openings:30abf964,endings:30abf964,ads:c54e4e80', chapter_range_patterns = 'openings:オープニング;endings:エンディング', + languages = 'slang,en', } options = table_shallow_copy(defaults) opt.read_options(options, 'uosc') @@ -131,37 +132,40 @@ if options.autoload then mp.commandv('set', 'keep-open-pause', 'no') end fg, bg = serialize_rgba(options.foreground).color, serialize_rgba(options.background).color fgt, bgt = serialize_rgba(options.foreground_text).color, serialize_rgba(options.background_text).color +--[[ INTERNATIONALIZATION ]] +t = require('uosc_shared/lib/intl') + --[[ CONFIG ]] function create_default_menu() return { - {title = 'Subtitles', value = 'script-binding uosc/subtitles'}, - {title = 'Audio tracks', value = 'script-binding uosc/audio'}, - {title = 'Stream quality', value = 'script-binding uosc/stream-quality'}, - {title = 'Playlist', value = 'script-binding uosc/items'}, - {title = 'Chapters', value = 'script-binding uosc/chapters'}, - {title = 'Navigation', items = { - {title = 'Next', hint = 'playlist or file', value = 'script-binding uosc/next'}, - {title = 'Prev', hint = 'playlist or file', value = 'script-binding uosc/prev'}, - {title = 'Delete file & Next', value = 'script-binding uosc/delete-file-next'}, - {title = 'Delete file & Prev', value = 'script-binding uosc/delete-file-prev'}, - {title = 'Delete file & Quit', value = 'script-binding uosc/delete-file-quit'}, - {title = 'Open file', value = 'script-binding uosc/open-file'}, + {title = t('Subtitles'), value = 'script-binding uosc/subtitles'}, + {title = t('Audio tracks'), value = 'script-binding uosc/audio'}, + {title = t('Stream quality'), value = 'script-binding uosc/stream-quality'}, + {title = t('Playlist'), value = 'script-binding uosc/items'}, + {title = t('Chapters'), value = 'script-binding uosc/chapters'}, + {title = t('Navigation'), items = { + {title = t('Next'), hint = t('playlist or file'), value = 'script-binding uosc/next'}, + {title = t('Prev'), hint = t('playlist or file'), value = 'script-binding uosc/prev'}, + {title = t('Delete file & Next'), value = 'script-binding uosc/delete-file-next'}, + {title = t('Delete file & Prev'), value = 'script-binding uosc/delete-file-prev'}, + {title = t('Delete file & Quit'), value = 'script-binding uosc/delete-file-quit'}, + {title = t('Open file'), value = 'script-binding uosc/open-file'}, },}, - {title = 'Utils', items = { - {title = 'Aspect ratio', items = { - {title = 'Default', value = 'set video-aspect-override "-1"'}, + {title = t('Utils'), items = { + {title = t('Aspect ratio'), items = { + {title = t('Default'), value = 'set video-aspect-override "-1"'}, {title = '16:9', value = 'set video-aspect-override "16:9"'}, {title = '4:3', value = 'set video-aspect-override "4:3"'}, {title = '2.35:1', value = 'set video-aspect-override "2.35:1"'}, },}, - {title = 'Audio devices', value = 'script-binding uosc/audio-device'}, - {title = 'Editions', value = 'script-binding uosc/editions'}, - {title = 'Screenshot', value = 'async screenshot'}, - {title = 'Show in directory', value = 'script-binding uosc/show-in-directory'}, - {title = 'Open config folder', value = 'script-binding uosc/open-config-directory'}, + {title = t('Audio devices'), value = 'script-binding uosc/audio-device'}, + {title = t('Editions'), value = 'script-binding uosc/editions'}, + {title = t('Screenshot'), value = 'async screenshot'}, + {title = t('Show in directory'), value = 'script-binding uosc/show-in-directory'}, + {title = t('Open config folder'), value = 'script-binding uosc/open-config-directory'}, },}, - {title = 'Quit', value = 'quit'}, + {title = t('Quit'), value = 'quit'}, } end @@ -222,6 +226,7 @@ config = { local title_parts = split(title or '', ' *> *') for index, title_part in ipairs(#title_parts > 0 and title_parts or {''}) do + title_part = t(title_part) if index < #title_parts then submenu_id = submenu_id .. title_part @@ -987,7 +992,7 @@ bind_command('editions', create_self_updating_menu_opener({ local items = {} for _, edition in ipairs(editions or {}) do items[#items + 1] = { - title = edition.title or 'Edition', + title = edition.title or t('Edition'), hint = tostring(edition.id + 1), value = edition.id, active = edition.id == current_id, diff --git a/scripts/uosc_shared/elements/Controls.lua b/scripts/uosc_shared/elements/Controls.lua index 9a6be72..ea4ca34 100644 --- a/scripts/uosc_shared/elements/Controls.lua +++ b/scripts/uosc_shared/elements/Controls.lua @@ -65,7 +65,7 @@ function Controls:init() for i, item in ipairs(items) do local config = shorthands[item.config] and shorthands[item.config] or item.config local config_tooltip = split(config, ' *%? *') - local tooltip = config_tooltip[2] + local tooltip = t(config_tooltip[2]) config = shorthands[config_tooltip[1]] and split(shorthands[config_tooltip[1]], ' *%? *')[1] or config_tooltip[1] local config_badge = split(config, ' *# *') diff --git a/scripts/uosc_shared/elements/Menu.lua b/scripts/uosc_shared/elements/Menu.lua index 1830647..b6da012 100644 --- a/scripts/uosc_shared/elements/Menu.lua +++ b/scripts/uosc_shared/elements/Menu.lua @@ -799,7 +799,7 @@ function Menu:render() if draw_title then local title_ay = ay - self.item_height local title_height = self.item_height - 3 - menu.ass_safe_title = menu.ass_safe_title or ass_escape(menu.title) + menu.ass_safe_title = t(menu.ass_safe_title or ass_escape(menu.title)) -- Background ass:rect(ax + 2, title_ay, bx - 2, title_ay + title_height, { diff --git a/scripts/uosc_shared/intl/zh-hans.json b/scripts/uosc_shared/intl/zh-hans.json new file mode 100644 index 0000000..bb87429 --- /dev/null +++ b/scripts/uosc_shared/intl/zh-hans.json @@ -0,0 +1,50 @@ +{ + "Aspect ratio": "纵横比", + "Audio": "音频", + "Audio device": "音频设备", + "Audio devices": "音频设备", + "Audio tracks": "音频轨道", + "Chapters": "章节", + "Default": "默认", + "Delete file & Next": "删除文件并播放下一个", + "Delete file & Prev": "删除文件并播放上一个", + "Delete file & Quit": "删除文件并退出", + "Disabled": "禁用", + "Drives": "驱动器", + "Edition": "版本", + "Editions": "版本", + "First": "第一个", + "Fullscreen": "全屏", + "Last": "最后一个", + "Load": "加载", + "Load audio": "加载音频", + "Load subtitles": "加载字幕", + "Load video": "加载视频", + "Loop file": "单个循环", + "Loop playlist": "列表循环", + "Menu": "菜单", + "Navigation": "导航", + "Next": "下一个", + "Open config folder": "打开设置文件夹", + "Open file": "打开文件", + "Playlist": "播放列表", + "Playlist/Files": "播放/文件列表", + "Prev": "上一个", + "Previous": "上一个", + "Quit": "退出", + "Screenshot": "截图", + "Show in directory": "打开所在文件夹", + "Shuffle": "乱序", + "Stream quality": "流媒体质量", + "Subtitles": "字幕", + "Track": "轨道", + "Utils": "工具", + "Video": "视频", + "default": "默认", + "drive": "磁盘", + "external": "外置", + "forced": "强制", + "open file": "打开文件", + "parent dir": "父文件夹", + "playlist or file": "播放列表或文件" +} diff --git a/scripts/uosc_shared/lib/intl.lua b/scripts/uosc_shared/lib/intl.lua new file mode 100644 index 0000000..13a9582 --- /dev/null +++ b/scripts/uosc_shared/lib/intl.lua @@ -0,0 +1,61 @@ +local locale = {} + +-- https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/supported-languages?pivots=store-installer-msix#list-of-supported-languages +function get_languages() + local languages = {} + for _, lang in ipairs(split(options.languages, ',')) do + if (lang == 'slang') then + local slang = mp.get_property_native('slang') + if slang then + itable_append(languages, slang) + end + else + itable_append(languages, { lang }) + end + end + + return languages +end + +---@param path string +function get_locale_from_json(path) + local expand_path = mp.command_native({ 'expand-path', path }) + + local meta, meta_error = utils.file_info(expand_path) + if not meta or not meta.is_file then + return {} + end + + local json_file = io.open(expand_path, 'r') + if not json_file then + return {} + end + + local json = json_file:read('*all') + json_file:close() + + return utils.parse_json(json) +end + +function make_locale() + local translations = {} + for _, lang in ipairs(get_languages()) do + + if (lang:match('.json$')) then + table_assign(translations, get_locale_from_json(lang)) + else + table_assign(translations, get_locale_from_json('~~/scripts/uosc_shared/intl/' .. lang:lower() .. '.json')) + end + end + + return translations +end + +---@param text string +function t(text) + return locale[text] or text +end + +locale = make_locale() + +return t diff --git a/scripts/uosc_shared/lib/menus.lua b/scripts/uosc_shared/lib/menus.lua index 5b7b790..cd230c4 100644 --- a/scripts/uosc_shared/lib/menus.lua +++ b/scripts/uosc_shared/lib/menus.lua @@ -77,7 +77,7 @@ function create_select_tracklist_type_menu_opener(menu_title, track_type, track_ if load_command then items[#items + 1] = { - title = 'Load', bold = true, italic = true, hint = 'open file', value = '{load}', separator = true, + title = t('Load'), bold = true, italic = true, hint = t('open file'), value = '{load}', separator = true, } end @@ -91,7 +91,7 @@ function create_select_tracklist_type_menu_opener(menu_title, track_type, track_ -- If I'm mistaken and there is an active need for this, feel free to -- open an issue. if track_type == 'sub' then - disabled_item = {title = 'Disabled', italic = true, muted = true, hint = '—', value = nil, active = true} + disabled_item = {title = t('Disabled'), italic = true, muted = true, hint = '—', value = nil, active = true} items[#items + 1] = disabled_item end @@ -108,12 +108,12 @@ function create_select_tracklist_type_menu_opener(menu_title, track_type, track_ h(track.codec) if track['audio-channels'] then h(track['audio-channels'] .. ' channels') end if track['demux-samplerate'] then h(string.format('%.3gkHz', track['demux-samplerate'] / 1000)) end - if track.forced then h('forced') end - if track.default then h('default') end - if track.external then h('external') end + if track.forced then h(t('forced')) end + if track.default then h(t('default')) end + if track.external then h(t('external')) end items[#items + 1] = { - title = (track.title and track.title or 'Track ' .. track.id), + title = (track.title and track.title or t('Track') .. ' ' .. track.id), hint = table.concat(hint_values, ', '), value = track.id, active = track.selected, @@ -181,10 +181,10 @@ function open_file_navigation_menu(directory_path, handle_select, opts) if is_root then if state.platform == 'windows' then - items[#items + 1] = {title = '..', hint = 'Drives', value = '{drives}', separator = true} + items[#items + 1] = {title = '..', hint = t('Drives'), value = '{drives}', separator = true} end else - items[#items + 1] = {title = '..', hint = 'parent dir', value = directory.dirname, separator = true} + items[#items + 1] = {title = '..', hint = t('parent dir'), value = directory.dirname, separator = true} end local back_path = items[#items] and items[#items].value @@ -276,7 +276,7 @@ function open_drives_menu(handle_select, opts) if drive then local drive_path = normalize_path(drive) items[#items + 1] = { - title = drive, hint = 'drive', value = drive_path, active = opts.active_path == drive_path, + title = drive, hint = t('drive'), value = drive_path, active = opts.active_path == drive_path, } if opts.selected_path == drive_path then selected_index = #items end end @@ -286,7 +286,7 @@ function open_drives_menu(handle_select, opts) end return Menu:open( - {type = opts.type, title = opts.title or 'Drives', items = items, selected_index = selected_index}, + {type = opts.type, title = opts.title or t('Drives'), items = items, selected_index = selected_index}, handle_select ) end