From 37734445e7235d1000d87e0c56851fbe7c8ee583 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sat, 5 Mar 2022 20:38:22 +0100 Subject: [PATCH] CI: Add automated check for invalid locale files Also clean up issues found in the existing locale files --- .github/workflows/locale-check.yml | 14 +++++ CI/checkLocale.py | 93 ++++++++++++++++++++++++++++++ data/locale/de-DE.ini | 1 - data/locale/en-US.ini | 1 - data/locale/ru-RU.ini | 12 ---- data/locale/tr-TR.ini | 5 -- data/locale/zh-CN.ini | 14 +---- 7 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/locale-check.yml create mode 100644 CI/checkLocale.py diff --git a/.github/workflows/locale-check.yml b/.github/workflows/locale-check.yml new file mode 100644 index 00000000..1fec72a8 --- /dev/null +++ b/.github/workflows/locale-check.yml @@ -0,0 +1,14 @@ +name: Check locale + +on: [push, pull_request] + +jobs: + ubuntu64: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Check locale files + run: | + python3 ./CI/checkLocale.py -p data/locale/ diff --git a/CI/checkLocale.py b/CI/checkLocale.py new file mode 100644 index 00000000..e6792157 --- /dev/null +++ b/CI/checkLocale.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse + +defaultLocaleFile = "en-US.ini" + + +class localeEntry: + locale = "" + placeholders = [] + + def __init__(self, locale, widgets) -> None: + self.locale = locale + self.placeholders = widgets + + +def getNonDefaultLocales(dir): + files = [] + for filename in os.listdir(dir): + f = os.path.join(dir, filename) + if os.path.isfile(f) and not f.endswith(defaultLocaleFile): + files.append(f) + return files + + +def getAllLocaleEntriesWithWidgetPlaceholders(file): + localeEntries = [] + with open(file, 'r', encoding='UTF-8') as f: + for line in f.readlines(): + widgetPlaceholders = [] + for word in line.split("{{"): + if not "}}" in word: + continue + word = "{{" + word[:word.rfind("}}")] + "}}" + widgetPlaceholders.append(word) + localeEntries.append(localeEntry( + line.split("=")[0], widgetPlaceholders)) + return localeEntries + + +def getLocaleEntryFrom(entry, list): + for element in list: + if element.locale == entry.locale: + return element + return None + + +def checkWidgetPlacehodlers(file, expectedPlaceholders): + localeEntries = getAllLocaleEntriesWithWidgetPlaceholders(file) + result = True + for localeEntry in localeEntries: + expectedEntry = getLocaleEntryFrom(localeEntry, expectedPlaceholders) + if expectedEntry is None: + print( + "WARNING: Locale entry \"{}\" from \"{}\" not found in \"{}\"".format(localeEntry.locale, file, defaultLocaleFile)) + continue + for p in localeEntry.placeholders: + if p not in expectedEntry.placeholders: + print("WARNING: Locale entry \"{}\" from \"{}\" does contain \"{}\" while \"{}\" does not".format( + localeEntry.locale, file, p, defaultLocaleFile)) + for p in expectedEntry.placeholders: + if p not in localeEntry.placeholders: + result = False + print("ERROR: Locale entry \"{}\" from \"{}\" does not contain \"{}\"".format( + localeEntry.locale, file, p)) + return result + + +def main(): + parser = argparse.ArgumentParser( + description='Checks for inconsistencies regarding widget placeholders in the different locale files.') + parser.add_argument( + '-p', '--path', help='Path to locale folder', required=True) + args = parser.parse_args() + + placeholders = getAllLocaleEntriesWithWidgetPlaceholders( + os.path.join(args.path, defaultLocaleFile)) + nonDefaultLocales = getNonDefaultLocales(args.path) + + result = True + for f in nonDefaultLocales: + if checkWidgetPlacehodlers(f, placeholders) == False: + result = False + if result == False: + sys.exit(1) + print("SUCCESS: No issues found!") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index 699041e5..27c93444 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -223,7 +223,6 @@ AdvSceneSwitcher.videoTab.help="

