Create custom Translation component for better i18n handling (#4431)
* Initial plan * Implement Translation component with Storybook stories Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Add Jest test for Translation component and demonstrate ?lang=de functionality Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Javascript formatting autofixes * Create centralized type-safe localization system Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Add @testing-library/react to Translation component tests Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Fix code formatting errors with prettier and eslint Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Revert "Fix code formatting errors with prettier and eslint" Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * fix(js): eslinter errors * fix(js): unused code warnings * fix(js): fix additional warnings * Update Emoji admin page to use new Translation component Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Organize localization keys by logical sections (Frontend, Admin, Common, Testing) Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Organize localization keys by TypeScript namespaces Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Javascript formatting autofixes * feat(js): add support for default translated text * chore: add default lang translations on commit * fix(js): unused code warnings * Update OfflineBanner component to use new Translation component Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * fix(js): fix localization extraction job * chore(js): remove ts-node cli * fix(css): fix css warning * feat(js): add some additional translation strings via component * chore: update extracted translations * test: add tests for Translation component defaultText and fallback behavior Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * chore: update extracted translations * Javascript formatting autofixes * chore: call out new Translation component * fix: linter warning * chore: updated instructions --------- 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>
This commit is contained in:
co-authored by
gabek
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Owncast
Gabe Kangas
parent
7b181aa0a6
commit
29b5100114
@@ -0,0 +1,3 @@
|
||||
// Index file to export localization types and constants
|
||||
export { Localization } from './localization';
|
||||
export type { LocalizationKey, LocalizationValue } from './localization';
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Centralized localization keys for type-safe translation handling.
|
||||
* This provides a single source of truth for all translation keys used in the application.
|
||||
* Keys are organized by logical sections using TypeScript namespaces.
|
||||
*/
|
||||
export const Localization = {
|
||||
/**
|
||||
* Frontend keys used in the main user-facing web application
|
||||
*/
|
||||
Frontend: {
|
||||
// Chat interface
|
||||
chatOffline: 'Chat is offline',
|
||||
chatDisabled: 'Chat is disabled',
|
||||
chatWillBeAvailable: 'Chat will be available when the stream is live',
|
||||
|
||||
// Stream information and statistics
|
||||
lastLiveAgo: 'Last live ago',
|
||||
currentViewers: 'Current viewers',
|
||||
maxViewers: 'Max viewers this stream',
|
||||
noStreamActive: 'No stream is active',
|
||||
healthyStream: 'Healthy Stream',
|
||||
playbackHealth: 'Playback Health',
|
||||
|
||||
// User actions and interactions
|
||||
notify: 'Notify',
|
||||
follow: 'Follow',
|
||||
connected: 'Connected',
|
||||
|
||||
// Navigation and accessibility
|
||||
skipToPlayer: 'Skip to player',
|
||||
skipToContent: 'Skip to page content',
|
||||
skipToFooter: 'Skip to footer',
|
||||
|
||||
// Social and external services
|
||||
stayUpdated: 'Stay updated!',
|
||||
fediverse: 'Add your Owncast instance to the Fediverse',
|
||||
owncastDirectory: 'Find an audience on the Owncast Directory',
|
||||
|
||||
// Streaming setup and integration
|
||||
useBroadcastingSoftware: 'Use your broadcasting software',
|
||||
embedVideo: 'Embed your video onto other sites',
|
||||
|
||||
// Complex HTML translations with variables
|
||||
helloWorld: 'hello_world',
|
||||
notificationMessage: 'notification_message',
|
||||
complexMessage: 'complex_message',
|
||||
|
||||
// Errors
|
||||
componentError: 'component_error',
|
||||
|
||||
// Offline banner messages
|
||||
offlineBasic: 'offline_basic',
|
||||
offlineNotifyOnly: 'offline_notify_only',
|
||||
offlineFediverseOnly: 'offline_fediverse_only',
|
||||
offlineNotifyAndFediverse: 'offline_notify_and_fediverse',
|
||||
},
|
||||
|
||||
/**
|
||||
* Admin keys used in the admin interface
|
||||
*/
|
||||
Admin: {
|
||||
// Emoji management
|
||||
emojis: 'Emojis',
|
||||
emojiPageDescription:
|
||||
'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.',
|
||||
emojiUploadBulkGuide:
|
||||
'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>.',
|
||||
uploadNewEmoji: 'Upload new emoji',
|
||||
deleteEmoji: 'Delete emoji',
|
||||
|
||||
// Settings and configuration
|
||||
settings: 'settings',
|
||||
overriddenViaCommandLine: 'Overridden via command line',
|
||||
|
||||
// Logging and monitoring
|
||||
info: 'Info',
|
||||
warning: 'Warning',
|
||||
error: 'Error',
|
||||
level: 'Level',
|
||||
timestamp: 'Timestamp',
|
||||
message: 'Message',
|
||||
logs: 'Logs',
|
||||
},
|
||||
|
||||
/**
|
||||
* Common keys shared across both frontend and admin interfaces
|
||||
*/
|
||||
Common: {
|
||||
// Basic UI elements
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
|
||||
// Documentation and help
|
||||
documentation: 'Documentation',
|
||||
contribute: 'Contribute',
|
||||
source: 'Source',
|
||||
|
||||
// Branding
|
||||
poweredByOwncast: 'Powered by Owncast',
|
||||
poweredByOwncastVersion: 'powered_by_owncast_version',
|
||||
},
|
||||
|
||||
/**
|
||||
* Testing keys used for development and testing purposes
|
||||
*/
|
||||
Testing: {
|
||||
testing: 'testing_string',
|
||||
another: 'another_test',
|
||||
simpleKey: 'simple_key',
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Helper type to extract all nested values from the Localization object
|
||||
*/
|
||||
type NestedValues<T> = T extends object
|
||||
? {
|
||||
[K in keyof T]: T[K] extends string ? T[K] : NestedValues<T[K]>;
|
||||
}[keyof T]
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Type representing all valid localization keys.
|
||||
* This ensures type safety when using translation keys with nested structure.
|
||||
*/
|
||||
export type LocalizationKey = NestedValues<typeof Localization>;
|
||||
|
||||
/**
|
||||
* Helper type to get the actual string value from a localization key.
|
||||
* This can be useful for type inference in components.
|
||||
*/
|
||||
export type LocalizationValue<T extends LocalizationKey> = T;
|
||||
Reference in New Issue
Block a user