Fix some template strings in locale files, add script to find problematic values (#7519)

* Fix template strings

* add script to make it easier to find issues with template strings

* switch to sync fs, exclude non yaml files, add jsdoc comment

* Don't fail on duplicate templates in the same string

We should only need to validate if the key is in the string, not how many times the key is in the string
This commit is contained in:
ChunkyProgrammer
2025-06-27 13:10:58 +00:00
committed by GitHub
parent 15da2a0177
commit 77109c87a5
17 changed files with 129 additions and 40 deletions

2
.gitignore vendored
View File

@@ -28,3 +28,5 @@ debug/
# Lefthook
lefthook-local.yml
locale-errors.json

View File

@@ -0,0 +1,86 @@
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { load as loadYaml } from 'js-yaml'
const localesPath = join(import.meta.dirname, '..', 'static', 'locales')
const defaultLocale = 'en-US.yaml'
const errors = [
]
const defaultData = loadYaml(readFileSync(`${localesPath}/${defaultLocale}`, { encoding: 'utf-8' }))
const defaultKeys = Object.keys(defaultData)
const filesInLocaleDir = readdirSync(localesPath)
for (const file of filesInLocaleDir) {
if (file !== defaultLocale && file.endsWith('.yaml')) {
const fileData = loadYaml(readFileSync(`${localesPath}/${file}`, { encoding: 'utf-8' }))
const fileDataKeys = Object.keys(fileData)
addErrors(defaultData, fileData, defaultKeys, fileDataKeys, file)
}
}
writeFileSync('locale-errors.json', JSON.stringify(errors, null, 2))
if (errors.length > 0) {
console.error(errors)
} else {
console.log('no issues found')
}
/**
* @param {unknown} originalData - data from en-US converted to a JavaScript object
* @param {unknown} newData - data from the file we are analyzing converted to a JavaScript object
* @param {string[]} originalKeys - keys from en-US file
* @param {string[]} newKeys - keys from the file we are currently analyzing
* @param {string} file - the file we are currently analyzing
*/
function addErrors(originalData, newData, originalKeys, newKeys, file) {
newKeys.forEach(newKey => {
if (originalKeys.includes(newKey)) {
if (typeof originalData[newKey] === 'object') {
addErrors(originalData[newKey], newData[newKey], Object.keys(originalData[newKey]), Object.keys(newData[newKey]), file)
} else if (isMissingInterpolation(originalData[newKey], newData[newKey], file)) {
errors.push({ fileName: file, error: 'value is missing a template or has an extra template', key: newKey, defaultValue: originalData[newKey], value: newData[newKey] })
}
} else {
// The key doesn't exist in the en-US file but exists in current yaml file.
// We should go through this eventually but it's not as important as invalid templates
// errors.push({ fileName: file, error: 'extra key found', key: fdk })
}
})
}
/**
*
* @param {String} defaultValue
* @param {String} otherValue
*/
function isMissingInterpolation(defaultValue, otherValue, filename) {
if (otherValue === '') {
// not translated yet, we don't care
return false
}
const defaultMatches = Array.from(new Set(defaultValue.match(/{[^}]*}/g)))
const otherMatches = Array.from(new Set(otherValue.match(/{[^}]*}/g)))
if (defaultMatches) {
if (!otherMatches) {
// no templates found.
return true
}
defaultMatches.sort()
otherMatches.sort()
const defaultMatchesStringified = JSON.stringify(defaultMatches)
const otherMatchesStringified = JSON.stringify(otherMatches)
// check if templates match.
return defaultMatchesStringified !== otherMatchesStringified
} else if (otherMatches) {
// extra template found
return true
}
}

View File

