Add pluralization support to Translation component (#4441)

* Initial plan

* Add pluralization support to Translation component with comprehensive tests

Co-authored-by: gabek <414923+gabek@users.noreply.github.com>

* Javascript formatting autofixes

* Update web/tests/translation.test.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update web/components/ui/Translation/Translation.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: linter warning

* Simplify pluralization logic: use original key for plural, only _one for singular

Co-authored-by: gabek <414923+gabek@users.noreply.github.com>

* feat(i18n): fix support for nested namespace string and key extraction

* chore: update extracted translations

* fix(i18n): fix linter warnings in extraction script

* Update web/scripts/i18n-extract.js

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat(i18n): sort translation keys

* fix(i18n): fix linter warnings

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gabek <414923+gabek@users.noreply.github.com>
Co-authored-by: Owncast <owncast@owncast.online>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot
2025-07-13 17:34:06 -07:00
committed by GitHub
co-authored by gabek Copilot copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Owncast Gabe Kangas
parent 1fd86fa190
commit 59b905649e
9 changed files with 392 additions and 71 deletions
+50 -51
View File
@@ -1,52 +1,54 @@
{
"Add your Owncast instance to the Fediverse": "Add your Owncast instance to the Fediverse",
"Admin.directoryDescription": "Increase your audience by appearing in the <a href=\"https://directory.owncast.online\" target=\"_blank\" rel=\"noreferrer\"><strong>Owncast Directory</strong></a>. This is an external service run by the Owncast project. <a href=\"https://owncast.online/docs/directory/?source=admin\" target=\"_blank\" rel=\"noopener noreferrer\">Learn more</a>.",
"Admin.EditInstanceDetails.directoryDescription": "Increase your audience by appearing in the <a href=\"https://directory.owncast.online\" target=\"_blank\" rel=\"noreferrer\"><strong>Owncast Directory</strong></a>. This is an external service run by the Owncast project. <a href=\"https://owncast.online/docs/directory/?source=admin\" target=\"_blank\" rel=\"noopener noreferrer\">Learn more</a>.",
"Admin.EditInstanceDetails.offlineMessageDescription": "The offline message is displayed to your page visitors when you're not streaming. Markdown is supported.",
"Admin.EditInstanceDetails.serverUrlRequiredForDirectory": "You must set your <strong>Server URL</strong> above to enable the directory.",
"Admin.offlineMessageDescription": "The offline message is displayed to your page visitors when you're not streaming. Markdown is supported.",
"Admin.serverUrlRequiredForDirectory": "You must set your <strong>Server URL</strong> above to enable the directory.",
"Admin": {
"EditInstanceDetails": {
"directoryDescription": "Increase your audience by appearing in the <a href=\"https://directory.owncast.online\" target=\"_blank\" rel=\"noreferrer\"><strong>Owncast Directory</strong></a>. This is an external service run by the Owncast project. <a href=\"https://owncast.online/docs/directory/?source=admin\" target=\"_blank\" rel=\"noopener noreferrer\">Learn more</a>.",
"offlineMessageDescription": "The offline message is displayed to your page visitors when you're not streaming. Markdown is supported.",
"serverUrlRequiredForDirectory": "You must set your <strong>Server URL</strong> above to enable the directory."
},
"emojiPageDescription": "<strong><em>Missing translation Admin.emojiPageDescription: Please report</em></strong>",
"emojiUploadBulkGuide": "<strong><em>Missing translation Admin.emojiUploadBulkGuide: Please report</em></strong>",
"emojis": "<strong><em>Missing translation Admin.emojis: Please report</em></strong>",
"uploadNewEmoji": "<strong><em>Missing translation Admin.uploadNewEmoji: Please report</em></strong>"
},
"Banned Users": "Banned Users",
"Bring in moderators to help keep your chat in order": "Bring in moderators to help keep your chat in order.",
"CPU": "CPU",
"Chat Messages": "Chat Messages",
"Chat is disabled": "Chat is disabled",
"Chat is offline": "Chat is offline",
"Chat Messages": "Chat Messages",
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
"Chat will continue to be disabled until you begin a live stream": "Chat will continue to be disabled until you begin a live stream.",
"Click and never miss future streams!": "Click and never miss future streams!",
"Common": {
"poweredByOwncastVersion": "Powered by <a href='https://owncast.online'>Owncast v{{versionNumber}}</a>"
},
"Common tasks": "Common tasks",
"Common.poweredByOwncastVersion": "Powered by <a href='https://owncast.online'>Owncast v{{versionNumber}}</a>",
"complex_message": "This is a <em>complex</em> message with <strong>{{count}}</strong> viewers and <code>{{status}}</code> status.",
"Connected": "Connected",
"Contribute": "Contribute",
"CPU": "CPU",
"Current stream": "Current stream",
"Current viewers": "Current viewers",
"Delete emoji": "Delete emoji",
"developer APIs": "developer APIs.",
"discussions": "discussions",
"Disk": "Disk",
"documentation": "documentation",
"Documentation": "Documentation",
"Embed your video onto other sites": "Embed your video onto other sites",
"Emoji guide": "Emoji guide",
"Emojis": "Emojis",
"Enable Owncast social features": "Enable Owncast social features",
"Error": "Error",
"FAQ": "FAQ",
"Find an audience on the Owncast Directory": "Find an audience on the Owncast Directory",
"Fix your problems": "Fix your problems",
"Follow": "Follow",
"Frontend.componentError": "Error: {{message}}",
"Frontend.offlineBasic": "This stream is offline. Check back soon!",
"Frontend.offlineFediverseOnly": "This stream is offline. <span class='follow-link'>Follow</span> {{fediverseAccount}} on the Fediverse to see the next time {{streamer}} goes live.",
"Frontend.offlineNotifyAndFediverse": "This stream is offline. You can <span class='notify-link'>be notified</span> the next time {{streamer}} goes live or <span class='follow-link'>follow</span> {{fediverseAccount}} on the Fediverse.",
"Frontend.offlineNotifyOnly": "This stream is offline. <span class='notify-link'>Be notified</span> the next time {{streamer}} goes live.",
"Frontend": {
"chatOffline": "<strong><em>Missing translation Frontend.chatOffline: Please report</em></strong>",
"componentError": "Error: {{message}}",
"helloWorld": "<strong><em>Missing translation Frontend.helloWorld: Please report</em></strong>",
"notificationMessage": "<strong><em>Missing translation Frontend.notificationMessage: Please report</em></strong>",
"offlineBasic": "This stream is offline. Check back soon!",
"offlineFediverseOnly": "This stream is offline. <span class='follow-link'>Follow</span> {{fediverseAccount}} on the Fediverse to see the next time {{streamer}} goes live.",
"offlineNotifyAndFediverse": "This stream is offline. You can <span class='notify-link'>be notified</span> the next time {{streamer}} goes live or <span class='follow-link'>follow</span> {{fediverseAccount}} on the Fediverse.",
"offlineNotifyOnly": "This stream is offline. <span class='notify-link'>Be notified</span> the next time {{streamer}} goes live."
},
"Hardware Info": "Hardware Info",
"Healthy Stream": "Healthy Stream",
"hello_world": "Hello <strong>{{name}}</strong>, welcome to the world!",
"Help configuring my broadcasting software": "Help configuring my broadcasting software",
"Here you can upload new custom emojis for usage in the chat. When uploading a new emoji, the filename without extension will be used as emoji name. Additionally, emoji names are case-insensitive. For best results, ensure all emoji have unique names.": "Here you can upload new custom emojis for usage in the chat. When uploading a new emoji, the filename without extension will be used as emoji name. Additionally, emoji names are case-insensitive. For best results, ensure all emoji have unique names.",
"Hidden messages": "Hidden messages",
"Hide": "Hide",
"How can we help you?": "How can we help you?",
@@ -58,13 +60,13 @@
"I want to embed my stream into another site": "I want to embed my stream into another site",
"I want to tweak my video output": "I want to tweak my video output",
"I want to use an external storage provider": "I want to use an external storage provider",
"IP Bans": "IP Bans",
"If you found a bug, then please": "If you found a bug, then please",
"Inbound Audio Stream": "Inbound Audio Stream",
"Inbound Stream Details": "Inbound Stream Details",
"Inbound Video Stream": "Inbound Video Stream",
"Info": "Info",
"Input": "Input",
"IP Bans": "IP Bans",
"Last 12 hours": "Last 12 hours",
"Last 24 hours": "Last 24 hours",
"Last 3 months": "Last 3 months",
@@ -76,7 +78,6 @@
"Learn how you can add your Owncast stream to other sites you control": "Learn how you can add your Owncast stream to other sites you control.",
"Learn more": "Learn more",
"Learn more about chat moderation here": "Learn more about chat moderation here.",
"let us know": "let us know",
"Level": "Level",
"Link": "Link",
"List yourself in the Owncast Directory and show off your stream": {
@@ -84,7 +85,6 @@
},
"Logs": "Logs",
"Manage the messages from viewers that show up on your stream": "Manage the messages from viewers that show up on your stream.",
"max viewers": "max viewers",
"Max viewers last stream": "Max viewers last stream",
"Max viewers this stream": "Max viewers this stream",
"Memory": "Memory",
@@ -97,18 +97,7 @@
"No news": "No news.",
"No stream is active": "No stream is active",
"No viewer data has been collected yet": "No viewer data has been collected yet.",
"notification_message": "You can <a href=\"#\">click here</a> to receive notifications when {{streamer}} goes live.",
"Notify": "Notify",
"of all known players": {
" Other player status is unknown": "of all known players. Other player status is unknown."
},
"offline": "offline",
"offline_basic": "This stream is offline. Check back soon!",
"offline_fediverse_only": "This stream is offline. <span class='follow-link'>Follow</span> {{fediverseAccount}} on the Fediverse to see the next time {{streamer}} goes live.",
"offline_notify_and_fediverse": "This stream is offline. You can <span class='notify-link'>be notified</span> the next time {{streamer}} goes live or <span class='follow-link'>follow</span> {{fediverseAccount}} on the Fediverse.",
"offline_notify_only": "This stream is offline. <span class='notify-link'>Be notified</span> the next time {{streamer}} goes live.",
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
"or exist in our": "or exist in our",
"Other": "Other",
"Outbound Audio Stream": "Outbound Audio Stream",
"Outbound Stream Details": "Outbound Stream Details",
@@ -117,9 +106,7 @@
"Peak viewer count": "Peak viewer count",
"Playback Health": "Playback Health",
"Please wait": "Please wait",
"Powered by Owncast": "Powered by Owncast",
"Read the Docs": "Read the Docs",
"settings": "settings.",
"Show": "Show",
"Skip to footer": "Skip to footer",
"Skip to offline message": "Skip to offline message",
@@ -129,29 +116,41 @@
"Stay updated!": "Stay updated!",
"Stream health represents": "Stream health represents",
"Stream started": "Stream started",
"Testing.simpleKey": "This default text should be ignored",
"the next time goes live": "the next time goes live",
"This stream is offline": "This stream is offline.",
"TROUBLESHOOT": "TROUBLESHOOT",
"Testing": {
"itemCount": "<strong><em>Missing translation Testing.itemCount: Please report</em></strong>",
"messageCount": "<strong><em>Missing translation Testing.messageCount: Please report</em></strong>",
"noPluralKey": "<strong><em>Missing translation Testing.noPluralKey: Please report</em></strong>",
"simpleKey": "<strong><em>Missing translation Testing.simpleKey: Please report</em></strong>"
},
"Time": "Time",
"Timestamp": "Timestamp",
"to configure additional details about your viewers": "to configure additional details about your viewers.",
"to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream": "to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream.",
"TROUBLESHOOT": "TROUBLESHOOT",
"Troubleshooting": "Troubleshooting",
"Upload new emoji": "Upload new emoji",
"Use your broadcasting software": "Use your broadcasting software",
"used": "used",
"User": "User",
"View": "View",
"Viewer Info": "Viewer Info",
"Viewers": "Viewers",
"Visible messages": "Visible messages",
"Visit the": "Visit the",
"Want to upload custom emojis in bulk? Check out our": "Want to upload custom emojis in bulk? Check out our",
"Want to upload custom emojis in bulk? Check out our <a href=\"https://owncast.online/docs/chat/emoji\" rel=\"noopener noreferrer\" target=\"_blank\">Emoji guide</a>.": "Want to upload custom emojis in bulk? Check out our <a href=\"https://owncast.online/docs/chat/emoji\" rel=\"noopener noreferrer\" target=\"_blank\">Emoji guide</a>.",
"Warning": "Warning",
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "When a stream is active and chat is enabled, connected chat clients will be displayed here.",
"Yes": "Yes",
"You can build your own bots, overlays, tools and add-ons with our": "You can build your own bots, overlays, tools and add-ons with our",
"You should start one": "You should start one."
"You should start one": "You should start one.",
"developer APIs": "developer APIs.",
"discussions": "discussions",
"documentation": "documentation",
"let us know": "let us know",
"max viewers": "max viewers",
"of all known players": {
" Other player status is unknown": "of all known players. Other player status is unknown."
},
"offline": "offline",
"offline_basic": "This stream is offline. Check back soon!",
"or exist in our": "or exist in our",
"settings": "settings.",
"to configure additional details about your viewers": "to configure additional details about your viewers.",
"to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream": "to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream.",
"used": "used"
}
+32
View File
@@ -0,0 +1,32 @@
{
"Testing": {
"simpleKey": "<strong><em>Missing translation Testing.simpleKey: Please report</em></strong>",
"itemCount": "<strong><em>Missing translation Testing.itemCount: Please report</em></strong>",
"messageCount": "<strong><em>Missing translation Testing.messageCount: Please report</em></strong>",
"noPluralKey": "<strong><em>Missing translation Testing.noPluralKey: Please report</em></strong>"
},
"Frontend": {
"helloWorld": "<strong><em>Missing translation Frontend.helloWorld: Please report</em></strong>",
"notificationMessage": "<strong><em>Missing translation Frontend.notificationMessage: Please report</em></strong>",
"chatOffline": "<strong><em>Missing translation Frontend.chatOffline: Please report</em></strong>",
"offlineNotifyAndFediverse": "This stream is offline. You can <span class='notify-link'>be notified</span> the next time {{streamer}} goes live or <span class='follow-link'>follow</span> {{fediverseAccount}} on the Fediverse.",
"offlineNotifyOnly": "This stream is offline. <span class='notify-link'>Be notified</span> the next time {{streamer}} goes live.",
"offlineFediverseOnly": "This stream is offline. <span class='follow-link'>Follow</span> {{fediverseAccount}} on the Fediverse to see the next time {{streamer}} goes live.",
"offlineBasic": "This stream is offline. Check back soon!",
"componentError": "Error: {{message}}"
},
"Admin": {
"emojis": "<strong><em>Missing translation Admin.emojis: Please report</em></strong>",
"emojiPageDescription": "<strong><em>Missing translation Admin.emojiPageDescription: Please report</em></strong>",
"emojiUploadBulkGuide": "<strong><em>Missing translation Admin.emojiUploadBulkGuide: Please report</em></strong>",
"uploadNewEmoji": "<strong><em>Missing translation Admin.uploadNewEmoji: Please report</em></strong>",
"EditInstanceDetails": {
"offlineMessageDescription": "The offline message is displayed to your page visitors when you're not streaming. Markdown is supported.",
"directoryDescription": "Increase your audience by appearing in the <a href=\"https://directory.owncast.online\" target=\"_blank\" rel=\"noreferrer\"><strong>Owncast Directory</strong></a>. This is an external service run by the Owncast project. <a href=\"https://owncast.online/docs/directory/?source=admin\" target=\"_blank\" rel=\"noopener noreferrer\">Learn more</a>.",
"serverUrlRequiredForDirectory": "You must set your <strong>Server URL</strong> above to enable the directory."
}
},
"Common": {
"poweredByOwncastVersion": "Powered by <a href='https://owncast.online'>Owncast v{{versionNumber}}</a>"
}
}