Dieser Tab ermöglicht es ; Network Tab AdvSceneSwitcher.networkTab.title="Netzwerk" AdvSceneSwitcher.networkTab.warning="Die Verwendung des Servers außerhalb eines lokalen Netzwerks kann dazu führen, dass die aktive Szene von dritten Personen ausgelesen werden kann." -AdvSceneSwitcher.networkTab.Disabledwarning="Diese Funktionalität musste unter macOS leider aufgrund von Bibliotheksinkompatibilitäten mit dem obs-websocket plugin deaktiviert werden." AdvSceneSwitcher.networkTab.server="Server starten (Sendet Szenenwechselnachrichten zu allen verbundenen Clients)" AdvSceneSwitcher.networkTab.server.port="Port" AdvSceneSwitcher.networkTab.server.lockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index d609f9c2..6af59ece 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -627,7 +627,6 @@ AdvSceneSwitcher.videoTab.help="

This tab will allow you to AdvSceneSwitcher.networkTab.title="Network" AdvSceneSwitcher.networkTab.description="This tab will allow you to remotely control the active scene of another OBS instance.\nPlease note that the scene names have to match exactly on all OBS instances." AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene." -AdvSceneSwitcher.networkTab.DisabledWarning="This functionality unfortunately had to be disabled on macOS due to library incompatibilities when running the obs-websocket plugin in parallel." AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)" AdvSceneSwitcher.networkTab.server.port="Port" AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4" diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini index 8ae75bd7..22239944 100644 --- a/data/locale/ru-RU.ini +++ b/data/locale/ru-RU.ini @@ -78,23 +78,16 @@ AdvSceneSwitcher.logic.rootNone="Если" AdvSceneSwitcher.logic.not="Если нет" ; Macro Conditions AdvSceneSwitcher.condition.audio="Аудио" -AdvSceneSwitcher.ondition.audio.state.below="Ниже" -AdvSceneSwitcher.ondition.audio.state.above="Выше" -AdvSceneSwitcher.condition.audio.entry="Громкость {{audioSources}} равна {{condition}} {{volume}} в течении {{duration}} секунд" AdvSceneSwitcher.condition.cursor="Область экрана" AdvSceneSwitcher.condition.scene="Сцена" AdvSceneSwitcher.condition.scene.type.current="Текущий" AdvSceneSwitcher.condition.scene.type.previous="Предыдущий" -AdvSceneSwitcher.condition.scene.entry="{{sceneType}} сцена является {{scenes}} на {{duration}}" AdvSceneSwitcher.condition.window="Окно" AdvSceneSwitcher.condition.window.entry.line1="{{windows}} существует и ..." -AdvSceneSwitcher.condition.window.entry.line2="... это {{fullscreen}} полноэкранный {{maximized}} максимизированный {{focused}} сфокусированный" AdvSceneSwitcher.condition.file="Файл" -AdvSceneSwitcher.condition.file.entry.line1="Содержимое {{fileType}} {{filePath}} {{browseButton}} соответствует:" AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}" AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}" AdvSceneSwitcher.condition.media="Медиа" -AdvSceneSwitcher.condition.media.entry="{{mediaSources}} состояние {{states}} и {{timeRestrictions}} {{time}}" AdvSceneSwitcher.condition.video="Видео" AdvSceneSwitcher.condition.video.condition.match="точно соответствует" AdvSceneSwitcher.condition.video.condition.differ="не совпадает" @@ -104,16 +97,13 @@ AdvSceneSwitcher.condition.video.condition.noImage="не имеет вывода AdvSceneSwitcher.condition.video.askFileAction="Вы хотите использовать существующий файл или создать скриншот текущего выбранного источника?" AdvSceneSwitcher.condition.video.askFileAction.file="Использовать существующий файл" AdvSceneSwitcher.condition.video.askFileAction.screenshot="Создать скриншот" -AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{filePath}} {{browseButton}} для {{duration}}" AdvSceneSwitcher.condition.stream="Потоковое вещание" AdvSceneSwitcher.condition.stream.state.start="Поток запущен" AdvSceneSwitcher.condition.stream.state.stop="Поток остановлен" -AdvSceneSwitcher.condition.stream.entry="{{streamState}} для {{duration}}" AdvSceneSwitcher.condition.record="Запись" AdvSceneSwitcher.condition.record.state.start="Запись запущена" AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена" AdvSceneSwitcher.condition.record.state.stop="Запись остановлена" -AdvSceneSwitcher.condition.record.entry="{{recordState}} для {{duration}}" AdvSceneSwitcher.condition.process="Процесс" AdvSceneSwitcher.condition.process.entry="{{processes}} запущен {{focused}} и сфокусирован" AdvSceneSwitcher.condition.idle="Простой" @@ -153,7 +143,6 @@ AdvSceneSwitcher.action.streaming.type.stop="Остановить потоков AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание" AdvSceneSwitcher.action.streaming.entry="{{actions}}" AdvSceneSwitcher.action.run="Запустить" -AdvSceneSwitcher.action.run.entry="Запустить {{filePath}} {{browseButton}}" ; Transition Tab @@ -327,7 +316,6 @@ AdvSceneSwitcher.videoTab.help="