@@ -50,6 +50,7 @@
"pack:web": "webpack --mode=production --node-env=production --config _scripts/webpack.web.config.js",
"pack:botGuardScript": "webpack --config _scripts/webpack.botGuardScript.config.js",
"postinstall": "yarn run --silent patch-shaka",
"checkforbadtemplates": "node _scripts/findMissingTemplates.mjs",
"ci": "yarn install --silent --frozen-lockfile --network-concurrency 1"
},
"dependencies": {

View File

@@ -27,7 +27,7 @@ Close: 'suilt'
Back: 'Terug'
Forward: 'vorentoe'
Open New Window: 'Maak nuwe venster oop'
Go to page: 'Gaan na {bladsy}'
Go to page: 'Gaan na {page}'
Close Banner: 'Sluit baniere'
Version {versionNumber} is now available! Click for more details: 'Weergawe {versionNumber}
@@ -1171,7 +1171,7 @@ Screenshot Error: 'Skermkiekie het misluk. {error}'
Channel Hidden: '{channel} tot kanaalfilter toegevoeg'
Channel Unhidden: '{channel} verwyder uit kanaalfilter'
Trimmed input must be at least N characters long: 'Bygesnyde invoer moet ten minste
1 karakter lank wees | Bygesnyde invoer moet ten minste {lentgh} karakters lank
1 karakter lank wees | Bygesnyde invoer moet ten minste {length} karakters lank
wees'
Tag already exists: 'Etiket “{tagName}” bestaan reeds'

View File

@@ -29,13 +29,13 @@ Global:
Live: লাইভ
Sort By: বস্তু অনুযায়ী সজাওক
Counts:
Comment Count: টা মন্তব্য | {গণনা} মন্তব্য
Like Count: ৰ দৰে | {গণনা} লাইক
Subscriber Count: জন গ্ৰাহক | {গণনা} গ্ৰাহক
Watching Count: চাই থকা | {গণনা} চাই আছে
View Count: দৰ্শন | {গণনা} দৰ্শন
Video Count: ভিডিঅ' | {গণনা} ভিডিঅ'
Channel Count: চেনেল | {গণনা} চেনেল
Comment Count: টা মন্তব্য | {count} মন্তব্য
Like Count: ৰ দৰে | {count} লাইক
Subscriber Count: জন গ্ৰাহক | {count} গ্ৰাহক
Watching Count: চাই থকা | {count} চাই আছে
View Count: দৰ্শন | {count} দৰ্শন
Video Count: ভিডিঅ' | {count} ভিডিঅ'
Channel Count: চেনেল | {count} চেনেল
Shorts: শ্বৰ্ট
Posts: পোষ্ট
Search Listing:

View File

@@ -368,8 +368,8 @@ Global:
Video Count: 1 video | {count} video
Watching Count: 1 baxır | {count} baxır
Channel Count: 1 kanal | {count} kanal
Like Count: 1 bəyəni | {sayı} bəyənmələr
Comment Count: 1 şərh | {sayı} şərhlər
Like Count: 1 bəyəni | {count} bəyənmələr
Comment Count: 1 şərh | {count} şərhlər
Videos: Videolar
Live: Canlı
Posts: Postlar
@@ -396,7 +396,7 @@ Feed:
Feed Last Updated: '{feedName} axının son yeniləmə: {date}'
Refresh Feed: '{subscriptionName} / yenilə'
Search character limit: Axtarış sorğusu {searchCharacterLimit} simvol həddindən çoxdur
Go to page: '{Page} səhifəyə keç'
Go to page: '{page} səhifəyə keç'
Close Banner: Banneri Bağla
Display Label: '{label}: {value}'
Yes, Delete: Bəli, Sil

View File

@@ -124,13 +124,13 @@ Global:
Live: লাইভ
Sort By: 'সাজানোর পদ্ধতি'
Counts:
Video Count: ভিডিও |{সমষ্টি }ভিডিও
Subscriber Count: সাবস্ক্রাইবার |{সমষ্টি }সাবস্ক্রাইবার
View Count: দেখা হয়েছে |{সমষ্টি }দর্শন সংখ্যা
Watching Count: দেখছি |{গণনা }দেখছেন
Channel Count: চ্যানেল |{সমষ্টি }চ্যানেল সমূহ
Like Count: ১টি পছন্দ | {গণনা} পছন্দ করা হয়েছে
Comment Count: ১টি মন্তব্য | {গণনা} মন্তব্য করা হয়েছে
Video Count: ভিডিও |{count}ভিডিও
Subscriber Count: সাবস্ক্রাইবার |{count}সাবস্ক্রাইবার
View Count: দেখা হয়েছে |{count}দর্শন সংখ্যা
Watching Count: দেখছি |{count}দেখছেন
Channel Count: চ্যানেল |{count}চ্যানেল সমূহ
Like Count: ১টি পছন্দ | {count} পছন্দ করা হয়েছে
Comment Count: ১টি মন্তব্য | {count} মন্তব্য করা হয়েছে
Posts: পোস্ট
External link opening has been disabled in the general settings: সাধারণ পছন্দসমূহে
বহিঃসংযোগ খোলা নিষ্ক্রিয় রাখা হয়েছে
@@ -191,7 +191,7 @@ User Playlists:
Are you sure you want to delete this playlist? This cannot be undone: আপনি কি নিশ্চিত
যে আপনি চালুতালিকাটি মুছতে চান? এটি পূর্বাবস্থায় ফেরানো সম্ভব নয়।
More: অতিরিক্ত
Go to page: পরবর্তী {পেইজ}
Go to page: পরবর্তী {page}
Search character limit: সার্চ কোয়েরি {searchCharacterLimit} অক্ষরের সীমা অতিক্রম
করেছে
Close Banner: ব্যানারটি বন্ধ করুন

View File

@@ -1175,7 +1175,7 @@ Screenshot Error: 'Tapadenn skramm c''hwitet. {error}'
Channel Hidden: '{channel} ouzhpennet da sil ar chadennoù'
Channel Unhidden: '{channel} dilamet deus sil ar chadennoù'
Trimmed input must be at least N characters long: 'An destenn bevennet a rank bezañ
1 arouezenn enni da nebeutañ | An destenn bevennet a rank bezañ {number} arouezenn
1 arouezenn enni da nebeutañ | An destenn bevennet a rank bezañ {length} arouezenn
enni da nebeutañ'
Tag already exists: 'Bez ez eus dija deus an dikedenn "{tagName}"'

View File

@@ -478,7 +478,7 @@ Settings:
Default Viewing Mode:
Theater: Teatro
Default Viewing Mode: Vista predeterminada
External Player: Reproductor externo ({nombreReproductorExterno})
External Player: Reproductor externo ({externalPlayerName})
Full Screen: Pantalla Completa
Picture in Picture: Imagen en imagen
Privacy Settings:

View File

@@ -865,7 +865,7 @@ Video:
CodecAudio: 'کدک: {audioCodec} ({audioItag})'
Dropped Frames / Total Frames: 'فریم‌های رد شده: {droppedFrames} / مجموع فریم‌ها:
{totalFrames}'
CodecsVideoAudio: 'کدک‌ها: {videoCodec} ({videotag}) / {audioCodec} ({audioItag})'
CodecsVideoAudio: 'کدک‌ها: {videoCodec} ({videoItag}) / {audioCodec} ({audioItag})'
CodecsVideoAudioNoItags: 'کدک‌ها: {videoCodec} / {audioCodec}'
Take Screenshot: گرفتن عکس صفحه
Show Stats: نمایش اطلاعات

View File

@@ -219,7 +219,7 @@ User Playlists:
Enable Quick Bookmark With This Playlist: Ota nopea kirjanmerkki käyttöön tällä
soittolistalla
Remove Duplicate Videos: Poista päällekkäiset videot
TotalTimePlaylist: 'Kokonaisaika: {kesto}'
TotalTimePlaylist: 'Kokonaisaika: {duration}'
Are you sure you want to remove {playlistItemCount} duplicate videos from this playlist? This cannot be undone: Haluatko
varmasti poistaa 1 päällekkäisen videon tästä soittolistasta? Tätä ei voi peruuttaa.
| Haluatko varmasti poistaa {playlistItemCount} päällekkäiset videot tästä soittolistasta?

View File

@@ -824,7 +824,7 @@ Channel:
Video hidden by FreeTube: Video disembunyikan oleh FreeTube
This channel currently does not have any posts: Kanal ini belum memiliki pos apa
pun
votes: '{Votes} memilih'
votes: '{votes} memilih'
View Full Post: Lihat Postingan Lengkap
Viewing Posts Only Supported By Invidious: Melihat Postingan hanya didukung oleh
Invidious. Buka tab komunitas saluran untuk melihat konten di sana tanpa Invidious.
@@ -939,7 +939,7 @@ Video:
Show Original Details: Tampilkan Detail Asli
Show Modified Details: Tampilkan Detail yang Dimodifikasi
Player:
TranslatedCaptionTemplate: '{bahasa} (diterjemahkan dari "{bahasaasli}")'
TranslatedCaptionTemplate: '{language} (diterjemahkan dari "{originalLanguage}")'
Audio Tracks: Trek Audio
Autoplay is off: Putar otomatis mati
Autoplay is on: Putar otomatis hidup
@@ -947,12 +947,12 @@ Video:
Stats:
Video ID: 'ID Video: {videoId}'
Bitrate: 'Kecepatan bit: {bitrate} kbps'
Volume: 'Volume: {volumePersentase}%'
Volume: 'Volume: {volumePercentage}%'
Buffered: 'Di-buffer: {bufferedPercentage}%'
CodecAudio: 'Kodek: {audioCodec} ({audioItag})'
Dropped Frames / Total Frames: 'Frame yang Dihilangkan: {droppedFrames} / Jumlah
Frame: {totalFrames}'
Media Formats: 'Format Media: {format}'
Media Formats: 'Format Media: {formats}'
CodecsVideoAudio: 'Kodek: {videoCodec} ({videoItag}) / {audioCodec} ({audioItag})'
Resolution: 'Resolusi: {width}x{height}@{frameRate}'
Player Dimensions: 'Dimensi Pemain: {width}x{height}'
@@ -1288,7 +1288,7 @@ Hashtag:
This hashtag does not currently have any videos: Tagar ini saat ini tidak memiliki
video apa pun
Hashtag: Hastage
Display Label: '{label}: {nilai}'
Display Label: '{label}: {value}'
Channel Hidden: '{channel} ditambahkan ke filter saluran'
Age Restricted:
This video is age restricted: Video ini dibatasi usia
@@ -1296,7 +1296,7 @@ Age Restricted:
Trimmed input must be at least N characters long: Input yang dipangkas harus memiliki
panjang minimal 1 karakter | Input yang dipangkas harus memiliki panjang minimal
{length} karakter
KeyboardShortcutTemplate: '{label} ({pintasan})'
KeyboardShortcutTemplate: '{label} ({shortcut})'
shortcutJoinOperator: +
Chapters:
Key Moments: Momen-momen penting

View File

@@ -196,7 +196,7 @@ User Playlists:
Are you sure you want to remove {playlistItemCount} watched videos from this playlist? This cannot be undone: Sei sicuro di voler rimuovere 1 video guardato da questa playlist? Questa operazione non può essere annullata. | Vuoi rimuovere {playlistItemCount} video guardati da questa playlist? Questa operazione non può essere annullata.
Export Playlist: Esporta questa playlist
The playlist has been successfully exported: La playlist è stata esportata correttamente
TotalTimePlaylist: 'Tempo totale: {durata}'
TotalTimePlaylist: 'Tempo totale: {duration}'
History:
# On History Page
History: 'Cronologia'

View File

@@ -158,7 +158,7 @@ Settings:
Middle: 'შუაში'
End: 'ბოლოში'
Current Invidious Instance: 'Invidious-ის მიმდინარე ასლი'
The currently set default instance is {instance}: 'ამჟამად დაყენებული ასლია {number}'
The currently set default instance is {instance}: 'ამჟამად დაყენებული ასლია {instance}'
No default instance has been set: 'სტანდარტული ასლი არ არის დაყენებული'
Current instance will be randomized on startup: 'ჩართვისას მიმდინარე ასლი იქნება
შემთხვევით არჩეული'

View File

@@ -138,7 +138,7 @@ User Playlists:
Allow Adding Duplicate Video(s): ڕێگە بدە بە زیادکردنی ڤیدیۆ(ەکان)ی دووبارە
"{videoCount}/{totalVideoCount} Videos Already Added": '{videoCount}/{totalVideoCount}
ڤیدیۆکان پێشتر زیادکراون'
Added {count} Times: “پێشتر زیاد کراوە | زیادکرا {ژمارە} کاتەکان
Added {count} Times: “پێشتر زیاد کراوە | زیادکرا {count} کاتەکان
Save: هەڵگرتن
N playlists selected: '{playlistCount} هەڵبژێردراوە'
Search in Playlists: لە پلەی لیستەکاندا بگەڕێ
@@ -225,7 +225,7 @@ User Playlists:
EarliestCreatedFirst: سەرەتاییترین دروستکراوە
EarliestUpdatedFirst: زووترین نوێکراوەتەوە
EarliestPlayedFirst: زووترین یاری
TotalTimePlaylist: 'کۆی گشتی کات: {ماوە}'
TotalTimePlaylist: 'کۆی گشتی کات: {duration}'
Playlists with Matching Videos: پلەی لیستەکان لەگەڵ ڤیدیۆی هاوتا
Edit Playlist Info: دەستکاریکردنی زانیاری پلەی لیست
Delete Playlist: سڕینەوەی پلەی لیست
@@ -512,7 +512,7 @@ Channels:
Unsubscribe Prompt: ئایا دڵنیای کە دەتەوێت بەشداریکردن لە "{channelName}" هەڵبوەشێنیتەوە؟
Channels: کەناڵەکان
Title: لیستی کەناڵەکان
Count: '{ژمارە} کەناڵ(ەکان) دۆزرایەوە.'
Count: '{number} کەناڵ(ەکان) دۆزرایەوە.'
Right-click or hold to see history: کلیک لە ڕاست یان هەڵبژاردن بەدوورەوە بۆ بینینی
مێژووی
Search Listing:
@@ -535,8 +535,8 @@ KeyboardShortcutPrompt:
Zoom Out: زووم کردنە دەرەوە
Fullscreen: شاشەکەت پرکەرەوە
Feed:
Feed Last Updated: '{feedName} فید دوایین نوێکردنەوە: {بەروار}'
Refresh Feed: نوێکردنەوە {ناوی بەشداریکردن}
Feed Last Updated: '{feedName} فید دوایین نوێکردنەوە: {date}'
Refresh Feed: نوێکردنەوە {subscriptionName}
More: زیاتر
Search character limit: پرسیاری گەڕان لە سەرووی سنووری کاراکتەری {searchCharacterLimit}
دایە

View File

@@ -834,7 +834,7 @@ Video:
CodecsVideoAudioNoItags: 'Codecuri: {videoCodec} / {audioCodec}'
Stats: Statistici
Video ID: 'ID videoclip: {videoId}'
Media Formats: 'Formate media: {formate}'
Media Formats: 'Formate media: {formats}'
Bitrate: 'Rată de biți: {bitrate} kbps'
Volume: 'Volum: {volumePercentage}%'
Bandwidth: 'Lățime de bandă: {bandwidth} kbps'
@@ -1113,7 +1113,7 @@ Keys:
checkmark:
Yes, Delete: da, șterge
Yes, Open Link: Da, Link deschis
Display Label: '{label}: {valoare}'
Display Label: '{label}: {value}'
KeyboardShortcutTemplate: '{label} ({shortcut})'
Yes, Restart: Da, repornire
shortcutJoinOperator: +

View File

@@ -241,7 +241,7 @@ User Playlists:
பிளேலிச்ட் இப்போது {oldPlaylistName} க்கு க்கு பதிலாக விரைவான புக்மார்க்குக்கு
பயன்படுத்தப்படுகிறது. செயல்தவிர்க்க இங்கே சொடுக்கு செய்க'
Reverted to use {oldPlaylistName} for quick bookmark: 'விரைவான புக்மார்க்குக்கு
{oldPlayListName} bea ஐப் பயன்படுத்த மாற்றப்பட்டது'
{oldPlaylistName} bea ஐப் பயன்படுத்த மாற்றப்பட்டது'
Some videos in the playlist are not loaded yet. Click here to copy anyway.: 'பிளேலிச்ட்டில்
உள்ள சில வீடியோக்கள் இன்னும் ஏற்றப்படவில்லை. எப்படியும் நகலெடுக்க இங்கே சொடுக்கு