Эта вкладка по ; Network Tab AdvSceneSwitcher.networkTab.title="Сеть" AdvSceneSwitcher.networkTab.warning="Запуск сервера вне локальной сети позволит третьим лицам читать активную сцену." -AdvSceneSwitcher.networkTab.DisabledWarning="ту функциональность, к сожалению, пришлось отключить на macOS из-за несовместимости библиотек при параллельном запуске плагина obs-websocket." AdvSceneSwitcher.networkTab.server="Запустить сервер (отправляет сообщения о переключении сцены всем подключенным клиентам)" AdvSceneSwitcher.networkTab.server.port="Порт" AdvSceneSwitcher.networkTab.server.lockToIPv4="Заблокировать сервер на использование только IPv4" diff --git a/data/locale/tr-TR.ini b/data/locale/tr-TR.ini index e7e6c3b9..c0ffd2aa 100644 --- a/data/locale/tr-TR.ini +++ b/data/locale/tr-TR.ini @@ -92,7 +92,6 @@ AdvSceneSwitcher.condition.audio.state.below="altında" AdvSceneSwitcher.condition.audio.state.above="üstünde" AdvSceneSwitcher.condition.audio.state.mute="sessiz" AdvSceneSwitcher.condition.audio.state.unmute="ses açıldı" -AdvSceneSwitcher.condition.audio.entry="Hacim{{audioSources}} gibi {{condition}} {{volume}}" AdvSceneSwitcher.condition.cursor="İmleç" AdvSceneSwitcher.condition.cursor.type.region="alanında" AdvSceneSwitcher.condition.cursor.type.moving="hareketlidir" @@ -222,7 +221,6 @@ AdvSceneSwitcher.condition.sceneOrder.entry="Açık{{scenes}}{{sources}}{{condit AdvSceneSwitcher.condition.hotkey="Kısayol tuşu" AdvSceneSwitcher.condition.hotkey.name="Makro tetik kısayol tuşu" AdvSceneSwitcher.condition.hotkey.tip="Not: Bu kısayol tuşu için tuş atamalarını OBS ayarları penceresinde yapılandırabilirsiniz." -AdvSceneSwitcher.condition.hotkey.entry="İsim: {{name}}" AdvSceneSwitcher.condition.replay="Tekrar oynatma arabelleği" AdvSceneSwitcher.condition.replay.state.stopped="Tekrar oynatma arabelleği durdu" AdvSceneSwitcher.condition.replay.state.started="Tekrar oynatma arabelleği başladı" @@ -233,8 +231,6 @@ AdvSceneSwitcher.condition.date.state.at="de" AdvSceneSwitcher.condition.date.state.after="Sonra" AdvSceneSwitcher.condition.date.state.before="Önce" AdvSceneSwitcher.condition.date.state.between="Arasında" -AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} Tarih bileşenini yoksay {{ignoreTime}} Zaman bileşenini yoksay" -AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Tekrar her {{duration}} tarih eşleşmesinde" AdvSceneSwitcher.condition.sceneTransform="Sahne öğesi dönüşümü" AdvSceneSwitcher.condition.sceneTransform.getTransform="Dönüşümü al" AdvSceneSwitcher.condition.sceneTransform.regex="Normal ifadeler kullanın" @@ -569,7 +565,6 @@ AdvSceneSwitcher.videoTab.help="

Bu sekme, seçilen kaynakl AdvSceneSwitcher.networkTab.title="Ağ" AdvSceneSwitcher.networkTab.description="Bu sekme, başka bir OBS örneğinin etkin sahnesini uzaktan kontrol etmenizi sağlar.\nSahne adlarının tüm OBS örneklerinde tam olarak eşleşmesi gerektiğini lütfen unutmayın." AdvSceneSwitcher.networkTab.warning="Sunucuyu yerel bir ağın dışında çalıştırmak, üçüncü tarafların aktif sahneyi okumasına izin verecektir." -AdvSceneSwitcher.networkTab.DisabledWarning="Bu işlevsellik, obs-websocket eklentisini paralel olarak çalıştırırken kitaplık uyumsuzlukları nedeniyle ne yazık ki macOS'ta devre dışı bırakılmak zorunda kaldı." AdvSceneSwitcher.networkTab.server="Sunucuyu başlat (Bağlı tüm istemcilere sahne değiştirme mesajları gönderir)" AdvSceneSwitcher.networkTab.server.port="Port" AdvSceneSwitcher.networkTab.server.lockToIPv4="Sunucuyu yalnızca IPv4 kullanacak şekilde kilitleyin" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index 2e05d859..8d1e5eac 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -99,7 +99,6 @@ AdvSceneSwitcher.condition.audio.state.mute="静音" AdvSceneSwitcher.condition.audio.state.unmute="取消静音" AdvSceneSwitcher.condition.audio.type.output="输出音频" AdvSceneSwitcher.condition.audio.type.volume="配置音量级别" -AdvSceneSwitcher.condition.audio.entry="音量 来源 {{audioSources}} 是 {{condition}} {{volume}}" AdvSceneSwitcher.condition.cursor="屏幕区域" AdvSceneSwitcher.condition.cursor.type.region="当前位置" AdvSceneSwitcher.condition.cursor.type.moving="正在移动" @@ -112,7 +111,6 @@ AdvSceneSwitcher.condition.scene.type.current="当前场景是" AdvSceneSwitcher.condition.scene.type.previous="上一个场景是" AdvSceneSwitcher.condition.scene.type.changed="场景改变了" AdvSceneSwitcher.condition.scene.type.notChanged="场景没有改变" -AdvSceneSwitcher.condition.scene.waitForTransition="等待过渡完成" AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}} {{scenes}}" AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}" AdvSceneSwitcher.condition.window="窗口" @@ -126,7 +124,6 @@ AdvSceneSwitcher.condition.media="媒体" AdvSceneSwitcher.condition.media.anyOnScene="任何媒体来源" AdvSceneSwitcher.condition.media.allOnScene="所有媒体来源" AdvSceneSwitcher.condition.media.matchOnChange="仅在更改时匹配(注意:此选项将在未来版本中删除-请改用时间限制)" -AdvSceneSwitcher.condition.media.entry="{{mediaSources}} 状态是 {{states}} 和 {{timeRestrictions}} {{time}}" AdvSceneSwitcher.condition.video="视频" AdvSceneSwitcher.condition.video.condition.match="完全匹配" AdvSceneSwitcher.condition.video.condition.differ="不匹配" @@ -186,8 +183,6 @@ AdvSceneSwitcher.condition.timer.type.fixed="固定" AdvSceneSwitcher.condition.timer.type.random="随机" AdvSceneSwitcher.condition.timer.pause="暂停" AdvSceneSwitcher.condition.timer.continue="继续" -AdvSceneSwitcher.condition.timer.entry.line1.fixed="{{type}} 持续时间 {{duration}} 结束 " -AdvSceneSwitcher.condition.timer.entry.line1="{{duration}} 时间倒计时" AdvSceneSwitcher.condition.timer.entry.line2="剩余时间: {{remaining}} 秒" AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} 保存剩余时间 {{autoReset}} 达到时间后自动重置计时器" AdvSceneSwitcher.condition.timer.reset="重置" @@ -276,7 +271,7 @@ AdvSceneSwitcher.condition.transition.type.ended="转场特效结束" AdvSceneSwitcher.condition.transition.type.transitionSource="转场特效来自" AdvSceneSwitcher.condition.transition.type.transitionTarget="转场特效到" AdvSceneSwitcher.condition.transition.durationSuffix="秒" -AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{duration}}{{durationSuffix}}" +AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{scenes}}{{duration}}{{durationSuffix}}" AdvSceneSwitcher.condition.sceneVisibility="场景项目可见" AdvSceneSwitcher.condition.sceneVisibility.type.shown="显示的" AdvSceneSwitcher.condition.sceneVisibility.type.hidden="隐藏的" @@ -353,7 +348,6 @@ AdvSceneSwitcher.action.sceneVisibility.type.show="显示" AdvSceneSwitcher.action.sceneVisibility.type.hide="隐藏" AdvSceneSwitcher.action.sceneVisibility.type.source="来源" AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="全部" -AdvSceneSwitcher.action.sceneVisibility.entry="在 {{scenes}} {{actions}} {{sourceTypes}} {{sources}}" AdvSceneSwitcher.action.filter="滤镜" AdvSceneSwitcher.action.filter.type.enable="开启" AdvSceneSwitcher.action.filter.type.disable="关闭" @@ -387,7 +381,6 @@ AdvSceneSwitcher.action.pluginState.type.stop="停止高级场景切换插件" AdvSceneSwitcher.action.pluginState.type.noMatch="没有匹配项:" AdvSceneSwitcher.action.pluginState.type.import="从设置导入" AdvSceneSwitcher.action.pluginState.importWarning="注意:当“设置”窗口打开时,操作将被忽略。" -AdvSceneSwitcher.action.pluginState.entry="{{actions}} {{values}} {{scenes}}" AdvSceneSwitcher.action.virtualCamera="虚拟摄像机" AdvSceneSwitcher.action.virtualCamera.type.stop="停止虚拟摄像机" AdvSceneSwitcher.action.virtualCamera.type.start="启动虚拟摄像机" @@ -432,7 +425,6 @@ AdvSceneSwitcher.action.timer.type.reset="重置" AdvSceneSwitcher.action.timer.type.setTimeRemaining="设置剩余时间" AdvSceneSwitcher.action.timer.entry="{{timerAction}} 定时在 {{macros}} {{duration}}" AdvSceneSwitcher.action.random="随机" -AdvSceneSwitcher.action.random.entry="运行 {{macroSelection}}" AdvSceneSwitcher.action.systray="系统托盘通知" AdvSceneSwitcher.action.systray.entry="显示通知: {{message}}" AdvSceneSwitcher.action.screenshot="截图" @@ -458,8 +450,6 @@ AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="更改激活转场特 AdvSceneSwitcher.transitionTab.transitionForAToB="当自动从场景A切换到场景B时使用的转场特效" AdvSceneSwitcher.transitionTab.transitionsHelp="

这里的设定只影响由场景切换器引起的转场有效,不影响你手动的引起的转场。
在这里设定的转场特效优先级高于场景切换器其他地方设置的

单击加号添加项目.

" AdvSceneSwitcher.transitionTab.defaultTransition="当切换到这个场景时修改默认转场特效" -AdvSceneSwitcher.transitionTab.switchDefaultLabel="活跃时切换默认转场特效为" -AdvSceneSwitcher.transitionTab.entry="从场景 {{scenes}} 切换到场景 {{scenes2}} 时使用转场特效 {{transitions}}" AdvSceneSwitcher.transitionTab.defaultTransitionEntry="当场景 {{scenes}} 被激活时更改默认转场特效为 {{transitions}}" AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="单击加号添加项目." AdvSceneSwitcher.transitionTab.defaultTransition.delay="场景更改 {{defTransitionDelay}} 后切换转换特效." @@ -600,7 +590,6 @@ AdvSceneSwitcher.audioTab.title="音频" AdvSceneSwitcher.audioTab.condition.above="高于" AdvSceneSwitcher.audioTab.condition.below="低于" AdvSceneSwitcher.audioTab.ignoreInactiveSource="除非源处于非活动状态" -AdvSceneSwitcher.audioTab.entry="当{{audioSources}} 在 {{condition}} {{volumeWidget}} 后 {{duration}} 秒使用转场特效 {{transitions}} 切换到场景 {{scenes}}" AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="如果多个条目匹配 ..." AdvSceneSwitcher.audioTab.multiMatchfallback="... {{duration}} 秒切换到 {{scenes}} 使用 {{transitions}}" AdvSceneSwitcher.audioTab.help="此选项卡允许您根据源音量切换场景。\n例如,如果麦克风音量达到某个阈值,您可以自动切换到其他场景。\n\n单击高亮显示的加号继续." @@ -622,7 +611,6 @@ AdvSceneSwitcher.videoTab.help="

此选项卡将允许您 AdvSceneSwitcher.networkTab.title="网络" AdvSceneSwitcher.networkTab.description="此选项卡将允许您远程控制另一个OBS实例的活动场景。\n请注意,所有OBS实例上的场景名称必须完全匹配." AdvSceneSwitcher.networkTab.warning="在本地网络之外运行服务器将允许第三方读取活动场景." -AdvSceneSwitcher.networkTab.DisabledWarning="不幸的是,在并行运行obs websocket插件时,由于库不兼容,不得不在macOS上禁用此功能." AdvSceneSwitcher.networkTab.server="启动服务器(向所有连接的客户端发送场景切换消息)" AdvSceneSwitcher.networkTab.server.port="端口" AdvSceneSwitcher.networkTab.server.lockToIPv4="锁定为仅使用IPv4将服务器"