From 4c7356903d523335e720d659dfa591921fe90ac6 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 23 May 2026 01:34:26 +0200 Subject: [PATCH 01/22] add i18n-ally configuration for localization support [no ci] --- .vscode/extensions.json | 6 +++++- .vscode/settings.json | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 434432fea..34b9eba94 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,7 @@ { - "recommendations": ["webpro.vscode-knip", "oxc.oxc-vscode"] + "recommendations": [ + "webpro.vscode-knip", + "oxc.oxc-vscode", + "lokalise.i18n-ally" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 60018f71d..82961cddb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,22 @@ "editor.defaultFormatter": "oxc.oxc-vscode" }, "nixEnvSelector.nixFile": "${workspaceFolder}/flake.nix", - "nixEnvSelector.useFlakes": true + "nixEnvSelector.useFlakes": true, + "i18n-ally.localesPaths": [ + "public/locales" + ], + "i18n-ally.dirStructure": "auto", + "i18n-ally.enabledFrameworks": [ + "react", + "react-i18next" + ], + "i18n-ally.extract.keyMaxLength": 75, + "i18n-ally.extract.targetPickingStrategy": "most-similar", + "i18n-ally.keystyle": "nested", + "i18n-ally.extract.keygenStyle": "snake_case", + "i18n-ally.extract.ignoredByFiles": { + "src/app/components/DeviceVerificationSetup.tsx": [ + "Column" + ] + } } From a4eb7a3a0c3c15702ad4c8913ea0b20143d3d7b4 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 23 May 2026 01:34:54 +0200 Subject: [PATCH 02/22] integrate localization for device verification setup messages --- public/locales/en.json | 30 +++++++++++++ .../components/DeviceVerificationSetup.tsx | 45 +++++++++---------- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 7a2534b8f..27af76883 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -3,5 +3,35 @@ "RoomCommon": { "changed_room_name": " changed room name" } + }, + "Settings": { + "device_verification_setup": { + "error_uia_action_without_data": "Unexpected Error! UIA action is perform without data.", + "authentication_failed_failed_to_setup_device_verification": "Authentication failed! Failed to setup device verification.", + "unexpected_error_crypto_module_not_found": "Unexpected Error! Crypto module not found!", + "unexpected_error_failed_to_create_recovery_key": "Unexpected Error! Failed to create recovery key.", + "generate_a": "Generate a", + "for_verifying_identity_if_you_do_not_have_access_to_other_devices_additiona": "for verifying identity if you do not have access to other devices. Additionally, setup a passphrase as a memorable alternative.", + "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Authentication steps to perform this action are not supported by client.", + "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Store the Recovery Key in a safe place for future use, as you will need it to verify your identity if you do not have access to other devices.", + "setup_device_verification": "Setup Device Verification", + "reset_device_verification": "Reset Device Verification", + "resetting_device_verification_is_permanent": "Resetting device verification is permanent.", + "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Anyone you have verified with will see security alerts and your encryption backup will be lost. You almost certainly do not want to do this, unless you have lost", + "and_every_device_you_can_verify_from": "and every device you can verify from." + } + }, + "General": { + "continue": "Continue", + "passphrase": "Passphrase", + "optional": "optional", + "unexpected_error": "Unexpected Error!", + "recovery_key": "Recovery Key", + "download": "Download", + "copy": "Copy", + "hide": "Hide", + "show": "Show", + "recovery_passphrase": "Recovery Passphrase", + "reset": "Reset" } } diff --git a/src/app/components/DeviceVerificationSetup.tsx b/src/app/components/DeviceVerificationSetup.tsx index 34bfa8d90..19902b4f0 100644 --- a/src/app/components/DeviceVerificationSetup.tsx +++ b/src/app/components/DeviceVerificationSetup.tsx @@ -27,6 +27,7 @@ import { useAlive } from '$hooks/useAlive'; import { PasswordInput } from './password-input'; import { ActionUIA, ActionUIAFlowsLoader } from './ActionUIA'; import { UseStateProvider } from './UseStateProvider'; +import { t } from 'i18next'; type UIACallback = ( authDict: AuthDict | null @@ -82,7 +83,7 @@ function SetupVerification({ onComplete }: Readonly) { const handleAction = useCallback( async (authDict: AuthDict) => { if (!uiaAction) { - throw new Error('Unexpected Error! UIA action is perform without data.'); + throw new Error(t('Settings.device_verification_setup.error_uia_action_without_data')); } if (alive()) { setNextAuthData(null); @@ -125,7 +126,7 @@ function SetupVerification({ onComplete }: Readonly) { if (alive()) { setUIAAction(action); } else { - reject(new Error('Authentication failed! Failed to setup device verification.')); + reject(new Error(t('Settings.device_verification_setup.authentication_failed_failed_to_setup_device_verification'))); } return; } @@ -139,11 +140,11 @@ function SetupVerification({ onComplete }: Readonly) { useCallback( async (passphrase) => { const crypto = mx.getCrypto(); - if (!crypto) throw new Error('Unexpected Error! Crypto module not found!'); + if (!crypto) throw new Error(t('Settings.device_verification_setup.unexpected_error_crypto_module_not_found')); const recoveryKeyData = await crypto.createRecoveryKeyFromPassphrase(passphrase); if (!recoveryKeyData.encodedPrivateKey) { - throw new Error('Unexpected Error! Failed to create recovery key.'); + throw new Error(t('Settings.device_verification_setup.unexpected_error_failed_to_create_recovery_key')); } clearSecretStorageKeys(); @@ -184,11 +185,10 @@ function SetupVerification({ onComplete }: Readonly) { return ( - Generate a Recovery Key for verifying identity if you do not have access to other - devices. Additionally, setup a passphrase as a memorable alternative. + {t('Settings.device_verification_setup.generate_a')} {t('General.recovery_key')} {t('Settings.device_verification_setup.for_verifying_identity_if_you_do_not_have_access_to_other_devices_additiona')} - Passphrase (Optional) + {t('General.passphrase')} ({t('General.optional')}) {setupState.status === AsyncStatus.Error && ( - {setupState.error ? setupState.error.message : 'Unexpected Error!'} + {setupState.error ? setupState.error.message : t('General.unexpected_error')} )} {nextAuthData !== null && uiaAction && ( @@ -208,7 +208,7 @@ function SetupVerification({ onComplete }: Readonly) { authData={nextAuthData ?? uiaAction.authData} unsupported={() => ( - Authentication steps to perform this action are not supported by client. + {t('Settings.device_verification_setup.authentication_steps_to_perform_this_action_are_not_supported_by_client')} )} > @@ -248,11 +248,10 @@ function RecoveryKeyDisplay({ recoveryKey }: Readonly) return ( - Store the Recovery Key in a safe place for future use, as you will need it to verify your - identity if you do not have access to other devices. + {t('Settings.device_verification_setup.store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t')} - Recovery Key + {t('General.recovery_key')} ) {safeToDisplayKey} setShow(!show)} variant="Secondary" radii="Pill"> - {show ? 'Hide' : 'Show'} + {show ? t('General.hide') : t('General.show')} @@ -301,7 +300,7 @@ export const DeviceVerificationSetup = forwardRef - Setup Device Verification + {t('Settings.device_verification_setup.setup_device_verification')} @@ -336,7 +335,7 @@ export const DeviceVerificationReset = forwardRef - Reset Device Verification + {t('Settings.device_verification_setup.reset_device_verification')} @@ -358,16 +357,14 @@ export const DeviceVerificationReset = forwardRef ✋🧑‍🚒🤚 - Resetting device verification is permanent. + {t('Settings.device_verification_setup.resetting_device_verification_is_permanent')} - Anyone you have verified with will see security alerts and your encryption backup - will be lost. You almost certainly do not want to do this, unless you have lost{' '} - Recovery Key or Recovery Passphrase and every device you can verify - from. + {t('Settings.device_verification_setup.anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption')}{' '} + {t('General.recovery_key')} or {t('General.recovery_passphrase')} {t('Settings.device_verification_setup.and_every_device_you_can_verify_from')} )} From 1cacfd3c6ec6a0bf32f5b234fb5f804e78a14933 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 23 May 2026 01:41:34 +0200 Subject: [PATCH 03/22] update device verification messages for localization support --- public/locales/en.json | 22 +++++++++-- src/app/components/DeviceVerification.tsx | 37 ++++++++++--------- .../components/DeviceVerificationSetup.tsx | 24 ++++++------ 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 27af76883..a10128e2f 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -5,7 +5,7 @@ } }, "Settings": { - "device_verification_setup": { + "device_verification": { "error_uia_action_without_data": "Unexpected Error! UIA action is perform without data.", "authentication_failed_failed_to_setup_device_verification": "Authentication failed! Failed to setup device verification.", "unexpected_error_crypto_module_not_found": "Unexpected Error! Crypto module not found!", @@ -18,7 +18,20 @@ "reset_device_verification": "Reset Device Verification", "resetting_device_verification_is_permanent": "Resetting device verification is permanent.", "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Anyone you have verified with will see security alerts and your encryption backup will be lost. You almost certainly do not want to do this, unless you have lost", - "and_every_device_you_can_verify_from": "and every device you can verify from." + "and_every_device_you_can_verify_from": "and every device you can verify from.", + "please_accept_the_request_from_other_device": "Please accept the request from other device.", + "click_accept_to_start_the_verification_process": "Click accept to start the verification process.", + "waiting_for_request_to_be_accepted": "Waiting for request to be accepted...", + "confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order": "Confirm the emoji below are displayed on both devices, in the same order:", + "they_match": "They Match", + "do_not_match": "Do not Match", + "starting_verification_using_emoji_comparison": "Starting verification using emoji comparison...", + "your_device_is_verified": "Your device is verified.", + "verification_has_been_canceled": "Verification has been canceled.", + "device_verification": "Device Verification", + "unexpected_error_verification_is_started_but_verifier_is_missing": "Unexpected Error! Verification is started but verifier is missing.", + "verification_request_has_been_accepted": "Verification request has been accepted.", + "waiting_for_the_response_from_other_device": "Waiting for the response from other device..." } }, "General": { @@ -32,6 +45,9 @@ "hide": "Hide", "show": "Show", "recovery_passphrase": "Recovery Passphrase", - "reset": "Reset" + "reset": "Reset", + "close": "Close", + "accept": "Accept", + "okay": "Okay" } } diff --git a/src/app/components/DeviceVerification.tsx b/src/app/components/DeviceVerification.tsx index aedfe8e1e..af7e256d7 100644 --- a/src/app/components/DeviceVerification.tsx +++ b/src/app/components/DeviceVerification.tsx @@ -27,6 +27,7 @@ import { } from '$hooks/useVerificationRequest'; import { AsyncStatus, useAsyncCallback } from '$hooks/useAsyncCallback'; import { ContainerColor } from '$styles/ContainerColor.css'; +import { t } from 'i18next'; const DialogHeaderStyles: CSSProperties = { padding: `0 ${config.space.S200} 0 ${config.space.S400}`, @@ -51,7 +52,7 @@ function VerificationUnexpected({ message, onClose }: VerificationUnexpectedProp {message} ); @@ -60,8 +61,8 @@ function VerificationUnexpected({ message, onClose }: VerificationUnexpectedProp function VerificationWaitAccept() { return ( - Please accept the request from other device. - + {t('Settings.device_verification.please_accept_the_request_from_other_device')} + ); } @@ -75,7 +76,7 @@ function VerificationAccept({ onAccept }: VerificationAcceptProps) { const accepting = acceptState.status === AsyncStatus.Loading; return ( - Click accept to start the verification process. + {t('Settings.device_verification.click_accept_to_start_the_verification_process')} ); @@ -92,8 +93,8 @@ function VerificationAccept({ onAccept }: VerificationAcceptProps) { function VerificationWaitStart() { return ( - Verification request has been accepted. - + {t('Settings.device_verification.verification_request_has_been_accepted')} + ); } @@ -108,7 +109,7 @@ function AutoVerificationStart({ onStart }: VerificationStartProps) { return ( - + ); } @@ -130,7 +131,7 @@ function CompareEmoji({ sasData }: { sasData: ShowSasCallbacks }) { return ( - Confirm the emoji below are displayed on both devices, in the same order: + {t('Settings.device_verification.confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order')} } > - They Match + {t('Settings.device_verification.they_match')} @@ -191,7 +192,7 @@ function SasVerification({ verifier, onCancel }: SasVerificationProps) { return ( - + ); } @@ -203,10 +204,10 @@ function VerificationDone({ onExit }: VerificationDoneProps) { return (
- Your device is verified. + {t('Settings.device_verification.your_device_is_verified')}
); @@ -218,9 +219,9 @@ type VerificationCanceledProps = { function VerificationCanceled({ onClose }: VerificationCanceledProps) { return ( - Verification has been canceled. + {t('Settings.device_verification.verification_has_been_canceled')} ); @@ -270,7 +271,7 @@ export function DeviceVerification({ request, onExit }: DeviceVerificationProps)
- Device Verification + {t('Settings.device_verification.device_verification')} @@ -294,7 +295,7 @@ export function DeviceVerification({ request, onExit }: DeviceVerificationProps) ) : ( ))} diff --git a/src/app/components/DeviceVerificationSetup.tsx b/src/app/components/DeviceVerificationSetup.tsx index 19902b4f0..f67086976 100644 --- a/src/app/components/DeviceVerificationSetup.tsx +++ b/src/app/components/DeviceVerificationSetup.tsx @@ -83,7 +83,7 @@ function SetupVerification({ onComplete }: Readonly) { const handleAction = useCallback( async (authDict: AuthDict) => { if (!uiaAction) { - throw new Error(t('Settings.device_verification_setup.error_uia_action_without_data')); + throw new Error(t('Settings.device_verification.error_uia_action_without_data')); } if (alive()) { setNextAuthData(null); @@ -126,7 +126,7 @@ function SetupVerification({ onComplete }: Readonly) { if (alive()) { setUIAAction(action); } else { - reject(new Error(t('Settings.device_verification_setup.authentication_failed_failed_to_setup_device_verification'))); + reject(new Error(t('Settings.device_verification.authentication_failed_failed_to_setup_device_verification'))); } return; } @@ -140,11 +140,11 @@ function SetupVerification({ onComplete }: Readonly) { useCallback( async (passphrase) => { const crypto = mx.getCrypto(); - if (!crypto) throw new Error(t('Settings.device_verification_setup.unexpected_error_crypto_module_not_found')); + if (!crypto) throw new Error(t('Settings.device_verification.unexpected_error_crypto_module_not_found')); const recoveryKeyData = await crypto.createRecoveryKeyFromPassphrase(passphrase); if (!recoveryKeyData.encodedPrivateKey) { - throw new Error(t('Settings.device_verification_setup.unexpected_error_failed_to_create_recovery_key')); + throw new Error(t('Settings.device_verification.unexpected_error_failed_to_create_recovery_key')); } clearSecretStorageKeys(); @@ -185,7 +185,7 @@ function SetupVerification({ onComplete }: Readonly) { return ( - {t('Settings.device_verification_setup.generate_a')} {t('General.recovery_key')} {t('Settings.device_verification_setup.for_verifying_identity_if_you_do_not_have_access_to_other_devices_additiona')} + {t('Settings.device_verification.generate_a')} {t('General.recovery_key')} {t('Settings.device_verification.for_verifying_identity_if_you_do_not_have_access_to_other_devices_additiona')} {t('General.passphrase')} ({t('General.optional')}) @@ -208,7 +208,7 @@ function SetupVerification({ onComplete }: Readonly) { authData={nextAuthData ?? uiaAction.authData} unsupported={() => ( - {t('Settings.device_verification_setup.authentication_steps_to_perform_this_action_are_not_supported_by_client')} + {t('Settings.device_verification.authentication_steps_to_perform_this_action_are_not_supported_by_client')} )} > @@ -248,7 +248,7 @@ function RecoveryKeyDisplay({ recoveryKey }: Readonly) return ( - {t('Settings.device_verification_setup.store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t')} + {t('Settings.device_verification.store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t')} {t('General.recovery_key')} @@ -300,7 +300,7 @@ export const DeviceVerificationSetup = forwardRef - {t('Settings.device_verification_setup.setup_device_verification')} + {t('Settings.device_verification.setup_device_verification')} @@ -335,7 +335,7 @@ export const DeviceVerificationReset = forwardRef - {t('Settings.device_verification_setup.reset_device_verification')} + {t('Settings.device_verification.reset_device_verification')} @@ -357,10 +357,10 @@ export const DeviceVerificationReset = forwardRef ✋🧑‍🚒🤚 - {t('Settings.device_verification_setup.resetting_device_verification_is_permanent')} + {t('Settings.device_verification.resetting_device_verification_is_permanent')} - {t('Settings.device_verification_setup.anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption')}{' '} - {t('General.recovery_key')} or {t('General.recovery_passphrase')} {t('Settings.device_verification_setup.and_every_device_you_can_verify_from')} + {t('Settings.device_verification.anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption')}{' '} + {t('General.recovery_key')} or {t('General.recovery_passphrase')} {t('Settings.device_verification.and_every_device_you_can_verify_from')} ) : ( )} diff --git a/src/app/features/room/RoomViewTyping.tsx b/src/app/features/room/RoomViewTyping.tsx index dc01e4b7a..cfd1dd319 100644 --- a/src/app/features/room/RoomViewTyping.tsx +++ b/src/app/features/room/RoomViewTyping.tsx @@ -70,11 +70,11 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>( <> {typingNames[0]} - {t('RoomView.typing_sep_word')} + {t('RoomView.typing.typing_sep_word')} {typingNames[1]} - {t('RoomView.are_typing')} + {t('RoomView.typing.are_typing')} )} diff --git a/src/app/features/room/ThreadBrowser.tsx b/src/app/features/room/ThreadBrowser.tsx index 766889032..eb3eeaa52 100644 --- a/src/app/features/room/ThreadBrowser.tsx +++ b/src/app/features/room/ThreadBrowser.tsx @@ -55,6 +55,7 @@ import { EncryptedContent } from './message'; import * as css from './ThreadDrawer.css'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; import { mobileOrTablet } from '$utils/user-agent'; +import { t } from 'i18next'; type ThreadPreviewProps = { room: Room; @@ -215,7 +216,7 @@ function ThreadPreview({ room, thread, onClick, onJump }: ThreadPreviewProps) { )} - Jump + {t('RoomView.Threads.jump')} @@ -471,7 +472,7 @@ export function ThreadBrowser({ room, onOpenThread, onClose, overlay }: ThreadBr - Threads + {t('RoomView.Threads.threads')} @@ -480,7 +481,7 @@ export function ThreadBrowser({ room, onOpenThread, onClose, overlay }: ThreadBr variant="SurfaceVariant" size="300" radii="300" - aria-label="Close threads" + aria-label={t('RoomView.close_threads')} > @@ -497,7 +498,7 @@ export function ThreadBrowser({ room, onOpenThread, onClose, overlay }: ThreadBr ref={searchRef} value={query} onChange={handleSearchChange} - placeholder="Search threads..." + placeholder={t('RoomView.search_threads')} variant="Surface" size="400" radii="400" @@ -512,7 +513,7 @@ export function ThreadBrowser({ room, onOpenThread, onClose, overlay }: ThreadBr setQuery(''); searchRef.current?.focus(); }} - aria-label="Clear search" + aria-label={t('RoomView.clear_search')} > @@ -552,7 +553,7 @@ export function ThreadBrowser({ room, onOpenThread, onClose, overlay }: ThreadBr > - {lowerQuery ? 'No threads match your search.' : 'No threads yet.'} + {lowerQuery ? t('RoomView.Threads.no_threads_match_your_search') : t('RoomView.Threads.no_threads_yet')} ); diff --git a/src/app/features/room/ThreadDrawer.tsx b/src/app/features/room/ThreadDrawer.tsx index 070cdaf58..1e4907a10 100644 --- a/src/app/features/room/ThreadDrawer.tsx +++ b/src/app/features/room/ThreadDrawer.tsx @@ -66,6 +66,7 @@ import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollo import * as css from './ThreadDrawer.css'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; import { mobileOrTablet } from '$utils/user-agent'; +import { t } from 'i18next'; /** * Resolve the list of reply events to show in the thread drawer. @@ -777,7 +778,7 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra - Thread + {t('RoomView.Threads.thread')} @@ -870,7 +871,7 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra > - No replies yet. Start the thread below! + {t('RoomView.Threads.no_replies_yet_start_the_thread_below')} ); diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index ca1517de8..c808ad894 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -83,6 +83,7 @@ import type { PerMessageProfileBeeperFormat } from '$hooks/usePerMessageProfile' import { convertBeeperFormatToOurPerMessageProfile } from '$hooks/usePerMessageProfile'; import { MessageEditor } from './MessageEditor'; import * as css from './styles.css'; +import { t } from 'i18next'; export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void; @@ -193,7 +194,7 @@ export const MessagePinItem = as< ref={ref} > - {isPinned ? 'Unpin Message' : 'Pin Message'} + {isPinned ? t('RoomView.Message.unpin_message') : t('RoomView.Message.pin_message')} ); @@ -640,10 +641,10 @@ function MessageInternal( const originalRoomId = messageForwardedProps.originalRoomId; return { label: messageForwardedProps.originalEventPrivate - ? 'Forwarded private message' + ? t('RoomView.Message.forwarded_private_message') : isSameRoomForward(originalRoomId) - ? 'Forwarded from earlier in this room' - : 'Forwarded from another room', + ? t('RoomView.Message.forwarded_from_earlier_in_this_room') + : t('RoomView.Message.forwarded_from_another_room'), roomId: originalRoomId, eventId: messageForwardedProps.originalEventId, ts: messageForwardedProps.originalTimestamp ?? 0, @@ -655,8 +656,8 @@ function MessageInternal( const originalRoomId = msc2723ForwardedMessageProps.room_id; return { label: isSameRoomForward(originalRoomId) - ? 'Forwarded from earlier in this room' - : 'Forwarded from another room', + ? t('RoomView.Message.forwarded_from_earlier_in_this_room') + : t('RoomView.Message.forwarded_from_another_room'), roomId: originalRoomId, eventId: msc2723ForwardedMessageProps.event_id, ts: msc2723ForwardedMessageProps.origin_server_ts ?? 0, @@ -712,7 +713,7 @@ function MessageInternal( data-mention-event-id={forwardedNotice.eventId} onClick={mentionClickHandler} > - jump to original + {t('RoomView.Message.jump_to_original')} )} @@ -746,11 +747,11 @@ function MessageInternal( {isFailedSend && ( - Failed to send. + {t('RoomView.Message.failed_to_send')} {canResend && ( - Retry + {t('General.retry')} )} {canDeleteFailedSend && ( @@ -760,7 +761,7 @@ function MessageInternal( radii="Pill" onClick={handleDeleteFailedSendClick} > - Delete + {t('General.delete')} )} @@ -769,7 +770,7 @@ function MessageInternal( - Only you can see this. + {t('RoomView.Message.only_you_can_see_this')} - Dismiss + {t('General.dismiss')} )} @@ -1004,7 +1005,7 @@ function MessageInternal( size="T300" truncate > - Add Reaction + {t('RoomView.Message.add_reaction')} )} @@ -1033,7 +1034,7 @@ function MessageInternal( size="T300" truncate > - Add to User Sticker Pack + {t('RoomView.Message.add_to_user_sticker_pack')} )} @@ -1051,7 +1052,7 @@ function MessageInternal( }} > - Reply + {t('RoomView.Message.reply')} {!isThreadedMessage && ( @@ -1076,7 +1077,7 @@ function MessageInternal( size="T300" truncate > - Reply in Thread + {t('RoomView.Message.reply_in_thread')} )} @@ -1097,7 +1098,7 @@ function MessageInternal( size="T300" truncate > - Edit Message + {t('RoomView.Message.edit_message')} )} @@ -1128,7 +1129,7 @@ function MessageInternal( padding: `${config.space.S100} ${config.space.S200}`, }} > - Nickname + {t('General.nickname')} - Save + {t('General.save')} {nicknames[senderId] && ( - Clear + {t('General.clear')} )} @@ -1197,7 +1198,7 @@ function MessageInternal( size="T300" truncate > - {nicknames[senderId] ? 'Edit Nickname' : 'Set Nickname'} + {nicknames[senderId] ? t('General.edit_nickname') : t('General.set_nickname')} ))} @@ -1436,7 +1437,7 @@ export const Event = as<'div', EventProps>( size="T300" truncate > - Reply + {t('RoomView.Message.reply')} {!hideReadReceipts && ( diff --git a/src/app/features/room/message/MessageEditor.tsx b/src/app/features/room/message/MessageEditor.tsx index 10dbca6d7..b14035a0c 100644 --- a/src/app/features/room/message/MessageEditor.tsx +++ b/src/app/features/room/message/MessageEditor.tsx @@ -67,6 +67,8 @@ import { readdAngleBracketsForHiddenPreviews, stripMarkdownEscapesForHiddenPreviews, } from './hiddenLinkPreviews'; +import { t } from 'i18next'; +import * as prefixes from '$unstable/prefixes' type MessageEditorProps = { roomId: string; @@ -229,8 +231,8 @@ export const MessageEditor = as<'div', MessageEditorProps>( : undefined; const rawPmp = - editedEvent?.getContent()?.['m.new_content']?.['com.beeper.per_message_profile'] ?? - mEvent.getContent()?.['com.beeper.per_message_profile']; + editedEvent?.getContent()?.['m.new_content']?.[prefixes.MATRIX_UNSTABLE_PER_MESSAGE_PROFILE_PROPERTY_NAME] ?? + mEvent.getContent()?.[prefixes.MATRIX_UNSTABLE_PER_MESSAGE_PROFILE_PROPERTY_NAME]; const pmpDisplayname = rawPmp !== null && @@ -253,7 +255,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( customHtml = htmlPrefix + customHtml; } - newContent['com.beeper.per_message_profile'] = rawPmp; + newContent[prefixes.MATRIX_UNSTABLE_PER_MESSAGE_PROFILE_PROPERTY_NAME] = rawPmp; } const contentBody: IContent & Omit, 'm.relates_to'> = { @@ -301,11 +303,11 @@ export const MessageEditor = as<'div', MessageEditorProps>( if (oldContent.file !== undefined) content['m.new_content'].file = oldContent.file; if (oldContent.url !== undefined) content['m.new_content'].url = oldContent.url; - if (oldContent['page.codeberg.everypizza.msc4193.spoiler'] !== undefined) { - content['page.codeberg.everypizza.msc4193.spoiler'] = - oldContent['page.codeberg.everypizza.msc4193.spoiler']; - content['m.new_content']['page.codeberg.everypizza.msc4193.spoiler'] = + if (oldContent[prefixes.MATRIX_UNSTABLE_SPOILER_PROPERTY_NAME] !== undefined) { + content[prefixes.MATRIX_UNSTABLE_SPOILER_PROPERTY_NAME] = oldContent['page.codeberg.everypizza.msc4193.spoiler']; + content['m.new_content'][prefixes.MATRIX_UNSTABLE_SPOILER_PROPERTY_NAME] = + oldContent[prefixes.MATRIX_UNSTABLE_SPOILER_PROPERTY_NAME]; } } content['com.beeper.linkpreviews'] = []; @@ -519,7 +521,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( > ( ) : undefined } > - Save + {t('General.save')} - Cancel + {t('General.cancel')} diff --git a/src/app/features/room/message/Reactions.tsx b/src/app/features/room/message/Reactions.tsx index e2fc88478..038c28be3 100644 --- a/src/app/features/room/message/Reactions.tsx +++ b/src/app/features/room/message/Reactions.tsx @@ -24,6 +24,7 @@ import { stopPropagation } from '$utils/keyboard'; import { useMediaAuthentication } from '$hooks/useMediaAuthentication'; import { ReactionViewer } from '$features/room/reaction-viewer'; import * as css from './styles.css'; +import { t } from 'i18next'; export type ReactionsProps = { room: Room; diff --git a/src/app/features/room/reaction-viewer/ReactionViewer.tsx b/src/app/features/room/reaction-viewer/ReactionViewer.tsx index 2f27fe5de..95500de69 100644 --- a/src/app/features/room/reaction-viewer/ReactionViewer.tsx +++ b/src/app/features/room/reaction-viewer/ReactionViewer.tsx @@ -29,6 +29,7 @@ import { useOpenUserRoomProfile } from '$state/hooks/userRoomProfile'; import { useSpaceOptionally } from '$hooks/useSpace'; import { getMouseEventCords } from '$utils/dom'; import * as css from './ReactionViewer.css'; +import { t } from 'i18next'; export type ReactionViewerProps = { room: Room; @@ -99,7 +100,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
- {`Reacted with :${selectedShortcode}:`} + {t('RoomView.Reactions.reacted_with')} {`:${selectedShortcode}:`} From c029fe1c7a9aa9ad7aae59eb6c0052f806ab4b4a Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 23 May 2026 02:19:36 +0200 Subject: [PATCH 06/22] formatting :p --- .vscode/extensions.json | 6 +-- .vscode/settings.json | 13 ++---- src/app/components/DeviceVerification.tsx | 30 ++++++++++--- .../components/DeviceVerificationSetup.tsx | 45 ++++++++++++++----- src/app/features/room/RoomTombstone.tsx | 4 +- src/app/features/room/ThreadBrowser.tsx | 4 +- src/app/features/room/message/Message.tsx | 4 +- .../features/room/message/MessageEditor.tsx | 7 +-- .../room/reaction-viewer/ReactionViewer.tsx | 4 +- 9 files changed, 78 insertions(+), 39 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 34b9eba94..f3e7809fb 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,3 @@ { - "recommendations": [ - "webpro.vscode-knip", - "oxc.oxc-vscode", - "lokalise.i18n-ally" - ] + "recommendations": ["webpro.vscode-knip", "oxc.oxc-vscode", "lokalise.i18n-ally"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 82961cddb..e5946b5fa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,21 +15,14 @@ }, "nixEnvSelector.nixFile": "${workspaceFolder}/flake.nix", "nixEnvSelector.useFlakes": true, - "i18n-ally.localesPaths": [ - "public/locales" - ], + "i18n-ally.localesPaths": ["public/locales"], "i18n-ally.dirStructure": "auto", - "i18n-ally.enabledFrameworks": [ - "react", - "react-i18next" - ], + "i18n-ally.enabledFrameworks": ["react", "react-i18next"], "i18n-ally.extract.keyMaxLength": 75, "i18n-ally.extract.targetPickingStrategy": "most-similar", "i18n-ally.keystyle": "nested", "i18n-ally.extract.keygenStyle": "snake_case", "i18n-ally.extract.ignoredByFiles": { - "src/app/components/DeviceVerificationSetup.tsx": [ - "Column" - ] + "src/app/components/DeviceVerificationSetup.tsx": ["Column"] } } diff --git a/src/app/components/DeviceVerification.tsx b/src/app/components/DeviceVerification.tsx index af7e256d7..25e51d9f3 100644 --- a/src/app/components/DeviceVerification.tsx +++ b/src/app/components/DeviceVerification.tsx @@ -62,7 +62,9 @@ function VerificationWaitAccept() { return ( {t('Settings.device_verification.please_accept_the_request_from_other_device')} - + ); } @@ -76,7 +78,9 @@ function VerificationAccept({ onAccept }: VerificationAcceptProps) { const accepting = acceptState.status === AsyncStatus.Loading; return ( - {t('Settings.device_verification.click_accept_to_start_the_verification_process')} + + {t('Settings.device_verification.click_accept_to_start_the_verification_process')} + diff --git a/src/app/features/settings/Persona/PerMessageProfileEditor.tsx b/src/app/features/settings/Persona/PerMessageProfileEditor.tsx index f05129eae..5ea59647b 100644 --- a/src/app/features/settings/Persona/PerMessageProfileEditor.tsx +++ b/src/app/features/settings/Persona/PerMessageProfileEditor.tsx @@ -17,6 +17,7 @@ import { import type { PronounSet } from '$utils/pronouns'; import { parsePronounsStringToPronounsSetArray } from '$utils/pronouns'; import { SequenceCardStyle } from '../styles.css'; +import { t } from 'i18next'; /** * the props we use for the per-message profile editor, which is used to edit a per-message profile. This is used in the settings page when the user wants to edit a profile. @@ -226,7 +227,7 @@ export function PerMessageProfileEditor({ style={{ width: '100%', marginBottom: config.space.S200 }} > - Profile ID: + {t('Settings.PerMessageProfiles.profile_id')} @@ -263,7 +264,7 @@ export function PerMessageProfileEditor({ overflow: 'visible', marginTop: 20, }} - aria-label="Avatar and upload" + aria-label={t('Settings.Profile.avatar_and_upload')} > ( - + p )} @@ -303,9 +304,9 @@ export function PerMessageProfileEditor({ fontSize: 14, padding: '0 8px', }} - aria-label="Upload avatar image" + aria-label={t('Settings.Profile.upload_avatar_image')} > - Upload + {t('General.upload')} {uploadAtom && ( - Display Name: + {t('Settings.PerMessageProfiles.display_name')} @@ -408,7 +409,7 @@ export function PerMessageProfileEditor({ fontSize: 16, height: 50, }} - placeholder="Pronouns" + placeholder={t('General.pronouns')} readOnly={changingDisplayName || disableSetDisplayname} aria-label={`Pronouns for ${profileId}`} title={`Pronouns for ${profileId}`} @@ -477,7 +478,7 @@ export function PerMessageProfileEditor({ aria-label={`Delete profile ${profileId}`} title={`Delete profile ${profileId}`} > - Delete + {t('General.delete')} diff --git a/src/app/features/settings/cosmetics/Cosmetics.tsx b/src/app/features/settings/cosmetics/Cosmetics.tsx index 49ab59374..7f6e917b4 100644 --- a/src/app/features/settings/cosmetics/Cosmetics.tsx +++ b/src/app/features/settings/cosmetics/Cosmetics.tsx @@ -26,14 +26,15 @@ import { SequenceCardStyle } from '$features/settings/styles.css'; import { SettingsSectionPage } from '../SettingsSectionPage'; import { Appearance } from './Themes'; import { LanguageSpecificPronouns } from './LanguageSpecificPronouns'; +import { t } from 'i18next'; const emojiSizeItems = [ - { id: 'none', name: 'None (Same size as text)' }, - { id: 'extraSmall', name: 'Extra Small' }, - { id: 'small', name: 'Small' }, - { id: 'normal', name: 'Normal' }, - { id: 'large', name: 'Large' }, - { id: 'extraLarge', name: 'Extra Large' }, + { id: 'none', name: t('Settings.Cosmetics.none_same_size_as_text') }, + { id: 'extraSmall', name: t('Settings.Cosmetics.extra_small') }, + { id: 'small', name: t('Settings.Cosmetics.small') }, + { id: 'normal', name: t('Settings.Cosmetics.normal') }, + { id: 'large', name: t('Settings.Cosmetics.large') }, + { id: 'extraLarge', name: t('Settings.Cosmetics.extra_large') }, ]; function SelectJumboEmojiSize() { @@ -105,10 +106,10 @@ function SelectJumboEmojiSize() { } const profileCardRenderItems: { id: RenderUserCardsMode; name: string }[] = [ - { id: 'both', name: 'Light & dark' }, - { id: 'light', name: 'Light only' }, - { id: 'dark', name: 'Dark only' }, - { id: 'none', name: 'Off' }, + { id: 'both', name: t('Settings.Cosmetics.light_and_dark') }, + { id: 'light', name: t('Settings.Cosmetics.light_only') }, + { id: 'dark', name: t('Settings.Cosmetics.dark_only') }, + { id: 'none', name: t('Settings.Cosmetics.off') }, ]; function SelectRenderCustomProfileCards() { @@ -125,7 +126,7 @@ function SelectRenderCustomProfileCards() { }; const currentLabel = - profileCardRenderItems.find((i) => i.id === renderUserCardsMode)?.name ?? 'Light & dark'; + profileCardRenderItems.find((i) => i.id === renderUserCardsMode)?.name ?? t('Settings.Cosmetics.light_and_dark'); return ( <> @@ -183,12 +184,12 @@ function SelectRenderCustomProfileCards() { function JumboEmoji() { return ( - Jumbo Emoji + {t('Settings.Cosmetics.jumbo_emoji')} } /> @@ -206,22 +207,22 @@ function Privacy() { return ( - Privacy & Security + {t('Settings.Cosmetics.privacy_and_security')} } /> } @@ -230,9 +231,9 @@ function Privacy() { } @@ -259,12 +260,12 @@ function IdentityCosmetics() { return ( - Identity + {t('Settings.Cosmetics.identity')} } /> } /> } /> } @@ -310,9 +311,9 @@ function IdentityCosmetics() { } @@ -320,17 +321,17 @@ function IdentityCosmetics() { } /> } /> diff --git a/src/app/features/settings/cosmetics/LanguageSpecificPronouns.tsx b/src/app/features/settings/cosmetics/LanguageSpecificPronouns.tsx index dbab076a8..b68f6a901 100644 --- a/src/app/features/settings/cosmetics/LanguageSpecificPronouns.tsx +++ b/src/app/features/settings/cosmetics/LanguageSpecificPronouns.tsx @@ -4,6 +4,7 @@ import { SequenceCard } from '$components/sequence-card'; import { useEffect, useState } from 'react'; import { getSettings, setSettings } from '$state/settings'; import { SequenceCardStyle } from '../styles.css'; +import { t } from 'i18next'; export type LanguageSpecificPronounsConfig = { enabled?: boolean | string; @@ -69,7 +70,7 @@ export function LanguageSpecificPronouns() { return ( - Language Specific Pronouns + {t('Settings.Cosmetics.language_specific_pronouns')} {useLanguageSpecificPronouns && ( Date: Sat, 30 May 2026 00:15:21 +0200 Subject: [PATCH 18/22] implement localization for EmojiBoard component and related labels --- public/locales/en.json | 11 +++++++++-- src/app/components/emoji-board/EmojiBoard.tsx | 13 +++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index c645b797a..1a8a0483a 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -119,7 +119,8 @@ "upload": "Upload", "upload_area": "Upload area", "display_name": "Display name", - "pronouns": "Pronouns" + "pronouns": "Pronouns", + "unknown": "Unknown" }, "RoomView": { "failed_to_load_history": "Failed to load history.", @@ -174,7 +175,13 @@ } }, "RoomInput": { - "recording_duration": "Recording duration:" + "recording_duration": "Recording duration:", + "EmojiBoard": { + "no_results_found": "No Results found", + "search_results": "Search Results", + "personal_pack": "Personal Pack", + "unknown_pack": "Unknown Pack" + } }, "RoomCreate": { "versions": "Versions", diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index d209b2d0a..02c831fc2 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -55,6 +55,7 @@ import { EmojiBoardLayout, } from './components'; import { EmojiBoardTab, EmojiType } from './types'; +import { t } from 'i18next'; const RECENT_GROUP_ID = 'recent_group'; const SEARCH_GROUP_ID = 'search_group'; @@ -91,7 +92,7 @@ const useGroups = ( imagePacks.forEach((pack) => { let label = pack.meta.name; - if (!label) label = isUserId(pack.id) ? 'Personal Pack' : mx.getRoom(pack.id)?.name; + if (!label) label = isUserId(pack.id) ? t('RoomInput.EmojiBoard.personal_pack') : mx.getRoom(pack.id)?.name; g.push({ id: pack.id, @@ -119,7 +120,7 @@ const useGroups = ( imagePacks.forEach((pack) => { let label = pack.meta.name; - if (!label) label = isUserId(pack.id) ? 'Personal Pack' : mx.getRoom(pack.id)?.name; + if (!label) label = isUserId(pack.id) ? t('RoomInput.EmojiBoard.personal_pack') : mx.getRoom(pack.id)?.name; g.push({ id: pack.id, @@ -210,7 +211,7 @@ function EmojiSidebar({ {packs.map((pack) => { let label = pack.meta.name; - if (!label) label = isUserId(pack.id) ? 'Personal Pack' : mx.getRoom(pack.id)?.name; + if (!label) label = isUserId(pack.id) ? t('RoomInput.EmojiBoard.personal_pack') : mx.getRoom(pack.id)?.name; // limit width and height to 36 to prevent very large icons from breaking the layout, since custom emoji pack icons can be of any size // trying to get close to the render target size of the icons in the sidebar, which is around 24px @@ -223,7 +224,7 @@ function EmojiSidebar({ key={pack.id} active={activeGroupId === pack.id} id={pack.id} - label={label ?? 'Unknown Pack'} + label={label ?? t('RoomInput.EmojiBoard.unknown_pack')} url={url ?? undefined} onClick={handleScrollToGroup} /> @@ -295,7 +296,7 @@ function StickerSidebar({ key={pack.id} active={activeGroupId === pack.id} id={pack.id} - label={label ?? 'Unknown Pack'} + label={label ?? t('RoomInput.EmojiBoard.unknown_pack')} url={url ?? undefined} onClick={handleScrollToGroup} /> @@ -588,7 +589,7 @@ export function EmojiBoard({ {searchedItems && ( {searchedItems.map((element, index) => renderItem(element, index))} From c54c2bbe331fba0bd35e8edd8f3dc0edca815ed0 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 30 May 2026 00:22:32 +0200 Subject: [PATCH 19/22] implement localization for settings and experimental features --- public/locales/en.json | 25 +++++++++++++++++-- src/app/components/AccountDataEditor.tsx | 17 +++++++------ .../experimental/BandwithSavingEmojis.tsx | 7 +++--- .../settings/experimental/Experimental.tsx | 15 +++++------ .../experimental/MSC4268HistoryShare.tsx | 11 ++++---- 5 files changed, 50 insertions(+), 25 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 1a8a0483a..7ef9d0421 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -89,7 +89,22 @@ "selected_language_for_pronouns": "Selected language for pronouns", "the_language_to_show_pronouns_for_when_the_above_setting_is_enabled": "The language to show pronouns for when the above setting is enabled.", "language_code_e_g_en_de_en_de": "Language code (e.g. 'en', 'de', 'en,de')" - } + }, + "save_bandwidth_for_sticker_and_emoji_images": "Save Bandwidth for Sticker and Emoji Images", + "enable_bandwidth_saving_for_stickers_and_emojis": "Enable bandwidth saving for stickers and emojis", + "if_enabled_sticker_and_emoji_images_will_be_optimized_to_save_bandwidth_thi": "If enabled, sticker and emoji images will be optimized to save bandwidth. This helps reduce data usage when viewing these images. But will increase server computation load.", + "personas_per_message_profiles": "Personas (Per-Message Profiles)", + "show_personas_tab": "Show Personas Tab", + "enables_the_personas_tab_in_the_settings_menu_for_per_message_profiles": "Enables the personas tab in the settings menu for per-message profiles", + "experimental": "Experimental", + "the_features_listed_below_may_be_unstable_or_incomplete": "The features listed below may be unstable or incomplete,", + "use_at_your_own_risk": "use at your own risk", + "please_report_any_new_issues_potentially_caused_by_these_features": "Please report any new issues potentially caused by these features!", + "enable_sharing_of_encrypted_history": "Enable Sharing of Encrypted History", + "enable_the_sharehistory_command": "Enable the /sharehistory command", + "if_enabled_this_command_will_allow_users_to_share_encrypted_history_with_ot": "If enabled, this command will allow users to share encrypted history with other newly joined users, as per MSC4268.", + "disable_sharehistory_command": "Disable /sharehistory command", + "enable_sharehistory_command": "Enable /sharehistory command" }, "General": { "continue": "Continue", @@ -120,7 +135,8 @@ "upload_area": "Upload area", "display_name": "Display name", "pronouns": "Pronouns", - "unknown": "Unknown" + "unknown": "Unknown", + "edit": "Edit" }, "RoomView": { "failed_to_load_history": "Failed to load history.", @@ -214,5 +230,10 @@ "invite_to_direct_message_anyway": "Invite to Direct Message anyway", "invite_another_member": "Invite another Member" } + }, + "DevTools": { + "json_content": "JSON Content", + "account_data": "Account Data", + "developer_tools": "Developer Tools" } } diff --git a/src/app/components/AccountDataEditor.tsx b/src/app/components/AccountDataEditor.tsx index 131ae7fab..953ccbfd6 100644 --- a/src/app/components/AccountDataEditor.tsx +++ b/src/app/components/AccountDataEditor.tsx @@ -24,6 +24,7 @@ import { useTextAreaCodeEditor } from '$hooks/useTextAreaCodeEditor'; import { Page, PageHeader } from './page'; import { SequenceCard } from './sequence-card'; import { TextViewerContent } from './text-viewer'; +import { t } from 'i18next'; const EDITOR_INTENT_SPACE_COUNT = 2; @@ -122,7 +123,7 @@ function AccountDataEdit({ aria-disabled={submitting} > - Account Data + {t('DevTools.account_data')} } > - Save + {t('General.save')} @@ -166,7 +167,7 @@ function AccountDataEdit({ - JSON Content + {t('DevTools.json_content')} - Account Data + {t('DevTools.account_data')} - JSON Content + {t('DevTools.json_content')} } > - Developer Tools + {t('DevTools.developer_tools')} diff --git a/src/app/features/settings/experimental/BandwithSavingEmojis.tsx b/src/app/features/settings/experimental/BandwithSavingEmojis.tsx index 6240f2369..89f0a28f2 100644 --- a/src/app/features/settings/experimental/BandwithSavingEmojis.tsx +++ b/src/app/features/settings/experimental/BandwithSavingEmojis.tsx @@ -4,6 +4,7 @@ import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { Box, Switch, Text } from 'folds'; import { SequenceCardStyle } from '../styles.css'; +import { t } from 'i18next'; export function BandwidthSavingEmojis() { const [useBandwidthSaving, setUseBandwidthSaving] = useSetting( @@ -13,7 +14,7 @@ export function BandwidthSavingEmojis() { return ( - Save Bandwidth for Sticker and Emoji Images + {t('Settings.save_bandwidth_for_sticker_and_emoji_images')} } diff --git a/src/app/features/settings/experimental/Experimental.tsx b/src/app/features/settings/experimental/Experimental.tsx index 330412185..c12456aad 100644 --- a/src/app/features/settings/experimental/Experimental.tsx +++ b/src/app/features/settings/experimental/Experimental.tsx @@ -10,6 +10,7 @@ import { Sync } from '../general'; import { SettingsSectionPage } from '../SettingsSectionPage'; import { BandwidthSavingEmojis } from './BandwithSavingEmojis'; import { MSC4268HistoryShare } from './MSC4268HistoryShare'; +import { t } from 'i18next'; function PersonaToggle() { const [showPersonaSetting, setShowPersonaSetting] = useSetting( @@ -19,12 +20,12 @@ function PersonaToggle() { return ( - Personas (Per-Message Profiles) + {t('Settings.personas_per_message_profiles')} } @@ -40,7 +41,7 @@ type ExperimentalProps = { }; export function Experimental({ requestBack, requestClose }: Readonly) { return ( - + @@ -49,10 +50,10 @@ export function Experimental({ requestBack, requestClose }: Readonly - The features listed below may be unstable or incomplete,{' '} - use at your own risk. + {t('Settings.the_features_listed_below_may_be_unstable_or_incomplete')}{' '} + {t('Settings.use_at_your_own_risk')}.
- Please report any new issues potentially caused by these features! + {t('Settings.please_report_any_new_issues_potentially_caused_by_these_features')} } /> diff --git a/src/app/features/settings/experimental/MSC4268HistoryShare.tsx b/src/app/features/settings/experimental/MSC4268HistoryShare.tsx index 27c868d19..b130193d2 100644 --- a/src/app/features/settings/experimental/MSC4268HistoryShare.tsx +++ b/src/app/features/settings/experimental/MSC4268HistoryShare.tsx @@ -4,6 +4,7 @@ import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { Box, Switch, Text } from 'folds'; import { SequenceCardStyle } from '../styles.css'; +import { t } from 'i18next'; export function MSC4268HistoryShare() { const [enabledMSC4268Command, setEnabledMSC4268Command] = useSetting( @@ -13,7 +14,7 @@ export function MSC4268HistoryShare() { return ( - Enable Sharing of Encrypted History + {t('Settings.enable_sharing_of_encrypted_history')} } From a5661771a73525137fd8bddae231bc1cff01c841 Mon Sep 17 00:00:00 2001 From: Rye Date: Sat, 30 May 2026 00:32:10 +0200 Subject: [PATCH 20/22] implement localization for General, KeyboardShortcuts, and Persona settings --- public/locales/en.json | 43 ++++++++++++++++++- .../features/settings/Persona/PKCompat.tsx | 17 ++++---- src/app/features/settings/general/General.tsx | 33 +++++++------- .../keyboard-shortcuts/KeyboardShortcuts.tsx | 25 +++++------ 4 files changed, 81 insertions(+), 37 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 7ef9d0421..ae754c514 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -104,7 +104,48 @@ "enable_the_sharehistory_command": "Enable the /sharehistory command", "if_enabled_this_command_will_allow_users_to_share_encrypted_history_with_ot": "If enabled, this command will allow users to share encrypted history with other newly joined users, as per MSC4268.", "disable_sharehistory_command": "Disable /sharehistory command", - "enable_sharehistory_command": "Enable /sharehistory command" + "enable_sharehistory_command": "Enable /sharehistory command", + "General": { + "formatting": "Formatting", + "month": "Month", + "day_of_the_week": "Day of the Week", + "day_of_the_week_sunday_0": "Day of the week (Sunday = 0)", + "two_letter_day_name": "Two-letter day name", + "short_day_name": "Short day name", + "full_day_name": "Full day name", + "day_of_the_month": "Day of the Month", + "full_month_name": "Full month name", + "short_month_name": "Short month name", + "the_month": "The month", + "two_digit_month": "Two-digit month", + "four_digit_year": "Four-digit year", + "two_digit_year": "Two-digit year" + }, + "KeyboardShortcuts": { + "jump_to_the_highest_priority_unread_room": "Jump to the highest-priority unread room", + "go_to_next_unread_room_cycle": "Go to next unread room (cycle)", + "go_to_previous_unread_room_cycle": "Go to previous unread room (cycle)", + "undo_in_message_editor": "Undo in message editor", + "redo_in_message_editor": "Redo in message editor", + "bold": "Bold", + "italic": "Italic", + "underline": "Underline", + "keyboard_shortcuts": "Keyboard Shortcuts", + "close_keyboard_shortcuts": "Close keyboard shortcuts", + "messages": "Messages", + "navigation": "Navigation" + }, + "Persona": { + "limited_compatibility_with_pluralkit_like_functions": "Limited Compatibility with PluralKit-like functions", + "enable_pk_commands": "Enable PK commands", + "if_enabled_it_will_enable_a_few_pk_style_commands_currently_verry_limited": "If enabled, it will enable a few pk style commands, currently verry limited", + "disable_pk_commands": "disable pk; commands", + "enable_pks_commands": "enable pk; commands", + "enable_shorthands": "Enable Shorthands", + "if_enabled_you_can_use_shorthands_to_use_a_persona_for_one_message_only_eg": "If enabled, you can use shorthands to use a Persona for one message only (eg. '✨:test')", + "disable_checking_typed_messages_for_shorthands": "disable checking typed messages for shorthands", + "enable_checking_typed_messages_for_shorthands": "enable checking typed messages for shorthands" + } }, "General": { "continue": "Continue", diff --git a/src/app/features/settings/Persona/PKCompat.tsx b/src/app/features/settings/Persona/PKCompat.tsx index 8d55eaec1..6d84561c7 100644 --- a/src/app/features/settings/Persona/PKCompat.tsx +++ b/src/app/features/settings/Persona/PKCompat.tsx @@ -4,6 +4,7 @@ import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { Box, Switch, Text } from 'folds'; import { SequenceCardStyle } from '../styles.css'; +import { t } from 'i18next'; export function PKCompatSettings() { const [usePKCompat, setUsePKCompat] = useSetting(settingsAtom, 'pkCompat'); @@ -11,7 +12,7 @@ export function PKCompatSettings() { return ( - Limited Compatibility with PluralKit-like functions + {t('Settings.Persona.limited_compatibility_with_pluralkit_like_functions')} } /> } diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 29361bdd6..49066da76 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -47,6 +47,7 @@ import { isKeyHotkey } from 'is-hotkey'; import { settingsSyncLastSyncedAtom, settingsSyncStatusAtom } from '$hooks/useSettingsSync'; import { exportSettingsAsJson, importSettingsFromJson } from '$utils/settingsSync'; import { SettingsSectionPage } from '../SettingsSectionPage'; +import { t } from 'i18next'; type DateHintProps = { hasChanges: boolean; @@ -75,26 +76,26 @@ function DateHint({ hasChanges, handleReset }: Readonly) { >
- Formatting + {t('Settings.General.formatting')}
- Year + {t('Settings.General.year')}
YY {': '} - Two-digit year + {t('Settings.General.two_digit_year')} {' '} YYYY - {': '}Four-digit year + {': '}{t('Settings.General.four_digit_year')} @@ -102,31 +103,31 @@ function DateHint({ hasChanges, handleReset }: Readonly) {
- Month + {t('Settings.General.month')}
M - {': '}The month + {': '}{t('Settings.General.the_month')} MM - {': '}Two-digit month + {': '}{t('Settings.General.two_digit_month')} {' '} MMM - {': '}Short month name + {': '}{t('Settings.General.short_month_name')} MMMM - {': '}Full month name + {': '}{t('Settings.General.full_month_name')} @@ -134,13 +135,13 @@ function DateHint({ hasChanges, handleReset }: Readonly) {
- Day of the Month + {t('Settings.General.day_of_the_month')}
D - {': '}Day of the month + {': '}{t('Settings.General.day_of_the_month')} @@ -153,31 +154,31 @@ function DateHint({ hasChanges, handleReset }: Readonly) {
- Day of the Week + {t('Settings.General.day_of_the_week')}
d - {': '}Day of the week (Sunday = 0) + {': '}{t('Settings.General.day_of_the_week_sunday_0')} dd - {': '}Two-letter day name + {': '}{t('Settings.General.two_letter_day_name')} ddd - {': '}Short day name + {': '}{t('Settings.General.short_day_name')} dddd - {': '}Full day name + {': '}{t('Settings.General.full_day_name')} diff --git a/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx b/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx index a0b52da48..79105ef97 100644 --- a/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx +++ b/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx @@ -7,6 +7,7 @@ import { Box, Scroll, Text, config } from 'folds'; import { PageContent } from '$components/page'; import { SettingsSectionPage } from '../SettingsSectionPage'; +import { t } from 'i18next'; type ShortcutEntry = { keys: string; @@ -29,21 +30,21 @@ function formatKey(key: string): string { const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { - name: 'Navigation', + name: t('Settings.KeyboardShortcuts.navigation'), shortcuts: [ - { keys: 'Alt+N', description: 'Jump to the highest-priority unread room' }, - { keys: 'Alt+Shift+Down', description: 'Go to next unread room (cycle)' }, - { keys: 'Alt+Shift+Up', description: 'Go to previous unread room (cycle)' }, + { keys: 'Alt+N', description: t('Settings.KeyboardShortcuts.jump_to_the_highest_priority_unread_room') }, + { keys: 'Alt+Shift+Down', description: t('Settings.KeyboardShortcuts.go_to_next_unread_room_cycle') }, + { keys: 'Alt+Shift+Up', description: t('Settings.KeyboardShortcuts.go_to_previous_unread_room_cycle') }, ], }, { - name: 'Messages', + name: t('Settings.KeyboardShortcuts.messages'), shortcuts: [ - { keys: 'Ctrl+Z / ⌘+Z', description: 'Undo in message editor' }, - { keys: 'Ctrl+Shift+Z / ⌘+Shift+Z', description: 'Redo in message editor' }, - { keys: 'Ctrl+B / ⌘+B', description: 'Bold' }, - { keys: 'Ctrl+I / ⌘+I', description: 'Italic' }, - { keys: 'Ctrl+U / ⌘+U', description: 'Underline' }, + { keys: 'Ctrl+Z / ⌘+Z', description: t('Settings.KeyboardShortcuts.undo_in_message_editor') }, + { keys: 'Ctrl+Shift+Z / ⌘+Shift+Z', description: t('Settings.KeyboardShortcuts.redo_in_message_editor') }, + { keys: 'Ctrl+B / ⌘+B', description: t('Settings.KeyboardShortcuts.bold') }, + { keys: 'Ctrl+I / ⌘+I', description: t('Settings.KeyboardShortcuts.italic') }, + { keys: 'Ctrl+U / ⌘+U', description: t('Settings.KeyboardShortcuts.underline') }, ], }, ]; @@ -112,9 +113,9 @@ type KeyboardShortcutsProps = { export function KeyboardShortcuts({ requestBack, requestClose }: KeyboardShortcutsProps) { return ( From 136cfc4e252fb82779a7051c84e01594b451cf37 Mon Sep 17 00:00:00 2001 From: Shea Date: Thu, 16 Jul 2026 13:06:54 +0300 Subject: [PATCH 21/22] make full romanian file, and delete fr and de files as i do not speak either to make the files properly Signed-off-by: Shea --- i18next.config.ts | 7 + public/locales/de.json | 118 -------- public/locales/en.json | 18 +- public/locales/fr.json | 118 -------- public/locales/ro.json | 273 ++++++++++++++++++ .../components/DeviceVerificationSetup.tsx | 9 +- .../DirectInvitePrompt.tsx | 9 +- src/app/features/room/RoomCallButton.test.tsx | 5 +- .../features/settings/cosmetics/Cosmetics.tsx | 48 +-- src/app/features/settings/general/General.tsx | 48 +++ src/app/features/settings/settingsLink.ts | 1 + src/app/i18n.ts | 6 +- 12 files changed, 373 insertions(+), 287 deletions(-) create mode 100644 i18next.config.ts delete mode 100644 public/locales/de.json delete mode 100644 public/locales/fr.json create mode 100644 public/locales/ro.json diff --git a/i18next.config.ts b/i18next.config.ts new file mode 100644 index 000000000..fc2fbae4f --- /dev/null +++ b/i18next.config.ts @@ -0,0 +1,7 @@ +export default { + locales: ['en', 'ro'], + extract: { + input: 'src/**/*.tsx', + output: 'public/locales/{{language}}/{{namespace}}.json', + }, +}; diff --git a/public/locales/de.json b/public/locales/de.json deleted file mode 100644 index 7e363da3a..000000000 --- a/public/locales/de.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "Organisms": { - "RoomCommon": { - "changed_room_name": " hat den Raum Name geändert" - } - }, - "Settings": { - "device_verification": { - "error_uia_action_without_data": "Unerwarteter Fehler! UIA-Aktion wird ohne Daten ausgeführt.", - "authentication_failed_failed_to_setup_device_verification": "Authentifizierung fehlgeschlagen! Fehler beim Einrichten der Geräteverifizierung.", - "unexpected_error_crypto_module_not_found": "Unerwarteter Fehler! \nKryptomodul nicht gefunden!", - "unexpected_error_failed_to_create_recovery_key": "Unexpected Error! Failed to create recovery key.", - "generate_a": "Generate a", - "for_verifying_identity_if_you_do_not_have_access_to_other_devices_additiona": "for verifying identity if you do not have access to other devices. Additionally, setup a passphrase as a memorable alternative.", - "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Authentication steps to perform this action are not supported by client.", - "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Store the Recovery Key in a safe place for future use, as you will need it to verify your identity if you do not have access to other devices.", - "setup_device_verification": "Richten Sie die Geräteverifizierung ein", - "reset_device_verification": "Geräteverifizierung zurücksetzen", - "resetting_device_verification_is_permanent": "Resetting device verification is permanent.", - "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Anyone you have verified with will see security alerts and your encryption backup will be lost. You almost certainly do not want to do this, unless you have lost", - "and_every_device_you_can_verify_from": "and every device you can verify from.", - "please_accept_the_request_from_other_device": "Please accept the request from other device.", - "click_accept_to_start_the_verification_process": "Click accept to start the verification process.", - "waiting_for_request_to_be_accepted": "Waiting for request to be accepted...", - "confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order": "Confirm the emoji below are displayed on both devices, in the same order:", - "they_match": "Sie passen zusammen", - "do_not_match": "Do not Match", - "starting_verification_using_emoji_comparison": "Verifizierung mit Emoji-Vergleich wird gestartet...", - "your_device_is_verified": "Your device is verified.", - "verification_has_been_canceled": "Verification has been canceled.", - "device_verification": "Device Verification", - "unexpected_error_verification_is_started_but_verifier_is_missing": "Unerwarteter Fehler! \nDie Verifizierung wurde gestartet, aber der Prüfer fehlt.", - "verification_request_has_been_accepted": "Verification request has been accepted.", - "waiting_for_the_response_from_other_device": "Waiting for the response from other device..." - } - }, - "General": { - "continue": "Weitermachen", - "passphrase": "Passphrase", - "optional": "optional", - "unexpected_error": "Unerwarteter Fehler!", - "recovery_key": "Wiederherstellungsschlüssel", - "download": "Herunterladen", - "copy": "kopieren", - "hide": "Verstecken", - "show": "Zeigen", - "recovery_passphrase": "Wiederherstellungspassphrase", - "reset": "Zurücksetzen", - "close": "Schließen", - "accept": "Akzeptieren", - "okay": "Verstanden", - "retry": "Wiederholen", - "user": "Benutzer", - "delete": "Löschen", - "dismiss": "Verwerfen", - "nickname": "Spitzname", - "save": "Speichern", - "clear": "Leeren", - "edit_nickname": "Spitzname bearbeiten", - "set_nickname": "Spitznamen festlegen", - "cancel": "Abbrechen" - }, - "RoomView": { - "failed_to_load_history": "Failed to load history.", - "failed_to_load_messages": "Failed to load messages.", - "jump_to_unread": "Zu „Ungelesen“ wechseln", - "mark_as_read": "Als gelesen markieren", - "new_messages": "Neue Nachrichten", - "jump_to_latest": "Zum Neuesten springen", - "call_started_by": "Anruf gestartet von", - "start_voice_call": "Sprachanruf starten", - "typing": { - "are_typing": "schreiben...", - "typing_sep_word": " and " - }, - "is_typing": "tippt...", - "close_threads": "Themen schließen", - "search_threads": "Threads durchsuchen...", - "clear_search": "Suche löschen", - "Threads": { - "no_threads_match_your_search": "No threads match your search.", - "no_threads_yet": "No threads yet.", - "jump": "Springen", - "threads": "Themen", - "thread": "Thema", - "no_replies_yet_start_the_thread_below": "No replies yet. Start the thread below!" - }, - "join_new_room": "Neuem Raum beitreten", - "this_room_has_been_replaced_and_is_no_longer_active": "This room has been replaced and is no longer active.", - "failed_to_join_replacement_room": "Failed to join replacement room!", - "open_new_room": "Neuen Raum öffnen", - "scroll_to_top": "Nach oben scrollen", - "Message": { - "pin_message": "Nachricht anpinnen", - "unpin_message": "Nachricht lösen", - "forwarded_private_message": "Forwarded private message", - "forwarded_from_earlier_in_this_room": "Forwarded from earlier in this room", - "forwarded_from_another_room": "Aus einem anderen Raum weitergeleitet", - "jump_to_original": "jump to original", - "failed_to_send": "Senden fehlgeschlagen.", - "only_you_can_see_this": "Only you can see this.", - "add_reaction": "Add Reaction", - "add_to_user_sticker_pack": "Add to User Sticker Pack", - "reply": "antworten", - "reply_in_thread": "Reply in Thread", - "edit_message": "Edit Message", - "Editor": { - "edit_message": "Edit message..." - } - }, - "Reactions": { - "reacted_with": "Reacted with" - } - }, - "RoomInput": { - "recording_duration": "Aufnahmedauer:" - } -} diff --git a/public/locales/en.json b/public/locales/en.json index ae754c514..c1006cccc 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -10,8 +10,8 @@ "authentication_failed_failed_to_setup_device_verification": "Authentication failed! Failed to setup device verification.", "unexpected_error_crypto_module_not_found": "Unexpected Error! Crypto module not found!", "unexpected_error_failed_to_create_recovery_key": "Unexpected Error! Failed to create recovery key.", - "generate_a": "Generate a", - "for_verifying_identity_if_you_do_not_have_access_to_other_devices_additiona": "for verifying identity if you do not have access to other devices. Additionally, setup a passphrase as a memorable alternative.", + "generate_a_recovery_key": "Generate a recovery key for verifying identity if you do not have access to other devices. Additionally, setup a passphrase as a memorable alternative", + "optional_passphrase": "Passphrase (optional)", "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Authentication steps to perform this action are not supported by client.", "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Store the Recovery Key in a safe place for future use, as you will need it to verify your identity if you do not have access to other devices.", "setup_device_verification": "Setup Device Verification", @@ -65,6 +65,10 @@ "assign_unique_colors_to_users_based_on_their_id_does_not_override_room_spac": "Assign unique colors to users based on their ID. Does not override room/space custom colors. Will override default role colors.", "show_pronoun_pills": "Show Pronoun Pills", "display_user_pronouns_in_the_message_timeline": "Display user pronouns in the message timeline.", + "max_pronoun_pills": "Max Pronoun Pills", + "maximum_number_of_pronoun_pills": "Maximum number of pronoun pills shown per user in the timeline. Additional pronouns appear behind the ... pill.", + "max_pronoun_pill_length": "Max Pronoun Pill Length", + "maximum_pronoun_pill_length": "Maximum characters shown in each pronoun pill before truncation.", "pronoun_pills_for_all": "Pronoun Pills for All", "attempts_to_convert_pronouns_in_names_into_pills_e_g_they_them_or_it_its_tu": "Attempts to convert pronouns in names into pills (e.g. [they/them] or (it/its) turns into a pill).", "render_custom_profile_cards": "Render Custom Profile Cards", @@ -218,10 +222,6 @@ "jump_to_original": "jump to original", "failed_to_send": "Failed to send.", "only_you_can_see_this": "Only you can see this.", - "add_reaction": "Add Reaction", - "add_to_user_sticker_pack": "Add to User Sticker Pack", - "reply": "Reply", - "reply_in_thread": "Reply in Thread", "edit_message": "Edit Message", "Editor": { "edit_message": "Edit message..." @@ -261,11 +261,7 @@ }, "Room": { "DirectInvite": { - "this_is_a": "This is a", - "direct_message": "Direct Message", - "room_intended_for_a_conversation_between_two_persons_would_you_like_to_conv": "room, intended for a conversation between two persons. Would you like to convert it into a", - "group_chat": "group chat", - "before_continuing": "before continuing?", + "direct_message_invite_prompt": "This is a Direct Message room, intended for a conversation between two persons. Would you like to convert it into a group chat before continuing?", "failed_to_convert_direct_message_to_room": "Failed to convert direct message to room!", "convert_to_group_chat_and_invite": "Convert to Group Chat and Invite", "invite_to_direct_message_anyway": "Invite to Direct Message anyway", diff --git a/public/locales/fr.json b/public/locales/fr.json deleted file mode 100644 index ae750caa2..000000000 --- a/public/locales/fr.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "Organisms": { - "RoomCommon": { - "changed_room_name": " changed room name" - } - }, - "Settings": { - "device_verification": { - "error_uia_action_without_data": "Unexpected Error! UIA action is perform without data.", - "authentication_failed_failed_to_setup_device_verification": "Authentication failed! Failed to setup device verification.", - "unexpected_error_crypto_module_not_found": "Unexpected Error! Crypto module not found!", - "unexpected_error_failed_to_create_recovery_key": "Unexpected Error! Failed to create recovery key.", - "generate_a": "Generate a", - "for_verifying_identity_if_you_do_not_have_access_to_other_devices_additiona": "for verifying identity if you do not have access to other devices. Additionally, setup a passphrase as a memorable alternative.", - "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Authentication steps to perform this action are not supported by client.", - "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Store the Recovery Key in a safe place for future use, as you will need it to verify your identity if you do not have access to other devices.", - "setup_device_verification": "Setup Device Verification", - "reset_device_verification": "Reset Device Verification", - "resetting_device_verification_is_permanent": "Resetting device verification is permanent.", - "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Anyone you have verified with will see security alerts and your encryption backup will be lost. You almost certainly do not want to do this, unless you have lost", - "and_every_device_you_can_verify_from": "and every device you can verify from.", - "please_accept_the_request_from_other_device": "Please accept the request from other device.", - "click_accept_to_start_the_verification_process": "Click accept to start the verification process.", - "waiting_for_request_to_be_accepted": "Waiting for request to be accepted...", - "confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order": "Confirm the emoji below are displayed on both devices, in the same order:", - "they_match": "They Match", - "do_not_match": "Do not Match", - "starting_verification_using_emoji_comparison": "Starting verification using emoji comparison...", - "your_device_is_verified": "Your device is verified.", - "verification_has_been_canceled": "Verification has been canceled.", - "device_verification": "Device Verification", - "unexpected_error_verification_is_started_but_verifier_is_missing": "Unexpected Error! Verification is started but verifier is missing.", - "verification_request_has_been_accepted": "Verification request has been accepted.", - "waiting_for_the_response_from_other_device": "Waiting for the response from other device..." - } - }, - "General": { - "continue": "Continue", - "passphrase": "Passphrase", - "optional": "optional", - "unexpected_error": "Unexpected Error!", - "recovery_key": "Recovery Key", - "download": "Download", - "copy": "Copy", - "hide": "Hide", - "show": "Show", - "recovery_passphrase": "Recovery Passphrase", - "reset": "Reset", - "close": "Close", - "accept": "Accept", - "okay": "Okay", - "retry": "Retry", - "user": "User", - "delete": "Delete", - "dismiss": "Dismiss", - "nickname": "Nickname", - "save": "Save", - "clear": "Clear", - "edit_nickname": "Edit Nickname", - "set_nickname": "Set Nickname", - "cancel": "Cancel" - }, - "RoomView": { - "failed_to_load_history": "Failed to load history.", - "failed_to_load_messages": "Failed to load messages.", - "jump_to_unread": "Jump to Unread", - "mark_as_read": "Mark as Read", - "new_messages": "New Messages", - "jump_to_latest": "Jump to Latest", - "call_started_by": "Call started by", - "start_voice_call": "Start Voice Call", - "typing": { - "are_typing": " are typing...", - "typing_sep_word": " and " - }, - "is_typing": "is typing...", - "close_threads": "Close threads", - "search_threads": "Search threads...", - "clear_search": "Clear search", - "Threads": { - "no_threads_match_your_search": "No threads match your search.", - "no_threads_yet": "No threads yet.", - "jump": "Jump", - "threads": "Threads", - "thread": "Thread", - "no_replies_yet_start_the_thread_below": "No replies yet. Start the thread below!" - }, - "join_new_room": "Join New Room", - "this_room_has_been_replaced_and_is_no_longer_active": "This room has been replaced and is no longer active.", - "failed_to_join_replacement_room": "Failed to join replacement room!", - "open_new_room": "Open New Room", - "scroll_to_top": "Scroll to Top", - "Message": { - "pin_message": "Pin Message", - "unpin_message": "Unpin Message", - "forwarded_private_message": "Forwarded private message", - "forwarded_from_earlier_in_this_room": "Forwarded from earlier in this room", - "forwarded_from_another_room": "Forwarded from another room", - "jump_to_original": "jump to original", - "failed_to_send": "Failed to send.", - "only_you_can_see_this": "Only you can see this.", - "add_reaction": "Add Reaction", - "add_to_user_sticker_pack": "Add to User Sticker Pack", - "reply": "Reply", - "reply_in_thread": "Reply in Thread", - "edit_message": "Edit Message", - "Editor": { - "edit_message": "Edit message..." - } - }, - "Reactions": { - "reacted_with": "Reacted with" - } - }, - "RoomInput": { - "recording_duration": "Recording duration:" - } -} diff --git a/public/locales/ro.json b/public/locales/ro.json new file mode 100644 index 000000000..081ec24f5 --- /dev/null +++ b/public/locales/ro.json @@ -0,0 +1,273 @@ +{ + "Organisms": { + "RoomCommon": { + "changed_room_name": " a schimbat numele camerei" + } + }, + "Settings": { + "device_verification": { + "error_uia_action_without_data": "Eroare neașteptată! Actiunea UIA este facută fară date.", + "authentication_failed_failed_to_setup_device_verification": "Autentificare eșuată! Verificarea dispozitivului a eșuat.", + "unexpected_error_crypto_module_not_found": "Eroare neașteptată! Modulul Crypto nu poate fi accessat!", + "unexpected_error_failed_to_create_recovery_key": "Eroare neașteptată! Incercarea de a crea o cheie de rezervă a eșuat.", + "generate_a_recovery_key": "Generați o cheie de recuperare pentru a îți verifica identitatea in cazul in care nu ai access la alte dispozitive. Adițional, setează o parolă ca o alternativă mai ușoară de amintit.", + "optional_passphrase": "Parolă (optională)", + "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Pași pentru autentificare nu sunt acceptați de această aplicație.", + "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Salvează cheia de recuperare intr-un loc sigur pentru a fi accesată in viitor. Vei avea nevoie de ea sa îți verifici identitatea in cazul in care nu ai acces la alte dispozitive.", + "setup_device_verification": "Configurează verificare dispozitivelor.", + "reset_device_verification": "Reseteaza Identitea de verificare a dispozitivelor", + "resetting_device_verification_is_permanent": "Resetarea verificării dispozitivelor este permanentă!", + "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Toată lumea cu care ai vorbit va primii o alertă de securitate si encriptia de rezervă va fi pierdută. Aproape sigur nu vrei să faci asta, singura situație în care ai vrea să faci asta este daca ai pierdut", + "and_every_device_you_can_verify_from": "și orice alt dispozitiv cu care ai putea verifica.", + "please_accept_the_request_from_other_device": "Acceptați cererea dintr-un alt dispozitiv.", + "click_accept_to_start_the_verification_process": "Apasa pe 'Acceptă' pentru a începe procesul de verificare.", + "waiting_for_request_to_be_accepted": "Asteptăm ca cererea să fie acceptată...", + "confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order": "Confirmă ca emoticoanele prezentate ulterior sunt aceleași pe ambele dispozitive, și în aceași ordine:", + "they_match": "Se potrivesc", + "do_not_match": "Sunt diferite", + "starting_verification_using_emoji_comparison": "Începe verificare prin comparare de emoticoane...", + "your_device_is_verified": "Dispozitivul acesta e verificat.", + "verification_has_been_canceled": "Verificarea a forst oprită.", + "device_verification": "Verificare dispozitiv", + "unexpected_error_verification_is_started_but_verifier_is_missing": "Eroare neașteptată! Verificarea a început dar verificatorul a dispărut.", + "verification_request_has_been_accepted": "Cererea de verificare a fost acceptată.", + "waiting_for_the_response_from_other_device": "Așteptăm pentru un răspuns de la celălalt dispozitiv..." + }, + "PerMessageProfiles": { + "profile_id": "ID profil:", + "display_name": "Nume Afișat:" + }, + "Profile": { + "avatar_and_upload": "Imagine de profil si încărcare", + "profile_avatar": "Imagine de profil", + "avatar_fallback": "Imagine de rezervă", + "upload_avatar_image": "Încarcă imagine de profil", + "display_name_input": "Afiș introducere nume de profil", + "reset_display_name": "Resetează numele afișat" + }, + "Cosmetics": { + "light_and_dark": "Luminos & Întunecat", + "light_only": "Doar luminos", + "dark_only": "Doar întunecat", + "off": "Niciodată", + "jumbo_emoji": "Emojiuri Jumbo", + "jumbo_emoji_size": "Mărime Emojiuri Jumbo", + "adjust_the_size_of_emojis_sent_without_text": "Schimbă mărimea emojiurilor trimise fâră text.", + "privacy_and_security": "Confidențialitate și Securitate", + "blur_media": "Estompază media.", + "blurs_images_and_videos_in_the_timeline": "Estompează imaginile si videourile din mesaje.", + "blur_avatars": "Estompează avatar-uri", + "blurs_user_profile_pictures_and_room_icons": "Estompează imaginile de profil si iconițele camerelor.", + "blur_emotes": "Estompează emoticoane", + "blurs_emoticons_within_messages": "Estompează emoticoanele din mesaje.", + "identity": "Identitate", + "colorful_names": "Nume Colorate Aleatoriu", + "assign_unique_colors_to_users_based_on_their_id_does_not_override_room_spac": "Atribuie culori unici utilizatorilor pe baza ID-ului lor. Această setare nu va inlocuii culorile atribuite de cameră/spațiu, dar va inlocuii culoarea rolului de bază", + "show_pronoun_pills": "Arată pilule de pronume", + "display_user_pronouns_in_the_message_timeline": "Arata pronumele utilizatorilor in mesaje", + "max_pronoun_pills": "Număr maxim de pilule de pronume", + "maximum_number_of_pronoun_pills": "Numărul maxim de pilule de pronume prezente de persoană in conversatie. Alte pronume apar după pilula intitulată '...'", + "max_pronoun_pill_length": "Lungimea maximă a pronumelor de profil", + "maximum_pronoun_pill_length": "Numărul maxim de litere in fiecare pronume înainte de a fi trunchiată.", + "pronoun_pills_for_all": "Pilule de pronume pentru toată lumea", + "attempts_to_convert_pronouns_in_names_into_pills_e_g_they_them_or_it_its_tu": "Încearcă sa transformi pronumele din nume in pilule (ex. [el/lui] sau [ea/iei] este transformat in pilulă).", + "render_custom_profile_cards": "Redă carduri de profil personalizate.", + "choose_whose_profile_card_colors_to_show_everyone_with_a_scheme_only_light": "Alege când sa se redea cardul de profil: pentru toată lumea, doar teme întunecate, doar teme luminoase, sau nu le reda deloc.", + "render_global_username_colors": "Redă culorile generale numelor persoanelor", + "display_the_username_colors_anyone_can_set_in_their_account_settings": "Redă culorile numelor tuturor persoanelor care au fost setate de acestea.", + "render_space_room_username_colors": "Redă culorile numelor setate de Cameră/Spațiu.", + "display_the_username_colors_that_can_be_set_with_color": "Redă culorile numelor care pot fi setate cu /color.", + "render_space_room_fonts": "Redă fonturile Camerei/Spațiului.", + "display_the_username_fonts_that_can_be_set_with_font": "Redă fontul numelor care pot fi setate cu /font.", + "consistent_icon_style": "Stil al icoanelor consistent.", + "harmonize_icon_appearance_with_background_fill": "Armonizează forma icoanelor cu o culoare de fundal.", + "extra_small": "Foarte mic", + "none_same_size_as_text": "Bază (aceași mărime ca textul)", + "small": "Mic", + "normal": "Normal", + "large": "Mari", + "extra_large": "Foarte mari", + "language_specific_pronouns": "Pronume specifice limbii", + "show_pronouns_only_in_selected_language": "Redă doar pronumele in limba selectată", + "if_enabled_pronouns_are_only_shown_when_they_match_your_selected_language_t": "Dacă activat, doar pronumele in limba selectată de tine vor apărea. Asta ajuta dacă contactele tale au setata pronume in limbi diferite. Nu afecteaza cum pronumele setate de tine sunt împărtășite.", + "selected_language_for_pronouns": "Limbi selectate pentru pronume.", + "the_language_to_show_pronouns_for_when_the_above_setting_is_enabled": "Limbile in care pronumele vor fi redate pentru setarea de asupra.", + "language_code_e_g_en_de_en_de": "Codul limbii (ex. 'ro', 'de', 'ro,de')" + }, + "save_bandwidth_for_sticker_and_emoji_images": "Reduceți Consumul rețelei pentru stickere si emoticoane", + "enable_bandwidth_saving_for_stickers_and_emojis": "Porneste salvare de 'bandwidth' pentru stickere si emoticoane", + "if_enabled_sticker_and_emoji_images_will_be_optimized_to_save_bandwidth_thi": "Dacă pornit, stickerele si emoticoanele vor fi optimizate pentru a consuma mai puțin internet. Asta ajută consumul de date când văzănd aceste imagini, dar crește costul de computare a server-ului.", + "personas_per_message_profiles": "Personalități. (Profiluri per-mesaj)", + "show_personas_tab": "Prezintă meniul de Personalități", + "enables_the_personas_tab_in_the_settings_menu_for_per_message_profiles": "Adauga meniul de personalități in bara de setări pentru profile specifice fiecărui mesaj.", + "experimental": "Experimental", + "the_features_listed_below_may_be_unstable_or_incomplete": "Opțiunile listate ulterior pot fi instabile sau incomplete,", + "use_at_your_own_risk": "folosește la riscul vostru", + "please_report_any_new_issues_potentially_caused_by_these_features": "Vă rugăm sa raportati orice probleme noi cauzate potențial de aceste opțiuni! Mersi!", + "enable_sharing_of_encrypted_history": "Activează împărtășirea de Istorie Criptată.", + "enable_the_sharehistory_command": "Activează comanda de /sharehistory", + "if_enabled_this_command_will_allow_users_to_share_encrypted_history_with_ot": "Dacă activată, această optiune va permite utilizatorilor să impărtașească istoria criptată cu alți utilizatori noi, per MSC4268.", + "disable_sharehistory_command": "Dezactivează comanda /sharehistory", + "enable_sharehistory_command": "Activează comanda /sharehistory", + "General": { + "formatting": "Formatare", + "month": "Luna", + "day_of_the_week": "Ziua săptămănii", + "day_of_the_week_sunday_0": "Ziua săptămănii (Duminică = 0)", + "two_letter_day_name": "Numele zilei cu 2 litere", + "short_day_name": "Numele zilei scurt", + "full_day_name": "Numele zilei întreg", + "day_of_the_month": "Ziua din lună", + "full_month_name": "Numele întreg al lunii", + "short_month_name": "Numele scurt al lunii", + "the_month": "Luna", + "two_digit_month": "Luna cu 2 cifre", + "four_digit_year": "Anul cu 4 cifre", + "two_digit_year": "Anul cu 2 cifre" + }, + "KeyboardShortcuts": { + "jump_to_the_highest_priority_unread_room": "Dute la camera cu cea mai ridicata prioritate de necitire", + "go_to_next_unread_room_cycle": "Dute la următoarea camera necitită (urmează un ciclu)", + "go_to_previous_unread_room_cycle": "Go to previous unread room (urmează un ciclu)", + "undo_in_message_editor": "Șterge în editorul de mesaje", + "redo_in_message_editor": "Reface în editorul de mesaje", + "bold": "Îngroșat", + "italic": "Italic", + "underline": "Subliniat", + "navigation": "Navigație" + }, + "Persona": { + "limited_compatibility_with_pluralkit_like_functions": "Compatibilitate limitată cu functii similare cu PluralKit", + "enable_pk_commands": "Activează comenzi PK", + "if_enabled_it_will_enable_a_few_pk_style_commands_currently_verry_limited": "Dacă activat, va permite folosirea de comenzi in stil PK, deocamdată foarte limitate", + "disable_pk_commands": "dezactivează comenzile pk;", + "enable_pks_commands": "activează comenzile pk;", + "enable_shorthands": "Activează scurtături", + "if_enabled_you_can_use_shorthands_to_use_a_persona_for_one_message_only_eg": "Dacă activat, vei putea folosii scuraturi pentru a folosi personalități pentru un mesaj (ex. '✨:test')", + "disable_checking_typed_messages_for_shorthands": "dezactivează verificarea mesajelor scrise pentru scurataturi", + "enable_checking_typed_messages_for_shorthands": "activeaza verificarea mesajelor scrise pentru scurataturi" + } + }, + "General": { + "continue": "Continuă", + "passphrase": "Parolă", + "optional": "opțional", + "unexpected_error": "Eroare Neașteptată!", + "recovery_key": "Cheie de Recuperare", + "download": "Descarcă", + "copy": "Copiază", + "hide": "Ascunde", + "show": "Arată", + "recovery_passphrase": "Parolă de recuperare", + "reset": "Resetează", + "close": "Închide", + "accept": "Accept", + "okay": "Okay", + "retry": "Reîncearcă", + "user": "utilizator", + "delete": "Șterge", + "dismiss": "Omite", + "nickname": "Poreclă", + "save": "Salvează", + "clear": "Curăță", + "edit_nickname": "Editează Poreclă", + "set_nickname": "Setează Poreclă", + "cancel": "Anulează", + "upload": "Încarcă", + "upload_area": "Zonă încărcare", + "display_name": "Nume Afișat", + "pronouns": "Pronume", + "unknown": "Necunoscut", + "edit": "Editează" + }, + "RoomView": { + "failed_to_load_history": "Încărcarea istoriei a eșuat.", + "failed_to_load_messages": "Încărcarea mesajelor a eșuat.", + "jump_to_unread": "Sari la necitit", + "mark_as_read": "Notează ca Citit", + "new_messages": "Mesaje Noi", + "jump_to_latest": "Sari la ultimele mesaje", + "call_started_by": "Apel început de", + "start_voice_call": "Începe apel audio", + "typing": { + "are_typing": " scriu...", + "typing_sep_word": " și " + }, + "is_typing": "scrie...", + "close_threads": "Închide fire", + "search_threads": "Caută fire...", + "clear_search": "Curăță căutarea", + "Threads": { + "no_threads_match_your_search": "Nici un fir nu se potrivește cautării.", + "no_threads_yet": "Înca nu sunt fire.", + "jump": "Sari", + "threads": "Fire", + "thread": "Fir", + "no_replies_yet_start_the_thread_below": "Încă nu are răspunsuri. Începe un fir de mesaje!" + }, + "join_new_room": "Alăturăte unei camere noi", + "this_room_has_been_replaced_and_is_no_longer_active": "Aceasă camera a fost schimbată și nu mai este activă.", + "failed_to_join_replacement_room": "Încercarea de a te alătura camerei noi a eșuat!", + "open_new_room": "Deschide Cameră nouă", + "scroll_to_top": "Derulează în Sus", + "Message": { + "pin_message": "Fixează Mesajul", + "unpin_message": "Scoata fixarea Mesajului", + "forwarded_private_message": "Mesaj privat redirecționat", + "forwarded_from_earlier_in_this_room": "Redirecționat dintr-un mesaj mai vechi din această cameră", + "forwarded_from_another_room": "Redirecționat din altă cameră", + "jump_to_original": "dute la original", + "failed_to_send": "Trimitere eșuată.", + "only_you_can_see_this": "Doar tu poți vedea asta.", + "edit_message": "Editează mesajul", + "Editor": { + "edit_message": "Editează mesajul..." + } + }, + "Reactions": { + "reacted_with": "Reacționat cu" + } + }, + "RoomInput": { + "recording_duration": "Lungime înregistrare:", + "EmojiBoard": { + "no_results_found": "Nu un rezultat", + "search_results": "Rezultate căutare", + "personal_pack": "Pachet Personal", + "unknown_pack": "Pachet Necunoscut" + } + }, + "RoomCreate": { + "versions": "Versiuni", + "founders": "Fondatori", + "special_privileged_users_can_be_assigned_during_creation_these_users_have_e": "In durata creări, anumite persoane pot fi privilegiate. Aceste persoana au un control ridicat si pot fi modificate doar cu o modernizare a camerei.", + "no_suggestions": "Nici o sugestie", + "please_provide_the_user_id_and_hit_enter": "Oferiti ID-ul de utilizator si apăsați Enter.", + "only_member_of_parent_space_can_join": "Doar membrii a spațiului părinte se pot alătura.", + "private": "Privată", + "only_people_with_invite_can_join": "Doar persoanele cu o invitație se pot alătura.", + "anyone_with_the_address_can_join": "Oricine are adresa se poate alătura.", + "public": "Publică", + "address_optional": "Adresă (Opțională)", + "pick_an_unique_address_to_make_it_discoverable": "Alege o adresă unică să poată fi comunitatea descoperită.", + "this_address_is_already_taken_please_select_a_different_one": "Această adresă este deja luată. Selectați una diferită.", + "voice_room": "Cameră vocală", + "live_audio_and_video_conversations": "; conversații audio-video live.", + "chat_room": "Cameră discuții", + "messages_photos_and_videos": "; Mesaje, fotografii, and videoclipuri." + }, + "Room": { + "DirectInvite": { + "direct_message_invite_prompt": "Aceasa este o conversație directă, menită pentru discuții între două persoane, Doriți sa o tranformați intr-un group de conversație înainte de a continua?", + "failed_to_convert_direct_message_to_room": "Transformarea convesației directe in conversație de grup a eșuat!", + "convert_to_group_chat_and_invite": "Transformă in grup și invită", + "invite_to_direct_message_anyway": "Invită la mesaje directe oricum", + "invite_another_member": "Invită altă persoana" + } + }, + "DevTools": { + "json_content": "Conținut JSON", + "account_data": "Datele contului", + "developer_tools": "Unelete Dezvoltatori" + } +} diff --git a/src/app/components/DeviceVerificationSetup.tsx b/src/app/components/DeviceVerificationSetup.tsx index 599bcd703..3ce6be146 100644 --- a/src/app/components/DeviceVerificationSetup.tsx +++ b/src/app/components/DeviceVerificationSetup.tsx @@ -227,14 +227,11 @@ function SetupVerification({ onComplete }: Readonly) { return ( - {t('Settings.device_verification.generate_a')} {t('General.recovery_key')}{' '} - {t( - 'Settings.device_verification.for_verifying_identity_if_you_do_not_have_access_to_other_devices_additiona' - )} + {t('Settings.device_verification.generate_a_recovery_key')} - {t('General.passphrase')} ({t('General.optional')}) + {t('Settings.device_verification.optional_passphrase')} @@ -402,7 +399,7 @@ export const DeviceVerificationReset = forwardRef{t('General.recovery_key')} or {t('General.recovery_passphrase')}{' '} + {t('General.recovery_key')} / {t('General.recovery_passphrase')}{' '} {t('Settings.device_verification.and_every_device_you_can_verify_from')} diff --git a/src/app/components/direct-invite-prompt/DirectInvitePrompt.tsx b/src/app/components/direct-invite-prompt/DirectInvitePrompt.tsx index 9ee8a03eb..b5e91953d 100644 --- a/src/app/components/direct-invite-prompt/DirectInvitePrompt.tsx +++ b/src/app/components/direct-invite-prompt/DirectInvitePrompt.tsx @@ -61,14 +61,7 @@ export function DirectInvitePrompt({
- - {t('Room.DirectInvite.this_is_a')} {t('Room.DirectInvite.direct_message')}{' '} - {t( - 'Room.DirectInvite.room_intended_for_a_conversation_between_two_persons_would_you_like_to_conv' - )}{' '} - {t('Room.DirectInvite.group_chat')}{' '} - {t('Room.DirectInvite.before_continuing')} - + {t('Room.DirectInvite.direct_message_invite_prompt')} {convertError && ( {t('Room.DirectInvite.failed_to_convert_direct_message_to_room')} {convertError} diff --git a/src/app/features/room/RoomCallButton.test.tsx b/src/app/features/room/RoomCallButton.test.tsx index 7f4ce6ae6..6fc10e3b1 100644 --- a/src/app/features/room/RoomCallButton.test.tsx +++ b/src/app/features/room/RoomCallButton.test.tsx @@ -3,6 +3,7 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'; import type * as JotaiModule from 'jotai'; import type { Room } from '$types/matrix-sdk'; import { RoomCallButton } from './RoomCallButton'; +import { t } from 'i18next'; const { startCallMock, useCallJoinedMock } = vi.hoisted(() => ({ startCallMock: vi.fn<(...args: unknown[]) => void>(), @@ -40,7 +41,7 @@ describe('RoomCallButton', () => { /> ); - fireEvent.click(screen.getByRole('button', { name: /start voice call/i })); + fireEvent.click(screen.getByRole('button', { name: t('RoomView.start_voice_call') })); await waitFor(() => { expect(startCallMock).toHaveBeenCalledWith(room, { @@ -61,7 +62,7 @@ describe('RoomCallButton', () => { /> ); - fireEvent.click(screen.getByRole('button', { name: /start video call/i })); + fireEvent.click(screen.getByRole('button', { name: t('RoomView.start_voice_call') })); await waitFor(() => { expect(startCallMock).toHaveBeenCalledWith(room, { diff --git a/src/app/features/settings/cosmetics/Cosmetics.tsx b/src/app/features/settings/cosmetics/Cosmetics.tsx index e412af9e1..0cf62439b 100644 --- a/src/app/features/settings/cosmetics/Cosmetics.tsx +++ b/src/app/features/settings/cosmetics/Cosmetics.tsx @@ -28,7 +28,7 @@ import { SequenceCardStyle } from '$features/settings/styles.css'; import { SettingsSectionPage } from '../SettingsSectionPage'; import { Appearance } from './Themes'; import { LanguageSpecificPronouns } from './LanguageSpecificPronouns'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; function PronounPillMaxCountInput({ disabled }: { disabled: boolean }) { const [maxCount, setMaxCount] = useSetting(settingsAtom, 'pronounPillMaxCount'); @@ -207,16 +207,18 @@ function IconSizeSettings() { ); } -const emojiSizeItems = [ - { id: 'none', name: t('Settings.Cosmetics.none_same_size_as_text') }, - { id: 'extraSmall', name: t('Settings.Cosmetics.extra_small') }, - { id: 'small', name: t('Settings.Cosmetics.small') }, - { id: 'normal', name: t('Settings.Cosmetics.normal') }, - { id: 'large', name: t('Settings.Cosmetics.large') }, - { id: 'extraLarge', name: t('Settings.Cosmetics.extra_large') }, -]; - function SelectJumboEmojiSize() { + const { t } = useTranslation(); + + const emojiSizeItems = [ + { id: 'none', name: t('Settings.Cosmetics.none_same_size_as_text') }, + { id: 'extraSmall', name: t('Settings.Cosmetics.extra_small') }, + { id: 'small', name: t('Settings.Cosmetics.small') }, + { id: 'normal', name: t('Settings.Cosmetics.normal') }, + { id: 'large', name: t('Settings.Cosmetics.large') }, + { id: 'extraLarge', name: t('Settings.Cosmetics.extra_large') }, + ]; + const [menuCords, setMenuCords] = useState(); const [jumboEmojiSize, setJumboEmojiSize] = useSetting(settingsAtom, 'jumboEmojiSize'); @@ -284,14 +286,15 @@ function SelectJumboEmojiSize() { ); } -const profileCardRenderItems: { id: RenderUserCardsMode; name: string }[] = [ - { id: 'both', name: t('Settings.Cosmetics.light_and_dark') }, - { id: 'light', name: t('Settings.Cosmetics.light_only') }, - { id: 'dark', name: t('Settings.Cosmetics.dark_only') }, - { id: 'none', name: t('Settings.Cosmetics.off') }, -]; - function SelectRenderCustomProfileCards() { + const { t } = useTranslation(); + const profileCardRenderItems: { id: RenderUserCardsMode; name: string }[] = [ + { id: 'both', name: t('Settings.Cosmetics.light_and_dark') }, + { id: 'light', name: t('Settings.Cosmetics.light_only') }, + { id: 'dark', name: t('Settings.Cosmetics.dark_only') }, + { id: 'none', name: t('Settings.Cosmetics.off') }, + ]; + const [menuCords, setMenuCords] = useState(); const [renderUserCardsMode, setRenderUserCardsMode] = useSetting(settingsAtom, 'renderUserCards'); @@ -362,6 +365,7 @@ function SelectRenderCustomProfileCards() { } function JumboEmoji() { + const { t } = useTranslation(); return ( {t('Settings.Cosmetics.jumbo_emoji')} @@ -378,6 +382,7 @@ function JumboEmoji() { } function Privacy() { + const { t } = useTranslation(); const [privacyBlur, setPrivacyBlur] = useSetting(settingsAtom, 'privacyBlur'); const [privacyBlurAvatars, setPrivacyBlurAvatars] = useSetting( settingsAtom, @@ -424,6 +429,7 @@ function Privacy() { } function IdentityCosmetics() { + const { t } = useTranslation(); const [legacyUsernameColor, setLegacyUsernameColor] = useSetting( settingsAtom, 'legacyUsernameColor' @@ -472,9 +478,9 @@ function IdentityCosmetics() { style={{ opacity: showPronouns ? 1 : 0.5 }} > } /> @@ -485,9 +491,9 @@ function IdentityCosmetics() { style={{ opacity: showPronouns ? 1 : 0.5 }} > } /> diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index baa730346..259e28101 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -57,6 +57,9 @@ import { exportSettingsAsJson, importSettingsFromJson } from '$utils/settingsSyn import { SettingsSectionPage } from '../SettingsSectionPage'; import { t } from 'i18next'; import { CallSoundSettings } from './CallSoundSettings'; +import { useTranslation } from 'react-i18next'; +import type { SettingMenuOption } from '$components/setting-menu-selector'; +import { SettingMenuSelector } from '$components/setting-menu-selector'; type DateHintProps = { hasChanges: boolean; @@ -431,6 +434,50 @@ function DateAndTime() { ); } +function LanguageChange() { + const { i18n } = useTranslation(); + + const languageOptions: SettingMenuOption[] = [ + { value: '', label: 'System' }, + { value: 'en', label: 'English' }, + { value: 'ro', label: 'Română' }, + ]; + const [curLanguage, setCurLanguage] = useState(localStorage.getItem('i18nextLng') ?? ''); + + const handleLanguageChange = (language: string) => { + if (language) { + setCurLanguage(language); + i18n.changeLanguage(language); + } else { + localStorage.removeItem('i18nextLng'); + setCurLanguage(''); + + const detected = i18n.services.languageDetector?.detect(); + i18n.changeLanguage(Array.isArray(detected) ? detected[0] : (detected ?? 'en')); + } + window.location.reload(); + }; + + return ( + + Language + + + } + /> + + + ); +} + function Editor({ isMobile }: Readonly<{ isMobile: boolean }>) { const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline'); const [editorToolbar, setEditorToolbar] = useSetting(settingsAtom, 'editorToolbar'); @@ -1675,6 +1722,7 @@ export function General({ requestBack, requestClose }: Readonly) { + diff --git a/src/app/features/settings/settingsLink.ts b/src/app/features/settings/settingsLink.ts index e39fc9b33..3d6dfc228 100644 --- a/src/app/features/settings/settingsLink.ts +++ b/src/app/features/settings/settingsLink.ts @@ -61,6 +61,7 @@ const settingsLinkFocusIdsBySection: Record({ // Prefer the browser / navigator language and avoid using cached localStorage value detection: { - // prefer querystring first (e.g. ?lng=de), then navigator, then html tag, path, subdomain - order: ['querystring', 'navigator', 'htmlTag', 'path', 'subdomain'], + // prefer querystring first (e.g. ?lng=de), then storage,System then navigator, then html tag, path, subdomain + order: ['querystring', 'localStorage', 'navigator', 'htmlTag', 'path', 'subdomain'], lookupQuerystring: 'lng', // do not cache the detected language in localStorage to avoid stale overrides - caches: [], + caches: ['localStorage'], }, debug: false, fallbackLng: 'en', From 1c40340feb2692dc66895c144d87ebaf9af26682 Mon Sep 17 00:00:00 2001 From: Shea Date: Fri, 17 Jul 2026 18:26:00 +0300 Subject: [PATCH 22/22] separated many localization items Signed-off-by: Shea --- .vscode/settings.json | 11 +- crowdin.yml | 3 - knip.json | 2 +- public/locales/en.json | 276 ------------------ public/locales/en/general.json | 169 +++++++++++ public/locales/en/room/create.json | 19 ++ public/locales/en/room/direct/invite.json | 7 + .../en/settings/device_verification.json | 28 ++ public/locales/en/settings/experimental.json | 22 ++ .../en/settings/keyboard_shortcuts.json | 18 ++ public/locales/en/settings/persona.json | 21 ++ public/locales/en/settings/profile.json | 8 + public/locales/ro.json | 273 ----------------- public/locales/ro/general.json | 166 +++++++++++ public/locales/ro/room/create.json | 19 ++ public/locales/ro/room/direct/invite.json | 7 + .../ro/settings/device_verification.json | 28 ++ public/locales/ro/settings/experimental.json | 22 ++ .../ro/settings/keyboard_shortcuts.json | 16 + public/locales/ro/settings/persona.json | 21 ++ public/locales/ro/settings/profile.json | 8 + .../components/DeviceVerificationSetup.tsx | 94 +++--- .../create-room/AdditionalCreatorInput.tsx | 13 +- .../create-room/CreateRoomAccessSelector.tsx | 13 +- .../DirectInvitePrompt.tsx | 17 +- src/app/components/message/Reply.tsx | 2 +- src/app/features/room/RoomViewTyping.tsx | 2 +- .../features/settings/Persona/PKCompat.tsx | 25 +- .../Persona/PerMessageProfileEditor.tsx | 65 +++-- .../features/settings/cosmetics/Cosmetics.tsx | 10 +- .../experimental/BandwithSavingEmojis.tsx | 9 +- .../settings/experimental/Experimental.tsx | 20 +- .../experimental/MSC4268HistoryShare.tsx | 13 +- .../experimental/MSC4274MediaGalleries.tsx | 12 +- src/app/features/settings/general/General.tsx | 2 +- .../keyboard-shortcuts/KeyboardShortcuts.tsx | 58 ++-- .../timeline/useTimelineEventRenderer.tsx | 2 +- src/app/i18n.ts | 20 +- 38 files changed, 769 insertions(+), 752 deletions(-) delete mode 100644 crowdin.yml delete mode 100644 public/locales/en.json create mode 100644 public/locales/en/general.json create mode 100644 public/locales/en/room/create.json create mode 100644 public/locales/en/room/direct/invite.json create mode 100644 public/locales/en/settings/device_verification.json create mode 100644 public/locales/en/settings/experimental.json create mode 100644 public/locales/en/settings/keyboard_shortcuts.json create mode 100644 public/locales/en/settings/persona.json create mode 100644 public/locales/en/settings/profile.json delete mode 100644 public/locales/ro.json create mode 100644 public/locales/ro/general.json create mode 100644 public/locales/ro/room/create.json create mode 100644 public/locales/ro/room/direct/invite.json create mode 100644 public/locales/ro/settings/device_verification.json create mode 100644 public/locales/ro/settings/experimental.json create mode 100644 public/locales/ro/settings/keyboard_shortcuts.json create mode 100644 public/locales/ro/settings/persona.json create mode 100644 public/locales/ro/settings/profile.json diff --git a/.vscode/settings.json b/.vscode/settings.json index e3d39b03b..f8e70cf7a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,16 +15,17 @@ }, "nixEnvSelector.nixFile": "${workspaceFolder}/flake.nix", "nixEnvSelector.useFlakes": true, + "i18n-ally.localesPaths": ["public/locales"], - "i18n-ally.dirStructure": "auto", + "i18n-ally.namespace": true, + "i18n-ally.pathMatcher": "{locale}/{namespaces}.json", "i18n-ally.enabledFrameworks": ["react", "react-i18next"], + "i18n-ally.keystyle": "nested", "i18n-ally.extract.keyMaxLength": 75, "i18n-ally.extract.targetPickingStrategy": "most-similar", - "i18n-ally.keystyle": "nested", "i18n-ally.extract.keygenStyle": "snake_case", - "i18n-ally.extract.ignoredByFiles": { - "src/app/components/DeviceVerificationSetup.tsx": ["Column"] - }, + "i18n-ally.extract.ignored": ["@"], + "explorer.fileNesting.enabled": true, "explorer.fileNesting.patterns": { "package.json": "pnpm-*.yaml, yarn.lock, package-lock.json, .npmrc, .nvmrc, .node-version", diff --git a/crowdin.yml b/crowdin.yml deleted file mode 100644 index 4c4d36d24..000000000 --- a/crowdin.yml +++ /dev/null @@ -1,3 +0,0 @@ -files: - - source: /public/locales/en.json - translation: /public/locales/%two_letters_code%.json diff --git a/knip.json b/knip.json index 1912dfad9..a54282478 100644 --- a/knip.json +++ b/knip.json @@ -1,7 +1,7 @@ { "$schema": "https://unpkg.com/knip@6/schema.json", "entry": ["src/sw.ts", "scripts/normalize-imports.js"], - "ignore": ["oxlint.config.ts", "oxfmt.config.ts"], + "ignore": ["oxlint.config.ts", "oxfmt.config.ts", "i18next.config.ts"], "ignoreExportsUsedInFile": { "interface": true, "type": true diff --git a/public/locales/en.json b/public/locales/en.json deleted file mode 100644 index c1006cccc..000000000 --- a/public/locales/en.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "Organisms": { - "RoomCommon": { - "changed_room_name": " changed room name" - } - }, - "Settings": { - "device_verification": { - "error_uia_action_without_data": "Unexpected Error! UIA action is perform without data.", - "authentication_failed_failed_to_setup_device_verification": "Authentication failed! Failed to setup device verification.", - "unexpected_error_crypto_module_not_found": "Unexpected Error! Crypto module not found!", - "unexpected_error_failed_to_create_recovery_key": "Unexpected Error! Failed to create recovery key.", - "generate_a_recovery_key": "Generate a recovery key for verifying identity if you do not have access to other devices. Additionally, setup a passphrase as a memorable alternative", - "optional_passphrase": "Passphrase (optional)", - "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Authentication steps to perform this action are not supported by client.", - "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Store the Recovery Key in a safe place for future use, as you will need it to verify your identity if you do not have access to other devices.", - "setup_device_verification": "Setup Device Verification", - "reset_device_verification": "Reset Device Verification", - "resetting_device_verification_is_permanent": "Resetting device verification is permanent.", - "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Anyone you have verified with will see security alerts and your encryption backup will be lost. You almost certainly do not want to do this, unless you have lost", - "and_every_device_you_can_verify_from": "and every device you can verify from.", - "please_accept_the_request_from_other_device": "Please accept the request from other device.", - "click_accept_to_start_the_verification_process": "Click accept to start the verification process.", - "waiting_for_request_to_be_accepted": "Waiting for request to be accepted...", - "confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order": "Confirm the emoji below are displayed on both devices, in the same order:", - "they_match": "They Match", - "do_not_match": "Do not Match", - "starting_verification_using_emoji_comparison": "Starting verification using emoji comparison...", - "your_device_is_verified": "Your device is verified.", - "verification_has_been_canceled": "Verification has been canceled.", - "device_verification": "Device Verification", - "unexpected_error_verification_is_started_but_verifier_is_missing": "Unexpected Error! Verification is started but verifier is missing.", - "verification_request_has_been_accepted": "Verification request has been accepted.", - "waiting_for_the_response_from_other_device": "Waiting for the response from other device..." - }, - "PerMessageProfiles": { - "profile_id": "Profile ID:", - "display_name": "Display Name:" - }, - "Profile": { - "avatar_and_upload": "Avatar and upload", - "profile_avatar": "Profile avatar", - "avatar_fallback": "Avatar fallback", - "upload_avatar_image": "Upload avatar image", - "display_name_input": "Display name input", - "reset_display_name": "Reset display name" - }, - "Cosmetics": { - "light_and_dark": "Light & dark", - "light_only": "Light only", - "dark_only": "Dark only", - "off": "Off", - "jumbo_emoji": "Jumbo Emoji", - "jumbo_emoji_size": "Jumbo Emoji Size", - "adjust_the_size_of_emojis_sent_without_text": "Adjust the size of emojis sent without text.", - "privacy_and_security": "Privacy & Security", - "blur_media": "Blur Media", - "blurs_images_and_videos_in_the_timeline": "Blurs images and videos in the timeline.", - "blur_avatars": "Blur Avatars", - "blurs_user_profile_pictures_and_room_icons": "Blurs user profile pictures and room icons.", - "blur_emotes": "Blur Emotes", - "blurs_emoticons_within_messages": "Blurs emoticons within messages.", - "identity": "Identity", - "colorful_names": "Colorful Names", - "assign_unique_colors_to_users_based_on_their_id_does_not_override_room_spac": "Assign unique colors to users based on their ID. Does not override room/space custom colors. Will override default role colors.", - "show_pronoun_pills": "Show Pronoun Pills", - "display_user_pronouns_in_the_message_timeline": "Display user pronouns in the message timeline.", - "max_pronoun_pills": "Max Pronoun Pills", - "maximum_number_of_pronoun_pills": "Maximum number of pronoun pills shown per user in the timeline. Additional pronouns appear behind the ... pill.", - "max_pronoun_pill_length": "Max Pronoun Pill Length", - "maximum_pronoun_pill_length": "Maximum characters shown in each pronoun pill before truncation.", - "pronoun_pills_for_all": "Pronoun Pills for All", - "attempts_to_convert_pronouns_in_names_into_pills_e_g_they_them_or_it_its_tu": "Attempts to convert pronouns in names into pills (e.g. [they/them] or (it/its) turns into a pill).", - "render_custom_profile_cards": "Render Custom Profile Cards", - "choose_whose_profile_card_colors_to_show_everyone_with_a_scheme_only_light": "Choose whose profile card colors to show: everyone with a scheme, only light or dark schemes, or hide them.", - "render_global_username_colors": "Render Global Username Colors", - "display_the_username_colors_anyone_can_set_in_their_account_settings": "Display the username colors anyone can set in their account settings.", - "render_space_room_username_colors": "Render Space/Room Username Colors", - "display_the_username_colors_that_can_be_set_with_color": "Display the username colors that can be set with /color.", - "render_space_room_fonts": "Render Space/Room Fonts", - "display_the_username_fonts_that_can_be_set_with_font": "Display the username fonts that can be set with /font.", - "consistent_icon_style": "Consistent Icon Style", - "harmonize_icon_appearance_with_background_fill": "Harmonize icon appearance with background fill", - "extra_small": "Extra Small", - "none_same_size_as_text": "None (Same size as text)", - "small": "Small", - "normal": "Normal", - "large": "Large", - "extra_large": "Extra Large", - "language_specific_pronouns": "Language Specific Pronouns", - "show_pronouns_only_in_selected_language": "Show pronouns only in selected language", - "if_enabled_pronouns_are_only_shown_when_they_match_your_selected_language_t": "If enabled, pronouns are only shown when they match your selected language. This helps if your contacts set pronouns in different languages. It doesn't affect how your pronouns are shared with others.", - "selected_language_for_pronouns": "Selected language for pronouns", - "the_language_to_show_pronouns_for_when_the_above_setting_is_enabled": "The language to show pronouns for when the above setting is enabled.", - "language_code_e_g_en_de_en_de": "Language code (e.g. 'en', 'de', 'en,de')" - }, - "save_bandwidth_for_sticker_and_emoji_images": "Save Bandwidth for Sticker and Emoji Images", - "enable_bandwidth_saving_for_stickers_and_emojis": "Enable bandwidth saving for stickers and emojis", - "if_enabled_sticker_and_emoji_images_will_be_optimized_to_save_bandwidth_thi": "If enabled, sticker and emoji images will be optimized to save bandwidth. This helps reduce data usage when viewing these images. But will increase server computation load.", - "personas_per_message_profiles": "Personas (Per-Message Profiles)", - "show_personas_tab": "Show Personas Tab", - "enables_the_personas_tab_in_the_settings_menu_for_per_message_profiles": "Enables the personas tab in the settings menu for per-message profiles", - "experimental": "Experimental", - "the_features_listed_below_may_be_unstable_or_incomplete": "The features listed below may be unstable or incomplete,", - "use_at_your_own_risk": "use at your own risk", - "please_report_any_new_issues_potentially_caused_by_these_features": "Please report any new issues potentially caused by these features!", - "enable_sharing_of_encrypted_history": "Enable Sharing of Encrypted History", - "enable_the_sharehistory_command": "Enable the /sharehistory command", - "if_enabled_this_command_will_allow_users_to_share_encrypted_history_with_ot": "If enabled, this command will allow users to share encrypted history with other newly joined users, as per MSC4268.", - "disable_sharehistory_command": "Disable /sharehistory command", - "enable_sharehistory_command": "Enable /sharehistory command", - "General": { - "formatting": "Formatting", - "month": "Month", - "day_of_the_week": "Day of the Week", - "day_of_the_week_sunday_0": "Day of the week (Sunday = 0)", - "two_letter_day_name": "Two-letter day name", - "short_day_name": "Short day name", - "full_day_name": "Full day name", - "day_of_the_month": "Day of the Month", - "full_month_name": "Full month name", - "short_month_name": "Short month name", - "the_month": "The month", - "two_digit_month": "Two-digit month", - "four_digit_year": "Four-digit year", - "two_digit_year": "Two-digit year" - }, - "KeyboardShortcuts": { - "jump_to_the_highest_priority_unread_room": "Jump to the highest-priority unread room", - "go_to_next_unread_room_cycle": "Go to next unread room (cycle)", - "go_to_previous_unread_room_cycle": "Go to previous unread room (cycle)", - "undo_in_message_editor": "Undo in message editor", - "redo_in_message_editor": "Redo in message editor", - "bold": "Bold", - "italic": "Italic", - "underline": "Underline", - "keyboard_shortcuts": "Keyboard Shortcuts", - "close_keyboard_shortcuts": "Close keyboard shortcuts", - "messages": "Messages", - "navigation": "Navigation" - }, - "Persona": { - "limited_compatibility_with_pluralkit_like_functions": "Limited Compatibility with PluralKit-like functions", - "enable_pk_commands": "Enable PK commands", - "if_enabled_it_will_enable_a_few_pk_style_commands_currently_verry_limited": "If enabled, it will enable a few pk style commands, currently verry limited", - "disable_pk_commands": "disable pk; commands", - "enable_pks_commands": "enable pk; commands", - "enable_shorthands": "Enable Shorthands", - "if_enabled_you_can_use_shorthands_to_use_a_persona_for_one_message_only_eg": "If enabled, you can use shorthands to use a Persona for one message only (eg. '✨:test')", - "disable_checking_typed_messages_for_shorthands": "disable checking typed messages for shorthands", - "enable_checking_typed_messages_for_shorthands": "enable checking typed messages for shorthands" - } - }, - "General": { - "continue": "Continue", - "passphrase": "Passphrase", - "optional": "optional", - "unexpected_error": "Unexpected Error!", - "recovery_key": "Recovery Key", - "download": "Download", - "copy": "Copy", - "hide": "Hide", - "show": "Show", - "recovery_passphrase": "Recovery Passphrase", - "reset": "Reset", - "close": "Close", - "accept": "Accept", - "okay": "Okay", - "retry": "Retry", - "user": "User", - "delete": "Delete", - "dismiss": "Dismiss", - "nickname": "Nickname", - "save": "Save", - "clear": "Clear", - "edit_nickname": "Edit Nickname", - "set_nickname": "Set Nickname", - "cancel": "Cancel", - "upload": "Upload", - "upload_area": "Upload area", - "display_name": "Display name", - "pronouns": "Pronouns", - "unknown": "Unknown", - "edit": "Edit" - }, - "RoomView": { - "failed_to_load_history": "Failed to load history.", - "failed_to_load_messages": "Failed to load messages.", - "jump_to_unread": "Jump to Unread", - "mark_as_read": "Mark as Read", - "new_messages": "New Messages", - "jump_to_latest": "Jump to Latest", - "call_started_by": "Call started by", - "start_voice_call": "Start Voice Call", - "typing": { - "are_typing": " are typing...", - "typing_sep_word": " and " - }, - "is_typing": "is typing...", - "close_threads": "Close threads", - "search_threads": "Search threads...", - "clear_search": "Clear search", - "Threads": { - "no_threads_match_your_search": "No threads match your search.", - "no_threads_yet": "No threads yet.", - "jump": "Jump", - "threads": "Threads", - "thread": "Thread", - "no_replies_yet_start_the_thread_below": "No replies yet. Start the thread below!" - }, - "join_new_room": "Join New Room", - "this_room_has_been_replaced_and_is_no_longer_active": "This room has been replaced and is no longer active.", - "failed_to_join_replacement_room": "Failed to join replacement room!", - "open_new_room": "Open New Room", - "scroll_to_top": "Scroll to Top", - "Message": { - "pin_message": "Pin Message", - "unpin_message": "Unpin Message", - "forwarded_private_message": "Forwarded private message", - "forwarded_from_earlier_in_this_room": "Forwarded from earlier in this room", - "forwarded_from_another_room": "Forwarded from another room", - "jump_to_original": "jump to original", - "failed_to_send": "Failed to send.", - "only_you_can_see_this": "Only you can see this.", - "edit_message": "Edit Message", - "Editor": { - "edit_message": "Edit message..." - } - }, - "Reactions": { - "reacted_with": "Reacted with" - } - }, - "RoomInput": { - "recording_duration": "Recording duration:", - "EmojiBoard": { - "no_results_found": "No Results found", - "search_results": "Search Results", - "personal_pack": "Personal Pack", - "unknown_pack": "Unknown Pack" - } - }, - "RoomCreate": { - "versions": "Versions", - "founders": "Founders", - "special_privileged_users_can_be_assigned_during_creation_these_users_have_e": "Special privileged users can be assigned during creation. These users have elevated control and can only be modified during a upgrade.", - "no_suggestions": "No Suggestions", - "please_provide_the_user_id_and_hit_enter": "Please provide the user ID and hit Enter.", - "only_member_of_parent_space_can_join": "Only member of parent space can join.", - "private": "Private", - "only_people_with_invite_can_join": "Only people with invite can join.", - "anyone_with_the_address_can_join": "Anyone with the address can join.", - "public": "Public", - "address_optional": "Address (Optional)", - "pick_an_unique_address_to_make_it_discoverable": "Pick an unique address to make it discoverable.", - "this_address_is_already_taken_please_select_a_different_one": "This address is already taken. Please select a different one.", - "voice_room": "Voice Room", - "live_audio_and_video_conversations": "- Live audio and video conversations.", - "chat_room": "Chat Room", - "messages_photos_and_videos": "- Messages, photos, and videos." - }, - "Room": { - "DirectInvite": { - "direct_message_invite_prompt": "This is a Direct Message room, intended for a conversation between two persons. Would you like to convert it into a group chat before continuing?", - "failed_to_convert_direct_message_to_room": "Failed to convert direct message to room!", - "convert_to_group_chat_and_invite": "Convert to Group Chat and Invite", - "invite_to_direct_message_anyway": "Invite to Direct Message anyway", - "invite_another_member": "Invite another Member" - } - }, - "DevTools": { - "json_content": "JSON Content", - "account_data": "Account Data", - "developer_tools": "Developer Tools" - } -} diff --git a/public/locales/en/general.json b/public/locales/en/general.json new file mode 100644 index 000000000..85d4cca31 --- /dev/null +++ b/public/locales/en/general.json @@ -0,0 +1,169 @@ +{ + "continue": "Continue", + "passphrase": "Passphrase", + "optional": "optional", + "unexpected_error": "Unexpected Error!", + "recovery_key": "Recovery Key", + "download": "Download", + "copy": "Copy", + "hide": "Hide", + "show": "Show", + "recovery_passphrase": "Recovery Passphrase", + "reset": "Reset", + "close": "Close", + "accept": "Accept", + "okay": "Okay", + "retry": "Retry", + "user": "User", + "delete": "Delete", + "dismiss": "Dismiss", + "nickname": "Nickname", + "save": "Save", + "clear": "Clear", + "edit_nickname": "Edit Nickname", + "set_nickname": "Set Nickname", + "cancel": "Cancel", + "upload": "Upload", + "upload_area": "Upload area", + "display_name": "Display name", + "pronouns": "Pronouns", + "unknown": "Unknown", + "edit": "Edit", + "Organisms": { + "RoomCommon": { + "changed_room_name": " changed room name" + } + }, + "Settings": { + "Cosmetics": { + "light_and_dark": "Light & dark", + "light_only": "Light only", + "dark_only": "Dark only", + "off": "Off", + "jumbo_emoji": "Jumbo Emoji", + "jumbo_emoji_size": "Jumbo Emoji Size", + "adjust_the_size_of_emojis_sent_without_text": "Adjust the size of emojis sent without text.", + "privacy_and_security": "Privacy & Security", + "blur_media": "Blur Media", + "blurs_images_and_videos_in_the_timeline": "Blurs images and videos in the timeline.", + "blur_avatars": "Blur Avatars", + "blurs_user_profile_pictures_and_room_icons": "Blurs user profile pictures and room icons.", + "blur_emotes": "Blur Emotes", + "blurs_emoticons_within_messages": "Blurs emoticons within messages.", + "identity": "Identity", + "colorful_names": "Colorful Names", + "assign_unique_colors_to_users_based_on_their_id_does_not_override_room_spac": "Assign unique colors to users based on their ID. Does not override room/space custom colors. Will override default role colors.", + "show_pronoun_pills": "Show Pronoun Pills", + "display_user_pronouns_in_the_message_timeline": "Display user pronouns in the message timeline.", + "max_pronoun_pills": "Max Pronoun Pills", + "maximum_number_of_pronoun_pills": "Maximum number of pronoun pills shown per user in the timeline. Additional pronouns appear behind the ... pill.", + "max_pronoun_pill_length": "Max Pronoun Pill Length", + "maximum_pronoun_pill_length": "Maximum characters shown in each pronoun pill before truncation.", + "pronoun_pills_for_all": "Pronoun Pills for All", + "attempts_to_convert_pronouns_in_names_into_pills_e_g_they_them_or_it_its_tu": "Attempts to convert pronouns in names into pills (e.g. [they/them] or (it/its) turns into a pill).", + "render_custom_profile_cards": "Render Custom Profile Cards", + "choose_whose_profile_card_colors_to_show_everyone_with_a_scheme_only_light": "Choose whose profile card colors to show: everyone with a scheme, only light or dark schemes, or hide them.", + "render_global_username_colors": "Render Global Username Colors", + "display_the_username_colors_anyone_can_set_in_their_account_settings": "Display the username colors anyone can set in their account settings.", + "render_space_room_username_colors": "Render Space/Room Username Colors", + "display_the_username_colors_that_can_be_set_with_color": "Display the username colors that can be set with /color.", + "render_space_room_fonts": "Render Space/Room Fonts", + "display_the_username_fonts_that_can_be_set_with_font": "Display the username fonts that can be set with /font.", + "consistent_icon_style": "Consistent Icon Style", + "harmonize_icon_appearance_with_background_fill": "Harmonize icon appearance with background fill", + "extra_small": "Extra Small", + "none_same_size_as_text": "None (Same size as text)", + "small": "Small", + "normal": "Normal", + "large": "Large", + "extra_large": "Extra Large", + "language_specific_pronouns": "Language Specific Pronouns", + "show_pronouns_only_in_selected_language": "Show pronouns only in selected language", + "if_enabled_pronouns_are_only_shown_when_they_match_your_selected_language_t": "If enabled, pronouns are only shown when they match your selected language. This helps if your contacts set pronouns in different languages. It doesn't affect how your pronouns are shared with others.", + "selected_language_for_pronouns": "Selected language for pronouns", + "the_language_to_show_pronouns_for_when_the_above_setting_is_enabled": "The language to show pronouns for when the above setting is enabled.", + "language_code_e_g_en_de_en_de": "Language code (e.g. 'en', 'de', 'en,de')" + }, + "General": { + "formatting": "Formatting", + "month": "Month", + "day_of_the_week": "Day of the Week", + "day_of_the_week_sunday_0": "Day of the week (Sunday = 0)", + "two_letter_day_name": "Two-letter day name", + "short_day_name": "Short day name", + "full_day_name": "Full day name", + "day_of_the_month": "Day of the Month", + "full_month_name": "Full month name", + "short_month_name": "Short month name", + "the_month": "The month", + "two_digit_month": "Two-digit month", + "four_digit_year": "Four-digit year", + "two_digit_year": "Two-digit year" + } + }, + "RoomView": { + "failed_to_load_history": "Failed to load history.", + "failed_to_load_messages": "Failed to load messages.", + "jump_to_unread": "Jump to Unread", + "mark_as_read": "Mark as Read", + "new_messages": "New Messages", + "jump_to_latest": "Jump to Latest", + "call_started_by": "Call started by", + "start_voice_call": "Start Voice Call", + "typing": { + "are_typing": " are typing...", + "typing_sep_word": " and " + }, + "is_typing": "is typing...", + "close_threads": "Close threads", + "search_threads": "Search threads...", + "clear_search": "Clear search", + "Threads": { + "no_threads_match_your_search": "No threads match your search.", + "no_threads_yet": "No threads yet.", + "jump": "Jump", + "threads": "Threads", + "thread": "Thread", + "no_replies_yet_start_the_thread_below": "No replies yet. Start the thread below!" + }, + "join_new_room": "Join New Room", + "this_room_has_been_replaced_and_is_no_longer_active": "This room has been replaced and is no longer active.", + "failed_to_join_replacement_room": "Failed to join replacement room!", + "open_new_room": "Open New Room", + "scroll_to_top": "Scroll to Top", + "Message": { + "pin_message": "Pin Message", + "unpin_message": "Unpin Message", + "forwarded_private_message": "Forwarded private message", + "forwarded_from_earlier_in_this_room": "Forwarded from earlier in this room", + "forwarded_from_another_room": "Forwarded from another room", + "jump_to_original": "jump to original", + "failed_to_send": "Failed to send.", + "only_you_can_see_this": "Only you can see this.", + "edit_message": "Edit Message", + "Editor": { + "edit_message": "Edit message..." + } + }, + "Reactions": { + "reacted_with": "Reacted with" + } + }, + "RoomInput": { + "recording_duration": "Recording duration:", + "EmojiBoard": { + "no_results_found": "No Results found", + "search_results": "Search Results", + "personal_pack": "Personal Pack", + "unknown_pack": "Unknown Pack" + } + }, + "Room": { + "DirectInvite": {} + }, + "DevTools": { + "json_content": "JSON Content", + "account_data": "Account Data", + "developer_tools": "Developer Tools" + } +} diff --git a/public/locales/en/room/create.json b/public/locales/en/room/create.json new file mode 100644 index 000000000..ba437d4bf --- /dev/null +++ b/public/locales/en/room/create.json @@ -0,0 +1,19 @@ +{ + "versions": "Versions", + "founders": "Founders", + "special_privileged_users_can_be_assigned_during_creation_these_users_have_e": "Special privileged users can be assigned during creation. These users have elevated control and can only be modified during a upgrade.", + "no_suggestions": "No Suggestions", + "please_provide_the_user_id_and_hit_enter": "Please provide the user ID and hit Enter.", + "only_member_of_parent_space_can_join": "Only member of parent space can join.", + "private": "Private", + "only_people_with_invite_can_join": "Only people with invite can join.", + "anyone_with_the_address_can_join": "Anyone with the address can join.", + "public": "Public", + "address_optional": "Address (Optional)", + "pick_an_unique_address_to_make_it_discoverable": "Pick an unique address to make it discoverable.", + "this_address_is_already_taken_please_select_a_different_one": "This address is already taken. Please select a different one.", + "voice_room": "Voice Room", + "live_audio_and_video_conversations": "- Live audio and video conversations.", + "chat_room": "Chat Room", + "messages_photos_and_videos": "- Messages, photos, and videos." +} diff --git a/public/locales/en/room/direct/invite.json b/public/locales/en/room/direct/invite.json new file mode 100644 index 000000000..fc40e8983 --- /dev/null +++ b/public/locales/en/room/direct/invite.json @@ -0,0 +1,7 @@ +{ + "direct_message_invite_prompt": "This is a Direct Message room, intended for a conversation between two persons. Would you like to convert it into a group chat before continuing?", + "failed_to_convert_direct_message_to_room": "Failed to convert direct message to room!", + "convert_to_group_chat_and_invite": "Convert to Group Chat and Invite", + "invite_to_direct_message_anyway": "Invite to Direct Message anyway", + "invite_another_member": "Invite another Member" +} diff --git a/public/locales/en/settings/device_verification.json b/public/locales/en/settings/device_verification.json new file mode 100644 index 000000000..2a69592ba --- /dev/null +++ b/public/locales/en/settings/device_verification.json @@ -0,0 +1,28 @@ +{ + "error_uia_action_without_data": "Unexpected Error! UIA action is perform without data.", + "authentication_failed_failed_to_setup_device_verification": "Authentication failed! Failed to setup device verification.", + "unexpected_error_crypto_module_not_found": "Unexpected Error! Crypto module not found!", + "unexpected_error_failed_to_create_recovery_key": "Unexpected Error! Failed to create recovery key.", + "generate_a_recovery_key": "Generate a recovery key for verifying identity if you do not have access to other devices. Additionally, setup a passphrase as a memorable alternative", + "optional_passphrase": "Passphrase (optional)", + "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Authentication steps to perform this action are not supported by client.", + "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Store the Recovery Key in a safe place for future use, as you will need it to verify your identity if you do not have access to other devices.", + "setup_device_verification": "Setup Device Verification", + "reset_device_verification": "Reset Device Verification", + "resetting_device_verification_is_permanent": "Resetting device verification is permanent.", + "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Anyone you have verified with will see security alerts and your encryption backup will be lost. You almost certainly do not want to do this, unless you have lost", + "and_every_device_you_can_verify_from": "and every device you can verify from.", + "please_accept_the_request_from_other_device": "Please accept the request from other device.", + "click_accept_to_start_the_verification_process": "Click accept to start the verification process.", + "waiting_for_request_to_be_accepted": "Waiting for request to be accepted...", + "confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order": "Confirm the emoji below are displayed on both devices, in the same order:", + "they_match": "They Match", + "do_not_match": "Do not Match", + "starting_verification_using_emoji_comparison": "Starting verification using emoji comparison...", + "your_device_is_verified": "Your device is verified.", + "verification_has_been_canceled": "Verification has been canceled.", + "device_verification": "Device Verification", + "unexpected_error_verification_is_started_but_verifier_is_missing": "Unexpected Error! Verification is started but verifier is missing.", + "verification_request_has_been_accepted": "Verification request has been accepted.", + "waiting_for_the_response_from_other_device": "Waiting for the response from other device..." +} diff --git a/public/locales/en/settings/experimental.json b/public/locales/en/settings/experimental.json new file mode 100644 index 000000000..1a580e655 --- /dev/null +++ b/public/locales/en/settings/experimental.json @@ -0,0 +1,22 @@ +{ + "save_bandwidth_for_sticker_and_emoji_images": "Save Bandwidth for Sticker and Emoji Images", + "enable_bandwidth_saving_for_stickers_and_emojis": "Enable bandwidth saving for stickers and emojis", + "if_enabled_sticker_and_emoji_images_will_be_optimized_to_save_bandwidth_thi": "If enabled, sticker and emoji images will be optimized to save bandwidth. This helps reduce data usage when viewing these images. But will increase server computation load.", + "personas_per_message_profiles": "Personas (Per-Message Profiles)", + "show_personas_tab": "Show Personas Tab", + "enables_the_personas_tab_in_the_settings_menu_for_per_message_profiles": "Enables the personas tab in the settings menu for per-message profiles", + "experimental": "Experimental", + "the_features_listed_below_may_be_unstable_or_incomplete": "The features listed below may be unstable or incomplete,", + "use_at_your_own_risk": "use at your own risk", + "please_report_any_new_issues_potentially_caused_by_these_features": "Please report any new issues potentially caused by these features!", + "enable_sharing_of_encrypted_history": "Enable Sharing of Encrypted History", + "enable_the_sharehistory_command": "Enable the /sharehistory command", + "if_enabled_this_command_will_allow_users_to_share_encrypted_history_with_ot": "If enabled, this command will allow users to share encrypted history with other newly joined users, as per MSC4268.", + "disable_sharehistory_command": "Disable /sharehistory command", + "enable_sharehistory_command": "Enable /sharehistory command", + "enable_media_galleries_support": "Enable Media Galleries Support", + "enable_media_galleries_title": "Enable Media Galleries", + "enable_media_galleries_description": "If enabled, multiple attachments will be sent in one message, as per MSC4274. Incompatible with clients that don't implement it.", + "disable_media_galleries": "Disable Media Galleries", + "enable_media_galleries": "Enable Media Gallleries" +} diff --git a/public/locales/en/settings/keyboard_shortcuts.json b/public/locales/en/settings/keyboard_shortcuts.json new file mode 100644 index 000000000..1cd7cfa7e --- /dev/null +++ b/public/locales/en/settings/keyboard_shortcuts.json @@ -0,0 +1,18 @@ +{ + "search_for_messages": "Search for messages", + "jump_to_the_highest_priority_unread_room": "Jump to the highest-priority unread room", + "go_to_next_unread_room_cycle": "Go to next unread room (cycle)", + "go_to_previous_unread_room_cycle": "Go to previous unread room (cycle)", + "undo_in_message_editor": "Undo in message editor", + "redo_in_message_editor": "Redo in message editor", + "seach_and_go_to_room": "Search and go to Room", + "open_sticker_picker": "Open Sticker Picker", + "bold": "Bold", + "italic": "Italic", + "underline": "Underline", + "keyboard_shortcuts": "Keyboard Shortcuts", + "close_keyboard_shortcuts": "Close keyboard shortcuts", + "general": "General", + "navigation": "Navigation", + "messages": "Messages" +} diff --git a/public/locales/en/settings/persona.json b/public/locales/en/settings/persona.json new file mode 100644 index 000000000..36d53633e --- /dev/null +++ b/public/locales/en/settings/persona.json @@ -0,0 +1,21 @@ +{ + "limited_compatibility_with_pluralkit_like_functions": "Limited Compatibility with PluralKit-like functions", + "enable_pk_commands": "Enable PK commands", + "if_enabled_it_will_enable_a_few_pk_style_commands_currently_verry_limited": "If enabled, it will enable a few pk style commands, currently verry limited", + "disable_pk_commands": "disable pk; commands", + "enable_pks_commands": "enable pk; commands", + "enable_shorthands": "Enable Shorthands", + "if_enabled_you_can_use_shorthands_to_use_a_persona_for_one_message_only_eg": "If enabled, you can use shorthands to use a Persona for one message only (eg. '✨:test')", + "disable_checking_typed_messages_for_shorthands": "disable checking typed messages for shorthands", + "enable_checking_typed_messages_for_shorthands": "enable checking typed messages for shorthands", + "profile_id": "Profile ID:", + "pronouns": "Pronouns:", + "reset_pronouns": "Reset Pronouns", + "display_name": "Display Name:", + "delete_profile": "Delete profile {{profileId}}", + "save_profile": "Save profile changes for {{profileId}}", + "save_profile_button_area": "Save button area for {{profileId}}", + "pronouns_for": "Pronouns for {{profileId}}", + "avatar_for": "Avatar for {{profileId}}", + "display_name_for": "Display name for {{profileId}}" +} diff --git a/public/locales/en/settings/profile.json b/public/locales/en/settings/profile.json new file mode 100644 index 000000000..1a4a955f5 --- /dev/null +++ b/public/locales/en/settings/profile.json @@ -0,0 +1,8 @@ +{ + "avatar_and_upload": "Avatar and upload", + "profile_avatar": "Profile avatar", + "avatar_fallback": "Avatar fallback", + "upload_avatar_image": "Upload avatar image", + "display_name_input": "Display name input", + "reset_display_name": "Reset display name" +} diff --git a/public/locales/ro.json b/public/locales/ro.json deleted file mode 100644 index 081ec24f5..000000000 --- a/public/locales/ro.json +++ /dev/null @@ -1,273 +0,0 @@ -{ - "Organisms": { - "RoomCommon": { - "changed_room_name": " a schimbat numele camerei" - } - }, - "Settings": { - "device_verification": { - "error_uia_action_without_data": "Eroare neașteptată! Actiunea UIA este facută fară date.", - "authentication_failed_failed_to_setup_device_verification": "Autentificare eșuată! Verificarea dispozitivului a eșuat.", - "unexpected_error_crypto_module_not_found": "Eroare neașteptată! Modulul Crypto nu poate fi accessat!", - "unexpected_error_failed_to_create_recovery_key": "Eroare neașteptată! Incercarea de a crea o cheie de rezervă a eșuat.", - "generate_a_recovery_key": "Generați o cheie de recuperare pentru a îți verifica identitatea in cazul in care nu ai access la alte dispozitive. Adițional, setează o parolă ca o alternativă mai ușoară de amintit.", - "optional_passphrase": "Parolă (optională)", - "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Pași pentru autentificare nu sunt acceptați de această aplicație.", - "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Salvează cheia de recuperare intr-un loc sigur pentru a fi accesată in viitor. Vei avea nevoie de ea sa îți verifici identitatea in cazul in care nu ai acces la alte dispozitive.", - "setup_device_verification": "Configurează verificare dispozitivelor.", - "reset_device_verification": "Reseteaza Identitea de verificare a dispozitivelor", - "resetting_device_verification_is_permanent": "Resetarea verificării dispozitivelor este permanentă!", - "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Toată lumea cu care ai vorbit va primii o alertă de securitate si encriptia de rezervă va fi pierdută. Aproape sigur nu vrei să faci asta, singura situație în care ai vrea să faci asta este daca ai pierdut", - "and_every_device_you_can_verify_from": "și orice alt dispozitiv cu care ai putea verifica.", - "please_accept_the_request_from_other_device": "Acceptați cererea dintr-un alt dispozitiv.", - "click_accept_to_start_the_verification_process": "Apasa pe 'Acceptă' pentru a începe procesul de verificare.", - "waiting_for_request_to_be_accepted": "Asteptăm ca cererea să fie acceptată...", - "confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order": "Confirmă ca emoticoanele prezentate ulterior sunt aceleași pe ambele dispozitive, și în aceași ordine:", - "they_match": "Se potrivesc", - "do_not_match": "Sunt diferite", - "starting_verification_using_emoji_comparison": "Începe verificare prin comparare de emoticoane...", - "your_device_is_verified": "Dispozitivul acesta e verificat.", - "verification_has_been_canceled": "Verificarea a forst oprită.", - "device_verification": "Verificare dispozitiv", - "unexpected_error_verification_is_started_but_verifier_is_missing": "Eroare neașteptată! Verificarea a început dar verificatorul a dispărut.", - "verification_request_has_been_accepted": "Cererea de verificare a fost acceptată.", - "waiting_for_the_response_from_other_device": "Așteptăm pentru un răspuns de la celălalt dispozitiv..." - }, - "PerMessageProfiles": { - "profile_id": "ID profil:", - "display_name": "Nume Afișat:" - }, - "Profile": { - "avatar_and_upload": "Imagine de profil si încărcare", - "profile_avatar": "Imagine de profil", - "avatar_fallback": "Imagine de rezervă", - "upload_avatar_image": "Încarcă imagine de profil", - "display_name_input": "Afiș introducere nume de profil", - "reset_display_name": "Resetează numele afișat" - }, - "Cosmetics": { - "light_and_dark": "Luminos & Întunecat", - "light_only": "Doar luminos", - "dark_only": "Doar întunecat", - "off": "Niciodată", - "jumbo_emoji": "Emojiuri Jumbo", - "jumbo_emoji_size": "Mărime Emojiuri Jumbo", - "adjust_the_size_of_emojis_sent_without_text": "Schimbă mărimea emojiurilor trimise fâră text.", - "privacy_and_security": "Confidențialitate și Securitate", - "blur_media": "Estompază media.", - "blurs_images_and_videos_in_the_timeline": "Estompează imaginile si videourile din mesaje.", - "blur_avatars": "Estompează avatar-uri", - "blurs_user_profile_pictures_and_room_icons": "Estompează imaginile de profil si iconițele camerelor.", - "blur_emotes": "Estompează emoticoane", - "blurs_emoticons_within_messages": "Estompează emoticoanele din mesaje.", - "identity": "Identitate", - "colorful_names": "Nume Colorate Aleatoriu", - "assign_unique_colors_to_users_based_on_their_id_does_not_override_room_spac": "Atribuie culori unici utilizatorilor pe baza ID-ului lor. Această setare nu va inlocuii culorile atribuite de cameră/spațiu, dar va inlocuii culoarea rolului de bază", - "show_pronoun_pills": "Arată pilule de pronume", - "display_user_pronouns_in_the_message_timeline": "Arata pronumele utilizatorilor in mesaje", - "max_pronoun_pills": "Număr maxim de pilule de pronume", - "maximum_number_of_pronoun_pills": "Numărul maxim de pilule de pronume prezente de persoană in conversatie. Alte pronume apar după pilula intitulată '...'", - "max_pronoun_pill_length": "Lungimea maximă a pronumelor de profil", - "maximum_pronoun_pill_length": "Numărul maxim de litere in fiecare pronume înainte de a fi trunchiată.", - "pronoun_pills_for_all": "Pilule de pronume pentru toată lumea", - "attempts_to_convert_pronouns_in_names_into_pills_e_g_they_them_or_it_its_tu": "Încearcă sa transformi pronumele din nume in pilule (ex. [el/lui] sau [ea/iei] este transformat in pilulă).", - "render_custom_profile_cards": "Redă carduri de profil personalizate.", - "choose_whose_profile_card_colors_to_show_everyone_with_a_scheme_only_light": "Alege când sa se redea cardul de profil: pentru toată lumea, doar teme întunecate, doar teme luminoase, sau nu le reda deloc.", - "render_global_username_colors": "Redă culorile generale numelor persoanelor", - "display_the_username_colors_anyone_can_set_in_their_account_settings": "Redă culorile numelor tuturor persoanelor care au fost setate de acestea.", - "render_space_room_username_colors": "Redă culorile numelor setate de Cameră/Spațiu.", - "display_the_username_colors_that_can_be_set_with_color": "Redă culorile numelor care pot fi setate cu /color.", - "render_space_room_fonts": "Redă fonturile Camerei/Spațiului.", - "display_the_username_fonts_that_can_be_set_with_font": "Redă fontul numelor care pot fi setate cu /font.", - "consistent_icon_style": "Stil al icoanelor consistent.", - "harmonize_icon_appearance_with_background_fill": "Armonizează forma icoanelor cu o culoare de fundal.", - "extra_small": "Foarte mic", - "none_same_size_as_text": "Bază (aceași mărime ca textul)", - "small": "Mic", - "normal": "Normal", - "large": "Mari", - "extra_large": "Foarte mari", - "language_specific_pronouns": "Pronume specifice limbii", - "show_pronouns_only_in_selected_language": "Redă doar pronumele in limba selectată", - "if_enabled_pronouns_are_only_shown_when_they_match_your_selected_language_t": "Dacă activat, doar pronumele in limba selectată de tine vor apărea. Asta ajuta dacă contactele tale au setata pronume in limbi diferite. Nu afecteaza cum pronumele setate de tine sunt împărtășite.", - "selected_language_for_pronouns": "Limbi selectate pentru pronume.", - "the_language_to_show_pronouns_for_when_the_above_setting_is_enabled": "Limbile in care pronumele vor fi redate pentru setarea de asupra.", - "language_code_e_g_en_de_en_de": "Codul limbii (ex. 'ro', 'de', 'ro,de')" - }, - "save_bandwidth_for_sticker_and_emoji_images": "Reduceți Consumul rețelei pentru stickere si emoticoane", - "enable_bandwidth_saving_for_stickers_and_emojis": "Porneste salvare de 'bandwidth' pentru stickere si emoticoane", - "if_enabled_sticker_and_emoji_images_will_be_optimized_to_save_bandwidth_thi": "Dacă pornit, stickerele si emoticoanele vor fi optimizate pentru a consuma mai puțin internet. Asta ajută consumul de date când văzănd aceste imagini, dar crește costul de computare a server-ului.", - "personas_per_message_profiles": "Personalități. (Profiluri per-mesaj)", - "show_personas_tab": "Prezintă meniul de Personalități", - "enables_the_personas_tab_in_the_settings_menu_for_per_message_profiles": "Adauga meniul de personalități in bara de setări pentru profile specifice fiecărui mesaj.", - "experimental": "Experimental", - "the_features_listed_below_may_be_unstable_or_incomplete": "Opțiunile listate ulterior pot fi instabile sau incomplete,", - "use_at_your_own_risk": "folosește la riscul vostru", - "please_report_any_new_issues_potentially_caused_by_these_features": "Vă rugăm sa raportati orice probleme noi cauzate potențial de aceste opțiuni! Mersi!", - "enable_sharing_of_encrypted_history": "Activează împărtășirea de Istorie Criptată.", - "enable_the_sharehistory_command": "Activează comanda de /sharehistory", - "if_enabled_this_command_will_allow_users_to_share_encrypted_history_with_ot": "Dacă activată, această optiune va permite utilizatorilor să impărtașească istoria criptată cu alți utilizatori noi, per MSC4268.", - "disable_sharehistory_command": "Dezactivează comanda /sharehistory", - "enable_sharehistory_command": "Activează comanda /sharehistory", - "General": { - "formatting": "Formatare", - "month": "Luna", - "day_of_the_week": "Ziua săptămănii", - "day_of_the_week_sunday_0": "Ziua săptămănii (Duminică = 0)", - "two_letter_day_name": "Numele zilei cu 2 litere", - "short_day_name": "Numele zilei scurt", - "full_day_name": "Numele zilei întreg", - "day_of_the_month": "Ziua din lună", - "full_month_name": "Numele întreg al lunii", - "short_month_name": "Numele scurt al lunii", - "the_month": "Luna", - "two_digit_month": "Luna cu 2 cifre", - "four_digit_year": "Anul cu 4 cifre", - "two_digit_year": "Anul cu 2 cifre" - }, - "KeyboardShortcuts": { - "jump_to_the_highest_priority_unread_room": "Dute la camera cu cea mai ridicata prioritate de necitire", - "go_to_next_unread_room_cycle": "Dute la următoarea camera necitită (urmează un ciclu)", - "go_to_previous_unread_room_cycle": "Go to previous unread room (urmează un ciclu)", - "undo_in_message_editor": "Șterge în editorul de mesaje", - "redo_in_message_editor": "Reface în editorul de mesaje", - "bold": "Îngroșat", - "italic": "Italic", - "underline": "Subliniat", - "navigation": "Navigație" - }, - "Persona": { - "limited_compatibility_with_pluralkit_like_functions": "Compatibilitate limitată cu functii similare cu PluralKit", - "enable_pk_commands": "Activează comenzi PK", - "if_enabled_it_will_enable_a_few_pk_style_commands_currently_verry_limited": "Dacă activat, va permite folosirea de comenzi in stil PK, deocamdată foarte limitate", - "disable_pk_commands": "dezactivează comenzile pk;", - "enable_pks_commands": "activează comenzile pk;", - "enable_shorthands": "Activează scurtături", - "if_enabled_you_can_use_shorthands_to_use_a_persona_for_one_message_only_eg": "Dacă activat, vei putea folosii scuraturi pentru a folosi personalități pentru un mesaj (ex. '✨:test')", - "disable_checking_typed_messages_for_shorthands": "dezactivează verificarea mesajelor scrise pentru scurataturi", - "enable_checking_typed_messages_for_shorthands": "activeaza verificarea mesajelor scrise pentru scurataturi" - } - }, - "General": { - "continue": "Continuă", - "passphrase": "Parolă", - "optional": "opțional", - "unexpected_error": "Eroare Neașteptată!", - "recovery_key": "Cheie de Recuperare", - "download": "Descarcă", - "copy": "Copiază", - "hide": "Ascunde", - "show": "Arată", - "recovery_passphrase": "Parolă de recuperare", - "reset": "Resetează", - "close": "Închide", - "accept": "Accept", - "okay": "Okay", - "retry": "Reîncearcă", - "user": "utilizator", - "delete": "Șterge", - "dismiss": "Omite", - "nickname": "Poreclă", - "save": "Salvează", - "clear": "Curăță", - "edit_nickname": "Editează Poreclă", - "set_nickname": "Setează Poreclă", - "cancel": "Anulează", - "upload": "Încarcă", - "upload_area": "Zonă încărcare", - "display_name": "Nume Afișat", - "pronouns": "Pronume", - "unknown": "Necunoscut", - "edit": "Editează" - }, - "RoomView": { - "failed_to_load_history": "Încărcarea istoriei a eșuat.", - "failed_to_load_messages": "Încărcarea mesajelor a eșuat.", - "jump_to_unread": "Sari la necitit", - "mark_as_read": "Notează ca Citit", - "new_messages": "Mesaje Noi", - "jump_to_latest": "Sari la ultimele mesaje", - "call_started_by": "Apel început de", - "start_voice_call": "Începe apel audio", - "typing": { - "are_typing": " scriu...", - "typing_sep_word": " și " - }, - "is_typing": "scrie...", - "close_threads": "Închide fire", - "search_threads": "Caută fire...", - "clear_search": "Curăță căutarea", - "Threads": { - "no_threads_match_your_search": "Nici un fir nu se potrivește cautării.", - "no_threads_yet": "Înca nu sunt fire.", - "jump": "Sari", - "threads": "Fire", - "thread": "Fir", - "no_replies_yet_start_the_thread_below": "Încă nu are răspunsuri. Începe un fir de mesaje!" - }, - "join_new_room": "Alăturăte unei camere noi", - "this_room_has_been_replaced_and_is_no_longer_active": "Aceasă camera a fost schimbată și nu mai este activă.", - "failed_to_join_replacement_room": "Încercarea de a te alătura camerei noi a eșuat!", - "open_new_room": "Deschide Cameră nouă", - "scroll_to_top": "Derulează în Sus", - "Message": { - "pin_message": "Fixează Mesajul", - "unpin_message": "Scoata fixarea Mesajului", - "forwarded_private_message": "Mesaj privat redirecționat", - "forwarded_from_earlier_in_this_room": "Redirecționat dintr-un mesaj mai vechi din această cameră", - "forwarded_from_another_room": "Redirecționat din altă cameră", - "jump_to_original": "dute la original", - "failed_to_send": "Trimitere eșuată.", - "only_you_can_see_this": "Doar tu poți vedea asta.", - "edit_message": "Editează mesajul", - "Editor": { - "edit_message": "Editează mesajul..." - } - }, - "Reactions": { - "reacted_with": "Reacționat cu" - } - }, - "RoomInput": { - "recording_duration": "Lungime înregistrare:", - "EmojiBoard": { - "no_results_found": "Nu un rezultat", - "search_results": "Rezultate căutare", - "personal_pack": "Pachet Personal", - "unknown_pack": "Pachet Necunoscut" - } - }, - "RoomCreate": { - "versions": "Versiuni", - "founders": "Fondatori", - "special_privileged_users_can_be_assigned_during_creation_these_users_have_e": "In durata creări, anumite persoane pot fi privilegiate. Aceste persoana au un control ridicat si pot fi modificate doar cu o modernizare a camerei.", - "no_suggestions": "Nici o sugestie", - "please_provide_the_user_id_and_hit_enter": "Oferiti ID-ul de utilizator si apăsați Enter.", - "only_member_of_parent_space_can_join": "Doar membrii a spațiului părinte se pot alătura.", - "private": "Privată", - "only_people_with_invite_can_join": "Doar persoanele cu o invitație se pot alătura.", - "anyone_with_the_address_can_join": "Oricine are adresa se poate alătura.", - "public": "Publică", - "address_optional": "Adresă (Opțională)", - "pick_an_unique_address_to_make_it_discoverable": "Alege o adresă unică să poată fi comunitatea descoperită.", - "this_address_is_already_taken_please_select_a_different_one": "Această adresă este deja luată. Selectați una diferită.", - "voice_room": "Cameră vocală", - "live_audio_and_video_conversations": "; conversații audio-video live.", - "chat_room": "Cameră discuții", - "messages_photos_and_videos": "; Mesaje, fotografii, and videoclipuri." - }, - "Room": { - "DirectInvite": { - "direct_message_invite_prompt": "Aceasa este o conversație directă, menită pentru discuții între două persoane, Doriți sa o tranformați intr-un group de conversație înainte de a continua?", - "failed_to_convert_direct_message_to_room": "Transformarea convesației directe in conversație de grup a eșuat!", - "convert_to_group_chat_and_invite": "Transformă in grup și invită", - "invite_to_direct_message_anyway": "Invită la mesaje directe oricum", - "invite_another_member": "Invită altă persoana" - } - }, - "DevTools": { - "json_content": "Conținut JSON", - "account_data": "Datele contului", - "developer_tools": "Unelete Dezvoltatori" - } -} diff --git a/public/locales/ro/general.json b/public/locales/ro/general.json new file mode 100644 index 000000000..d4d6fbca9 --- /dev/null +++ b/public/locales/ro/general.json @@ -0,0 +1,166 @@ +{ + "continue": "Continuă", + "passphrase": "Parolă", + "optional": "opțional", + "unexpected_error": "Eroare Neașteptată!", + "recovery_key": "Cheie de Recuperare", + "download": "Descarcă", + "copy": "Copiază", + "hide": "Ascunde", + "show": "Arată", + "recovery_passphrase": "Parolă de recuperare", + "reset": "Resetează", + "close": "Închide", + "accept": "Accept", + "okay": "Okay", + "retry": "Reîncearcă", + "user": "utilizator", + "delete": "Șterge", + "dismiss": "Omite", + "nickname": "Poreclă", + "save": "Salvează", + "clear": "Curăță", + "edit_nickname": "Editează Poreclă", + "set_nickname": "Setează Poreclă", + "cancel": "Anulează", + "upload": "Încarcă", + "upload_area": "Zonă încărcare", + "display_name": "Nume Afișat", + "pronouns": "Pronume", + "unknown": "Necunoscut", + "edit": "Editează", + "Organisms": { + "RoomCommon": { + "changed_room_name": " a schimbat numele camerei" + } + }, + "Settings": { + "Cosmetics": { + "light_and_dark": "Luminos & Întunecat", + "light_only": "Doar luminos", + "dark_only": "Doar întunecat", + "off": "Niciodată", + "jumbo_emoji": "Emojiuri Jumbo", + "jumbo_emoji_size": "Mărime Emojiuri Jumbo", + "adjust_the_size_of_emojis_sent_without_text": "Schimbă mărimea emojiurilor trimise fâră text.", + "privacy_and_security": "Confidențialitate și Securitate", + "blur_media": "Estompază media.", + "blurs_images_and_videos_in_the_timeline": "Estompează imaginile si videourile din mesaje.", + "blur_avatars": "Estompează avatar-uri", + "blurs_user_profile_pictures_and_room_icons": "Estompează imaginile de profil si iconițele camerelor.", + "blur_emotes": "Estompează emoticoane", + "blurs_emoticons_within_messages": "Estompează emoticoanele din mesaje.", + "identity": "Identitate", + "colorful_names": "Nume Colorate Aleatoriu", + "assign_unique_colors_to_users_based_on_their_id_does_not_override_room_spac": "Atribuie culori unici utilizatorilor pe baza ID-ului lor. Această setare nu va inlocuii culorile atribuite de cameră/spațiu, dar va inlocuii culoarea rolului de bază", + "show_pronoun_pills": "Arată pilule de pronume", + "display_user_pronouns_in_the_message_timeline": "Arata pronumele utilizatorilor in mesaje", + "max_pronoun_pills": "Număr maxim de pilule de pronume", + "maximum_number_of_pronoun_pills": "Numărul maxim de pilule de pronume prezente de persoană in conversatie. Alte pronume apar după pilula intitulată '...'", + "max_pronoun_pill_length": "Lungimea maximă a pronumelor de profil", + "maximum_pronoun_pill_length": "Numărul maxim de litere in fiecare pronume înainte de a fi trunchiată.", + "pronoun_pills_for_all": "Pilule de pronume pentru toată lumea", + "attempts_to_convert_pronouns_in_names_into_pills_e_g_they_them_or_it_its_tu": "Încearcă sa transformi pronumele din nume in pilule (ex. [el/lui] sau [ea/iei] este transformat in pilulă).", + "render_custom_profile_cards": "Redă carduri de profil personalizate.", + "choose_whose_profile_card_colors_to_show_everyone_with_a_scheme_only_light": "Alege când sa se redea cardul de profil: pentru toată lumea, doar teme întunecate, doar teme luminoase, sau nu le reda deloc.", + "render_global_username_colors": "Redă culorile generale numelor persoanelor", + "display_the_username_colors_anyone_can_set_in_their_account_settings": "Redă culorile numelor tuturor persoanelor care au fost setate de acestea.", + "render_space_room_username_colors": "Redă culorile numelor setate de Cameră/Spațiu.", + "display_the_username_colors_that_can_be_set_with_color": "Redă culorile numelor care pot fi setate cu /color.", + "render_space_room_fonts": "Redă fonturile Camerei/Spațiului.", + "display_the_username_fonts_that_can_be_set_with_font": "Redă fontul numelor care pot fi setate cu /font.", + "consistent_icon_style": "Stil al icoanelor consistent.", + "harmonize_icon_appearance_with_background_fill": "Armonizează forma icoanelor cu o culoare de fundal.", + "extra_small": "Foarte mic", + "none_same_size_as_text": "Bază (aceași mărime ca textul)", + "small": "Mic", + "normal": "Normal", + "large": "Mari", + "extra_large": "Foarte mari", + "language_specific_pronouns": "Pronume specifice limbii", + "show_pronouns_only_in_selected_language": "Redă doar pronumele in limba selectată", + "if_enabled_pronouns_are_only_shown_when_they_match_your_selected_language_t": "Dacă activat, doar pronumele in limba selectată de tine vor apărea. Asta ajuta dacă contactele tale au setata pronume in limbi diferite. Nu afecteaza cum pronumele setate de tine sunt împărtășite.", + "selected_language_for_pronouns": "Limbi selectate pentru pronume.", + "the_language_to_show_pronouns_for_when_the_above_setting_is_enabled": "Limbile in care pronumele vor fi redate pentru setarea de asupra.", + "language_code_e_g_en_de_en_de": "Codul limbii (ex. 'ro', 'de', 'ro,de')" + }, + "General": { + "formatting": "Formatare", + "month": "Luna", + "day_of_the_week": "Ziua săptămănii", + "day_of_the_week_sunday_0": "Ziua săptămănii (Duminică = 0)", + "two_letter_day_name": "Numele zilei cu 2 litere", + "short_day_name": "Numele zilei scurt", + "full_day_name": "Numele zilei întreg", + "day_of_the_month": "Ziua din lună", + "full_month_name": "Numele întreg al lunii", + "short_month_name": "Numele scurt al lunii", + "the_month": "Luna", + "two_digit_month": "Luna cu 2 cifre", + "four_digit_year": "Anul cu 4 cifre", + "two_digit_year": "Anul cu 2 cifre" + } + }, + "RoomView": { + "failed_to_load_history": "Încărcarea istoriei a eșuat.", + "failed_to_load_messages": "Încărcarea mesajelor a eșuat.", + "jump_to_unread": "Sari la necitit", + "mark_as_read": "Notează ca Citit", + "new_messages": "Mesaje Noi", + "jump_to_latest": "Sari la ultimele mesaje", + "call_started_by": "Apel început de", + "start_voice_call": "Începe apel audio", + "typing": { + "are_typing": " scriu...", + "typing_sep_word": " și " + }, + "is_typing": "scrie...", + "close_threads": "Închide fire", + "search_threads": "Caută fire...", + "clear_search": "Curăță căutarea", + "Threads": { + "no_threads_match_your_search": "Nici un fir nu se potrivește cautării.", + "no_threads_yet": "Înca nu sunt fire.", + "jump": "Sari", + "threads": "Fire", + "thread": "Fir", + "no_replies_yet_start_the_thread_below": "Încă nu are răspunsuri. Începe un fir de mesaje!" + }, + "join_new_room": "Alăturăte unei camere noi", + "this_room_has_been_replaced_and_is_no_longer_active": "Aceasă camera a fost schimbată și nu mai este activă.", + "failed_to_join_replacement_room": "Încercarea de a te alătura camerei noi a eșuat!", + "open_new_room": "Deschide Cameră nouă", + "scroll_to_top": "Derulează în Sus", + "Message": { + "pin_message": "Fixează Mesajul", + "unpin_message": "Scoata fixarea Mesajului", + "forwarded_private_message": "Mesaj privat redirecționat", + "forwarded_from_earlier_in_this_room": "Redirecționat dintr-un mesaj mai vechi din această cameră", + "forwarded_from_another_room": "Redirecționat din altă cameră", + "jump_to_original": "du-te la original", + "failed_to_send": "Trimitere eșuată.", + "only_you_can_see_this": "Doar tu poți vedea asta.", + "edit_message": "Editează mesajul", + "Editor": { + "edit_message": "Editează mesajul..." + } + }, + "Reactions": { + "reacted_with": "Reacționat cu" + } + }, + "RoomInput": { + "recording_duration": "Lungime înregistrare:", + "EmojiBoard": { + "no_results_found": "Nu un rezultat", + "search_results": "Rezultate căutare", + "personal_pack": "Pachet Personal", + "unknown_pack": "Pachet Necunoscut" + } + }, + "DevTools": { + "json_content": "Conținut JSON", + "account_data": "Datele contului", + "developer_tools": "Unelete Dezvoltatori" + } +} diff --git a/public/locales/ro/room/create.json b/public/locales/ro/room/create.json new file mode 100644 index 000000000..5f0c32455 --- /dev/null +++ b/public/locales/ro/room/create.json @@ -0,0 +1,19 @@ +{ + "versions": "Versiuni", + "founders": "Fondatori", + "special_privileged_users_can_be_assigned_during_creation_these_users_have_e": "In durata creări, anumite persoane pot fi privilegiate. Aceste persoana au un control ridicat si pot fi modificate doar cu o modernizare a camerei.", + "no_suggestions": "Nici o sugestie", + "please_provide_the_user_id_and_hit_enter": "Oferiti ID-ul de utilizator si apăsați Enter.", + "only_member_of_parent_space_can_join": "Doar membrii a spațiului părinte se pot alătura.", + "private": "Privată", + "only_people_with_invite_can_join": "Doar persoanele cu o invitație se pot alătura.", + "anyone_with_the_address_can_join": "Oricine are adresa se poate alătura.", + "public": "Publică", + "address_optional": "Adresă (Opțională)", + "pick_an_unique_address_to_make_it_discoverable": "Alege o adresă unică să poată fi comunitatea descoperită.", + "this_address_is_already_taken_please_select_a_different_one": "Această adresă este deja luată. Selectați una diferită.", + "voice_room": "Cameră vocală", + "live_audio_and_video_conversations": "; conversații audio-video live.", + "chat_room": "Cameră discuții", + "messages_photos_and_videos": "; Mesaje, fotografii, and videoclipuri." +} diff --git a/public/locales/ro/room/direct/invite.json b/public/locales/ro/room/direct/invite.json new file mode 100644 index 000000000..cf7ade501 --- /dev/null +++ b/public/locales/ro/room/direct/invite.json @@ -0,0 +1,7 @@ +{ + "direct_message_invite_prompt": "Aceasa este o conversație directă, menită pentru discuții între două persoane, Doriți sa o tranformați intr-un group de conversație înainte de a continua?", + "failed_to_convert_direct_message_to_room": "Transformarea convesației directe in conversație de grup a eșuat!", + "convert_to_group_chat_and_invite": "Transformă in grup și invită", + "invite_to_direct_message_anyway": "Invită la mesaje directe oricum", + "invite_another_member": "Invită altă persoana" +} diff --git a/public/locales/ro/settings/device_verification.json b/public/locales/ro/settings/device_verification.json new file mode 100644 index 000000000..1d377212c --- /dev/null +++ b/public/locales/ro/settings/device_verification.json @@ -0,0 +1,28 @@ +{ + "error_uia_action_without_data": "Eroare neașteptată! Actiunea UIA este facută fară date.", + "authentication_failed_failed_to_setup_device_verification": "Autentificare eșuată! Verificarea dispozitivului a eșuat.", + "unexpected_error_crypto_module_not_found": "Eroare neașteptată! Modulul Crypto nu poate fi accessat!", + "unexpected_error_failed_to_create_recovery_key": "Eroare neașteptată! Incercarea de a crea o cheie de rezervă a eșuat.", + "generate_a_recovery_key": "Generați o cheie de recuperare pentru a îți verifica identitatea in cazul in care nu ai access la alte dispozitive. Adițional, setează o parolă ca o alternativă mai ușoară de amintit.", + "optional_passphrase": "Parolă (optională)", + "authentication_steps_to_perform_this_action_are_not_supported_by_client": "Pași pentru autentificare nu sunt acceptați de această aplicație.", + "store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t": "Salvează cheia de recuperare intr-un loc sigur pentru a fi accesată in viitor. Vei avea nevoie de ea sa îți verifici identitatea in cazul in care nu ai acces la alte dispozitive.", + "setup_device_verification": "Configurează verificare dispozitivelor.", + "reset_device_verification": "Reseteaza Identitea de verificare a dispozitivelor", + "resetting_device_verification_is_permanent": "Resetarea verificării dispozitivelor este permanentă!", + "anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption": "Toată lumea cu care ai vorbit va primii o alertă de securitate si encriptia de rezervă va fi pierdută. Aproape sigur nu vrei să faci asta, singura situație în care ai vrea să faci asta este daca ai pierdut", + "and_every_device_you_can_verify_from": "și orice alt dispozitiv cu care ai putea verifica.", + "please_accept_the_request_from_other_device": "Acceptați cererea dintr-un alt dispozitiv.", + "click_accept_to_start_the_verification_process": "Apasa pe 'Acceptă' pentru a începe procesul de verificare.", + "waiting_for_request_to_be_accepted": "Asteptăm ca cererea să fie acceptată...", + "confirm_the_emoji_below_are_displayed_on_both_devices_in_the_same_order": "Confirmă ca emoticoanele prezentate ulterior sunt aceleași pe ambele dispozitive, și în aceași ordine:", + "they_match": "Se potrivesc", + "do_not_match": "Sunt diferite", + "starting_verification_using_emoji_comparison": "Începe verificare prin comparare de emoticoane...", + "your_device_is_verified": "Dispozitivul acesta e verificat.", + "verification_has_been_canceled": "Verificarea a forst oprită.", + "device_verification": "Verificare dispozitiv", + "unexpected_error_verification_is_started_but_verifier_is_missing": "Eroare neașteptată! Verificarea a început dar verificatorul a dispărut.", + "verification_request_has_been_accepted": "Cererea de verificare a fost acceptată.", + "waiting_for_the_response_from_other_device": "Așteptăm pentru un răspuns de la celălalt dispozitiv..." +} diff --git a/public/locales/ro/settings/experimental.json b/public/locales/ro/settings/experimental.json new file mode 100644 index 000000000..4b2a8c2df --- /dev/null +++ b/public/locales/ro/settings/experimental.json @@ -0,0 +1,22 @@ +{ + "save_bandwidth_for_sticker_and_emoji_images": "Reduceți Consumul rețelei pentru stickere si emoticoane", + "enable_bandwidth_saving_for_stickers_and_emojis": "Porneste salvare de 'bandwidth' pentru stickere si emoticoane", + "if_enabled_sticker_and_emoji_images_will_be_optimized_to_save_bandwidth_thi": "Dacă pornit, stickerele si emoticoanele vor fi optimizate pentru a consuma mai puțin internet. Asta ajută consumul de date când văzănd aceste imagini, dar crește costul de computare a server-ului.", + "personas_per_message_profiles": "Personalități. (Profiluri per-mesaj)", + "show_personas_tab": "Prezintă meniul de Personalități", + "enables_the_personas_tab_in_the_settings_menu_for_per_message_profiles": "Adauga meniul de personalități in bara de setări pentru profile specifice fiecărui mesaj.", + "experimental": "Experimental", + "the_features_listed_below_may_be_unstable_or_incomplete": "Opțiunile listate ulterior pot fi instabile sau incomplete,", + "use_at_your_own_risk": "folosește la riscul vostru", + "please_report_any_new_issues_potentially_caused_by_these_features": "Vă rugăm sa raportati orice probleme noi cauzate potențial de aceste opțiuni! Mersi!", + "enable_sharing_of_encrypted_history": "Activează împărtășirea de Istorie Criptată.", + "enable_the_sharehistory_command": "Activează comanda de /sharehistory", + "if_enabled_this_command_will_allow_users_to_share_encrypted_history_with_ot": "Dacă activată, această optiune va permite utilizatorilor să impărtașească istoria criptată cu alți utilizatori noi, per MSC4268.", + "disable_sharehistory_command": "Dezactivează comanda /sharehistory", + "enable_sharehistory_command": "Activează comanda /sharehistory", + "enable_media_galleries_support": "Activează suportul pentru galerii de media", + "enable_media_galleries_title": "Activează galeriile media", + "enable_media_galleries_description": "Dacă activat, mai multe atașamente vor fi trimise intr-un singur mesaj, pe baza MSC4272. Incompatibil cu alte aplicatii care nu mermit galerii", + "disable_media_galleries": "Dezactivează galeriile media", + "enable_media_galleries": "Activează galeriile media" +} diff --git a/public/locales/ro/settings/keyboard_shortcuts.json b/public/locales/ro/settings/keyboard_shortcuts.json new file mode 100644 index 000000000..1ee207d24 --- /dev/null +++ b/public/locales/ro/settings/keyboard_shortcuts.json @@ -0,0 +1,16 @@ +{ + "search_for_messages": "Caută mesaje", + "jump_to_the_highest_priority_unread_room": "Du-te la camera cu cea mai ridicata prioritate de necitire", + "go_to_next_unread_room_cycle": "Du-te la următoarea camera necitită (urmează un ciclu)", + "go_to_previous_unread_room_cycle": "Go to previous unread room (urmează un ciclu)", + "undo_in_message_editor": "Șterge în editorul de mesaje", + "redo_in_message_editor": "Reface în editorul de mesaje", + "seach_and_go_to_room": "Caută și du-te la cameră", + "open_sticker_picker": "Deschide pachet de stickere", + "bold": "Îngroșat", + "italic": "Italic", + "underline": "Subliniat", + "general": "General", + "navigation": "Navigație", + "messages": "Mesaje" +} diff --git a/public/locales/ro/settings/persona.json b/public/locales/ro/settings/persona.json new file mode 100644 index 000000000..93dd8e48c --- /dev/null +++ b/public/locales/ro/settings/persona.json @@ -0,0 +1,21 @@ +{ + "limited_compatibility_with_pluralkit_like_functions": "Compatibilitate limitată cu functii similare cu PluralKit", + "enable_pk_commands": "Activează comenzi PK", + "if_enabled_it_will_enable_a_few_pk_style_commands_currently_verry_limited": "Dacă activat, va permite folosirea de comenzi in stil PK, deocamdată foarte limitate", + "disable_pk_commands": "dezactivează comenzile pk;", + "enable_pks_commands": "activează comenzile pk;", + "enable_shorthands": "Activează scurtături", + "if_enabled_you_can_use_shorthands_to_use_a_persona_for_one_message_only_eg": "Dacă activat, vei putea folosii scuraturi pentru a folosi personalități pentru un mesaj (ex. '✨:test')", + "disable_checking_typed_messages_for_shorthands": "dezactivează verificarea mesajelor scrise pentru scurataturi", + "enable_checking_typed_messages_for_shorthands": "activeaza verificarea mesajelor scrise pentru scurataturi", + "profile_id": "ID profil:", + "pronouns": "Pronume:", + "reset_pronouns": "Resetează Pronumele", + "display_name": "Nume Afișat:", + "delete_profile": "Șterge profilul {{profileId}}", + "save_profile": "Salvează schimbările pentru {{profileId}}", + "save_profile_button_area": "Zona butonului de salvare a profilului {{profileId}}", + "pronouns_for": "Pronumele profilului {{profileId}}", + "avatar_for": "Avatarul profilului {{profileId}}", + "display_name_for": "Numele afișat pentru {{profileId}}" +} diff --git a/public/locales/ro/settings/profile.json b/public/locales/ro/settings/profile.json new file mode 100644 index 000000000..d85dda76a --- /dev/null +++ b/public/locales/ro/settings/profile.json @@ -0,0 +1,8 @@ +{ + "avatar_and_upload": "Imagine de profil si încărcare", + "profile_avatar": "Imagine de profil", + "avatar_fallback": "Imagine de rezervă", + "upload_avatar_image": "Încarcă imagine de profil", + "display_name_input": "Afiș introducere nume de profil", + "reset_display_name": "Resetează numele afișat" +} diff --git a/src/app/components/DeviceVerificationSetup.tsx b/src/app/components/DeviceVerificationSetup.tsx index 3ce6be146..2320dbb35 100644 --- a/src/app/components/DeviceVerificationSetup.tsx +++ b/src/app/components/DeviceVerificationSetup.tsx @@ -15,7 +15,7 @@ import { useAlive } from '$hooks/useAlive'; import { PasswordInput } from './password-input'; import { ActionUIA, ActionUIAFlowsLoader } from './ActionUIA'; import { UseStateProvider } from './UseStateProvider'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; type UIACallback = ( authDict: AuthDict | null @@ -58,14 +58,8 @@ function makeUIAAction( return action; } -function renderUnsupportedUIAFlow() { - return ( - - {t( - 'Settings.device_verification.authentication_steps_to_perform_this_action_are_not_supported_by_client' - )} - - ); +function renderUnsupportedUIAFlow(unsupportedFlow: string) { + return {unsupportedFlow}; } type SetupVerificationUIAProps = { @@ -92,6 +86,7 @@ type SetupVerificationProps = { function SetupVerification({ onComplete }: Readonly) { const mx = useMatrixClient(); const alive = useAlive(); + const { t } = useTranslation(['settings/device_verification', 'general']); const [uiaAction, setUIAAction] = useState>(); const [nextAuthData, setNextAuthData] = useState(); // null means no next action. @@ -99,7 +94,7 @@ function SetupVerification({ onComplete }: Readonly) { const handleAction = useCallback( async (authDict: AuthDict) => { if (!uiaAction) { - throw new Error(t('Settings.device_verification.error_uia_action_without_data')); + throw new Error(t('error_uia_action_without_data')); } if (alive()) { setNextAuthData(null); @@ -110,7 +105,7 @@ function SetupVerification({ onComplete }: Readonly) { setNextAuthData(authData); } }, - [uiaAction, alive] + [uiaAction, alive, t] ); const resetUIA = useCallback(() => { @@ -142,36 +137,25 @@ function SetupVerification({ onComplete }: Readonly) { if (alive()) { setUIAAction(action); } else { - reject( - new Error( - t( - 'Settings.device_verification.authentication_failed_failed_to_setup_device_verification' - ) - ) - ); + reject(new Error(t('authentication_failed_failed_to_setup_device_verification'))); } return; } reject(error); }); }), - [alive, resetUIA] + [alive, resetUIA, t] ); const [setupState, setup] = useAsyncCallback( useCallback( async (passphrase) => { const crypto = mx.getCrypto(); - if (!crypto) - throw new Error( - t('Settings.device_verification.unexpected_error_crypto_module_not_found') - ); + if (!crypto) throw new Error(t('unexpected_error_crypto_module_not_found')); const recoveryKeyData = await crypto.createRecoveryKeyFromPassphrase(passphrase); if (!recoveryKeyData.encodedPrivateKey) { - throw new Error( - t('Settings.device_verification.unexpected_error_failed_to_create_recovery_key') - ); + throw new Error(t('unexpected_error_failed_to_create_recovery_key')); } clearSecretStorageKeys(); @@ -189,7 +173,7 @@ function SetupVerification({ onComplete }: Readonly) { onComplete(recoveryKeyData.encodedPrivateKey); }, - [mx, onComplete, authUploadDeviceSigningKeys] + [mx, onComplete, authUploadDeviceSigningKeys, t] ) ); @@ -226,13 +210,9 @@ function SetupVerification({ onComplete }: Readonly) { return ( - - {t('Settings.device_verification.generate_a_recovery_key')} - + {t('generate_a_recovery_key')} - - {t('Settings.device_verification.optional_passphrase')} - + {t('optional_passphrase')} {setupState.status === AsyncStatus.Error && ( - {setupState.error ? setupState.error.message : t('General.unexpected_error')} + + {setupState.error ? setupState.error.message : t('unexpected_error', { ns: 'general' })} + )} {nextAuthData !== null && uiaAction && ( + renderUnsupportedUIAFlow( + t('authentication_steps_to_perform_this_action_are_not_supported_by_client') + ) + } > {renderSetupVerificationUIA} @@ -264,6 +250,7 @@ type RecoveryKeyDisplayProps = { }; function RecoveryKeyDisplay({ recoveryKey }: Readonly) { const [show, setShow] = useState(false); + const { t } = useTranslation(['settings/device_verification', 'general']); const handleCopy = () => { copyToClipboard(recoveryKey); @@ -281,12 +268,10 @@ function RecoveryKeyDisplay({ recoveryKey }: Readonly) return ( - {t( - 'Settings.device_verification.store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t' - )} + {t('store_the_recovery_key_in_a_safe_place_for_future_use_as_you_will_need_it_t')} - {t('General.recovery_key')} + {t('recovery_key', { ns: 'general' })} ) {safeToDisplayKey} setShow(!show)} variant="Secondary" radii="Pill"> - {show ? t('General.hide') : t('General.show')} + + {show ? t('hide', { ns: 'general' }) : t('show', { ns: 'general' })} +
@@ -323,6 +310,7 @@ type DeviceVerificationSetupProps = { export const DeviceVerificationSetup = forwardRef( ({ onCancel }, ref) => { const [recoveryKey, setRecoveryKey] = useState(); + const { t } = useTranslation(['settings/device_verification']); return ( @@ -335,7 +323,7 @@ export const DeviceVerificationSetup = forwardRef - {t('Settings.device_verification.setup_device_verification')} + {t('setup_device_verification')} {composerIcon(X)} @@ -358,6 +346,7 @@ type DeviceVerificationResetProps = { export const DeviceVerificationReset = forwardRef( ({ onCancel }, ref) => { const [reset, setReset] = useState(false); + const { t } = useTranslation(['settings/device_verification', 'general']); return ( @@ -370,7 +359,7 @@ export const DeviceVerificationReset = forwardRef - {t('Settings.device_verification.reset_device_verification')} + {t('reset_device_verification')} {composerIcon(X)} @@ -392,19 +381,16 @@ export const DeviceVerificationReset = forwardRef ✋🧑‍🚒🤚 + {t('resetting_device_verification_is_permanent')} - {t('Settings.device_verification.resetting_device_verification_is_permanent')} - - - {t( - 'Settings.device_verification.anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption' - )}{' '} - {t('General.recovery_key')} / {t('General.recovery_passphrase')}{' '} - {t('Settings.device_verification.and_every_device_you_can_verify_from')} + {t('anyone_you_have_verified_with_will_see_security_alerts_and_your_encryption')}{' '} + {t('recovery_key', { ns: 'general' })} /{' '} + {t('recovery_passphrase', { ns: 'general' })}{' '} + {t('and_every_device_you_can_verify_from')}
)} diff --git a/src/app/components/create-room/AdditionalCreatorInput.tsx b/src/app/components/create-room/AdditionalCreatorInput.tsx index 5b651290a..4f82f26d0 100644 --- a/src/app/components/create-room/AdditionalCreatorInput.tsx +++ b/src/app/components/create-room/AdditionalCreatorInput.tsx @@ -27,7 +27,7 @@ import type { UseAsyncSearchOptions } from '$hooks/useAsyncSearch'; import { useAsyncSearch } from '$hooks/useAsyncSearch'; import { highlightText, makeHighlightRegex } from '$plugins/react-custom-html-parser'; import { SettingTile } from '$components/setting-tile'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; export const useAdditionalCreators = (defaultCreators?: string[]) => { const mx = useMatrixClient(); @@ -83,6 +83,7 @@ export function AdditionalCreatorInput({ const mx = useMatrixClient(); const [menuCords, setMenuCords] = useState(); const directUsers = useDirectUsers(); + const { t } = useTranslation('room/create'); const [validUserId, setValidUserId] = useState(); const filteredUsers = useMemo( @@ -147,10 +148,8 @@ export function AdditionalCreatorInput({ return ( @@ -265,10 +264,10 @@ export function AdditionalCreatorInput({ gap="100" > - {t('RoomCreate.no_suggestions')} + {t('no_suggestions')} - {t('RoomCreate.please_provide_the_user_id_and_hit_enter')} + {t('please_provide_the_user_id_and_hit_enter')} )} diff --git a/src/app/components/create-room/CreateRoomAccessSelector.tsx b/src/app/components/create-room/CreateRoomAccessSelector.tsx index 2a2c44fc5..61faa66e2 100644 --- a/src/app/components/create-room/CreateRoomAccessSelector.tsx +++ b/src/app/components/create-room/CreateRoomAccessSelector.tsx @@ -4,7 +4,7 @@ import { Check, sizedIcon } from '$components/icons/phosphor'; import { SequenceCard } from '$components/sequence-card'; import { SettingTile } from '$components/setting-tile'; import { CreateRoomAccess } from './types'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; type CreateRoomAccessSelectorProps = { value?: CreateRoomAccess; @@ -20,6 +20,7 @@ export function CreateRoomAccessSelector({ disabled, getIcon, }: CreateRoomAccessSelectorProps) { + const { t } = useTranslation('room/create'); return ( {canRestrict && ( @@ -40,7 +41,7 @@ export function CreateRoomAccessSelector({ > Restricted - {t('RoomCreate.only_member_of_parent_space_can_join')} + {t('only_member_of_parent_space_can_join')} @@ -60,9 +61,9 @@ export function CreateRoomAccessSelector({ before={getIcon(CreateRoomAccess.Private)} after={value === CreateRoomAccess.Private && sizedIcon(Check)} > - {t('RoomCreate.private')} + {t('private')} - {t('RoomCreate.only_people_with_invite_can_join')} + {t('only_people_with_invite_can_join')} @@ -81,9 +82,9 @@ export function CreateRoomAccessSelector({ before={getIcon(CreateRoomAccess.Public)} after={value === CreateRoomAccess.Public && sizedIcon(Check)} > - {t('RoomCreate.public')} + {t('public')} - {t('RoomCreate.anyone_with_the_address_can_join')} + {t('anyone_with_the_address_can_join')} diff --git a/src/app/components/direct-invite-prompt/DirectInvitePrompt.tsx b/src/app/components/direct-invite-prompt/DirectInvitePrompt.tsx index b5e91953d..c706d569e 100644 --- a/src/app/components/direct-invite-prompt/DirectInvitePrompt.tsx +++ b/src/app/components/direct-invite-prompt/DirectInvitePrompt.tsx @@ -15,7 +15,7 @@ import { } from 'folds'; import { composerIcon, X } from '$components/icons/phosphor'; import { stopPropagation } from '$utils/keyboard'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; type DirectInvitePromptProps = { onCancel: () => void; @@ -32,6 +32,7 @@ export function DirectInvitePrompt({ converting, convertError, }: DirectInvitePromptProps) { + const { t } = useTranslation(['room/direct/invite', 'general']); return ( }> @@ -53,7 +54,7 @@ export function DirectInvitePrompt({ size="500" > - {t('Room.DirectInvite.invite_another_member')} + {t('invite_another_member')} {composerIcon(X)} @@ -61,10 +62,10 @@ export function DirectInvitePrompt({
- {t('Room.DirectInvite.direct_message_invite_prompt')} + {t('direct_message_invite_prompt')} {convertError && ( - {t('Room.DirectInvite.failed_to_convert_direct_message_to_room')} {convertError} + {t('failed_to_convert_direct_message_to_room')} {convertError} )} @@ -79,9 +80,7 @@ export function DirectInvitePrompt({ aria-disabled={converting} > - {converting - ? 'Converting...' - : t('Room.DirectInvite.convert_to_group_chat_and_invite')} + {converting ? 'Converting...' : t('convert_to_group_chat_and_invite')} diff --git a/src/app/components/message/Reply.tsx b/src/app/components/message/Reply.tsx index 1178b81b6..f19eff524 100644 --- a/src/app/components/message/Reply.tsx +++ b/src/app/components/message/Reply.tsx @@ -277,7 +277,7 @@ export const Reply = as<'div', ReplyProps>( const ignoredUsers = useIgnoredUsers(); const isBlockedSender = !!sender && ignoredUsers.includes(sender); - const { t } = useTranslation(); + const { t } = useTranslation('general'); const parseMemberEvent = useMemberEventParser(); diff --git a/src/app/features/room/RoomViewTyping.tsx b/src/app/features/room/RoomViewTyping.tsx index 8d4560a52..771632574 100644 --- a/src/app/features/room/RoomViewTyping.tsx +++ b/src/app/features/room/RoomViewTyping.tsx @@ -63,7 +63,7 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>( <> {typingNames[0]} - {t('RoomView.is_typing')} + {` ${t('RoomView.is_typing')}`} )} diff --git a/src/app/features/settings/Persona/PKCompat.tsx b/src/app/features/settings/Persona/PKCompat.tsx index de2ad52e0..aee5d23ab 100644 --- a/src/app/features/settings/Persona/PKCompat.tsx +++ b/src/app/features/settings/Persona/PKCompat.tsx @@ -4,17 +4,16 @@ import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { Box, Switch, Text } from 'folds'; import { SequenceCardStyle } from '../styles.css'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; export function PKCompatSettings() { const [usePKCompat, setUsePKCompat] = useSetting(settingsAtom, 'pkCompat'); const [usePmpProxying, setUsePmpProxying] = useSetting(settingsAtom, 'pmpProxying'); + const { t } = useTranslation(['settings/persona']); return ( - - {t('Settings.Persona.limited_compatibility_with_pluralkit_like_functions')} - + {t('limited_compatibility_with_pluralkit_like_functions')} } /> } diff --git a/src/app/features/settings/Persona/PerMessageProfileEditor.tsx b/src/app/features/settings/Persona/PerMessageProfileEditor.tsx index b4de0e0a2..b5df38b39 100644 --- a/src/app/features/settings/Persona/PerMessageProfileEditor.tsx +++ b/src/app/features/settings/Persona/PerMessageProfileEditor.tsx @@ -18,7 +18,7 @@ import { import type { PronounSet } from '$utils/pronouns'; import { parsePronounsStringToPronounsSetArray } from '$utils/pronouns'; import { SequenceCardStyle } from '../styles.css'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; /** * the props we use for the per-message profile editor, which is used to edit a per-message profile. This is used in the settings page when the user wants to edit a profile. @@ -46,6 +46,7 @@ export function PerMessageProfileEditor({ const [currentDisplayName, setCurrentDisplayName] = useState(displayName ?? ''); const [currentId, setCurrentId] = useState(profileId); const [newId, setNewId] = useState(profileId); + const { t } = useTranslation(['settings/persona', 'settings/profile', 'general']); console.warn(pronouns); @@ -228,7 +229,7 @@ export function PerMessageProfileEditor({ style={{ width: '100%', marginBottom: config.space.S200 }} > - {t('Settings.PerMessageProfiles.profile_id')} + {t('profile_id')} @@ -265,7 +266,7 @@ export function PerMessageProfileEditor({ overflow: 'visible', marginTop: 20, }} - aria-label={t('Settings.Profile.avatar_and_upload')} + aria-label={t('settings/profile:avatar_and_upload')} > ( - + p )} - alt={`Avatar for profile ${profileId}`} + alt={t('avatar_for', { profileId })} /> {uploadAtom && ( - {t('Settings.PerMessageProfiles.display_name')} + {t('display_name')} {menuIcon(X)} @@ -391,7 +392,7 @@ export function PerMessageProfileEditor({ alignSelf: 'flex-start', }} > - Pronouns: + {t('pronouns')} {menuIcon(X)} @@ -442,7 +443,7 @@ export function PerMessageProfileEditor({ flexShrink: 0, height: '100%', }} - aria-label={`Save button area for ${profileId}`} + aria-label={t('save_profile_button_area', { profileId })} > diff --git a/src/app/features/settings/cosmetics/Cosmetics.tsx b/src/app/features/settings/cosmetics/Cosmetics.tsx index 0cf62439b..8dda0d737 100644 --- a/src/app/features/settings/cosmetics/Cosmetics.tsx +++ b/src/app/features/settings/cosmetics/Cosmetics.tsx @@ -208,7 +208,7 @@ function IconSizeSettings() { } function SelectJumboEmojiSize() { - const { t } = useTranslation(); + const { t } = useTranslation('general'); const emojiSizeItems = [ { id: 'none', name: t('Settings.Cosmetics.none_same_size_as_text') }, @@ -287,7 +287,7 @@ function SelectJumboEmojiSize() { } function SelectRenderCustomProfileCards() { - const { t } = useTranslation(); + const { t } = useTranslation('general'); const profileCardRenderItems: { id: RenderUserCardsMode; name: string }[] = [ { id: 'both', name: t('Settings.Cosmetics.light_and_dark') }, { id: 'light', name: t('Settings.Cosmetics.light_only') }, @@ -365,7 +365,7 @@ function SelectRenderCustomProfileCards() { } function JumboEmoji() { - const { t } = useTranslation(); + const { t } = useTranslation('general'); return ( {t('Settings.Cosmetics.jumbo_emoji')} @@ -382,7 +382,7 @@ function JumboEmoji() { } function Privacy() { - const { t } = useTranslation(); + const { t } = useTranslation('general'); const [privacyBlur, setPrivacyBlur] = useSetting(settingsAtom, 'privacyBlur'); const [privacyBlurAvatars, setPrivacyBlurAvatars] = useSetting( settingsAtom, @@ -429,7 +429,7 @@ function Privacy() { } function IdentityCosmetics() { - const { t } = useTranslation(); + const { t } = useTranslation('general'); const [legacyUsernameColor, setLegacyUsernameColor] = useSetting( settingsAtom, 'legacyUsernameColor' diff --git a/src/app/features/settings/experimental/BandwithSavingEmojis.tsx b/src/app/features/settings/experimental/BandwithSavingEmojis.tsx index e815e881c..9b0144084 100644 --- a/src/app/features/settings/experimental/BandwithSavingEmojis.tsx +++ b/src/app/features/settings/experimental/BandwithSavingEmojis.tsx @@ -4,17 +4,18 @@ import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { Box, Switch, Text } from 'folds'; import { SequenceCardStyle } from '../styles.css'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; export function BandwidthSavingEmojis() { const [useBandwidthSaving, setUseBandwidthSaving] = useSetting( settingsAtom, 'saveStickerEmojiBandwidth' ); + const { t } = useTranslation(['settings/experimental']); return ( - {t('Settings.save_bandwidth_for_sticker_and_emoji_images')} + {t('save_bandwidth_for_sticker_and_emoji_images')} diff --git a/src/app/features/settings/experimental/Experimental.tsx b/src/app/features/settings/experimental/Experimental.tsx index a2f8d24c3..2ea22c2a4 100644 --- a/src/app/features/settings/experimental/Experimental.tsx +++ b/src/app/features/settings/experimental/Experimental.tsx @@ -11,25 +11,24 @@ import { Sync } from '../general'; import { SettingsSectionPage } from '../SettingsSectionPage'; import { BandwidthSavingEmojis } from './BandwithSavingEmojis'; import { MSC4268HistoryShare } from './MSC4268HistoryShare'; -import { t } from 'i18next'; import { MSC4274MediaGalleries } from './MSC4274MediaGalleries'; +import { useTranslation } from 'react-i18next'; function PersonaToggle() { const [showPersonaSetting, setShowPersonaSetting] = useSetting( settingsAtom, 'showPersonaSetting' ); + const { t } = useTranslation(['settings/experimental']); return ( - {t('Settings.personas_per_message_profiles')} + {t('personas_per_message_profiles')} } @@ -44,9 +43,10 @@ type ExperimentalProps = { requestClose: () => void; }; export function Experimental({ requestBack, requestClose }: Readonly) { + const { t } = useTranslation(['settings/experimental']); return ( @@ -58,10 +58,10 @@ export function Experimental({ requestBack, requestClose }: Readonly - {t('Settings.the_features_listed_below_may_be_unstable_or_incomplete')}{' '} - {t('Settings.use_at_your_own_risk')}. + {t('the_features_listed_below_may_be_unstable_or_incomplete')}{' '} + {t('use_at_your_own_risk')}.
- {t('Settings.please_report_any_new_issues_potentially_caused_by_these_features')} + {t('please_report_any_new_issues_potentially_caused_by_these_features')} } /> diff --git a/src/app/features/settings/experimental/MSC4268HistoryShare.tsx b/src/app/features/settings/experimental/MSC4268HistoryShare.tsx index 21f3b472b..94e4df345 100644 --- a/src/app/features/settings/experimental/MSC4268HistoryShare.tsx +++ b/src/app/features/settings/experimental/MSC4268HistoryShare.tsx @@ -4,17 +4,18 @@ import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { Box, Switch, Text } from 'folds'; import { SequenceCardStyle } from '../styles.css'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; export function MSC4268HistoryShare() { const [enabledMSC4268Command, setEnabledMSC4268Command] = useSetting( settingsAtom, 'enableMSC4268CMD' ); + const { t } = useTranslation(['settings/experimental']); return ( - {t('Settings.enable_sharing_of_encrypted_history')} + {t('enable_sharing_of_encrypted_history')} } diff --git a/src/app/features/settings/experimental/MSC4274MediaGalleries.tsx b/src/app/features/settings/experimental/MSC4274MediaGalleries.tsx index 397f9306c..0128405b3 100644 --- a/src/app/features/settings/experimental/MSC4274MediaGalleries.tsx +++ b/src/app/features/settings/experimental/MSC4274MediaGalleries.tsx @@ -4,16 +4,18 @@ import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { Box, Switch, Text } from 'folds'; import { SequenceCardStyle } from '../styles.css'; +import { useTranslation } from 'react-i18next'; export function MSC4274MediaGalleries() { const [enabledMediaGalleries, setEnabledMediaGalleries] = useSetting( settingsAtom, 'enableMediaGalleries' ); + const { t } = useTranslation(['settings/experimental']); return ( - Enable Media Galleries Support + {t('enable_media_galleries_support')} } /> diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 259e28101..510a4bfcf 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -435,7 +435,7 @@ function DateAndTime() { } function LanguageChange() { - const { i18n } = useTranslation(); + const { i18n } = useTranslation('general'); const languageOptions: SettingMenuOption[] = [ { value: '', label: 'System' }, diff --git a/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx b/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx index f1fd741db..ac0bba256 100644 --- a/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx +++ b/src/app/features/settings/keyboard-shortcuts/KeyboardShortcuts.tsx @@ -7,7 +7,7 @@ import { Box, Scroll, Text, config } from 'folds'; import { PageContent } from '$components/page'; import { SettingsSectionPage } from '../SettingsSectionPage'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; type ShortcutEntry = { keys: string; @@ -19,53 +19,53 @@ type ShortcutCategory = { shortcuts: ShortcutEntry[]; }; -function formatKey(key: string): string { - const isMac = - typeof navigator !== 'undefined' && navigator.platform.toUpperCase().indexOf('MAC') >= 0; - return key - .replace(/\bmod\b/g, isMac ? '⌘' : 'Ctrl') - .replace(/\balt\b/gi, isMac ? '⌥' : 'Alt') - .replace(/\bshift\b/gi, '⇧'); -} - +// uses translation keys to avoid initializing it too early const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { - name: 'General', - shortcuts: [{ keys: 'Ctrl+F / ⌘+F', description: 'Search for messages' }], + name: 'general', + shortcuts: [{ keys: 'Ctrl+F / ⌘+F', description: 'search_for_messages' }], }, { - name: 'Navigation', + name: 'navigation', shortcuts: [ { keys: 'Alt+N', - description: t('Settings.KeyboardShortcuts.jump_to_the_highest_priority_unread_room'), + description: 'jump_to_the_highest_priority_unread_room', }, { keys: 'Alt+Shift+Down', - description: t('Settings.KeyboardShortcuts.go_to_next_unread_room_cycle'), + description: 'go_to_next_unread_room_cycle', }, { keys: 'Alt+Shift+Up', - description: t('Settings.KeyboardShortcuts.go_to_previous_unread_room_cycle'), + description: 'go_to_previous_unread_room_cycle', }, - { keys: 'Ctrl+K / ⌘+K', description: 'Search and go to Room' }, + { keys: 'Ctrl+K / ⌘+K', description: 'seach_and_go_to_room' }, ], }, { - name: t('Settings.KeyboardShortcuts.messages'), + name: 'messages', shortcuts: [ - { keys: 'Ctrl+Z / ⌘+Z', description: t('Settings.KeyboardShortcuts.undo_in_message_editor') }, + { keys: 'Ctrl+Z / ⌘+Z', description: 'undo_in_message_editor' }, { keys: 'Ctrl+Shift+Z / ⌘+Shift+Z', - description: t('Settings.KeyboardShortcuts.redo_in_message_editor'), + description: 'redo_in_message_editor', }, - { keys: 'Ctrl+B / ⌘+B', description: t('Settings.KeyboardShortcuts.bold') }, - { keys: 'Ctrl+I / ⌘+I', description: t('Settings.KeyboardShortcuts.italic') }, - { keys: 'Ctrl+U / ⌘+U', description: t('Settings.KeyboardShortcuts.underline') }, - { keys: 'Ctrl+E / ⌘+E', description: 'Open Sticker Picker' }, + { keys: 'Ctrl+B / ⌘+B', description: 'bold' }, + { keys: 'Ctrl+I / ⌘+I', description: 'italic' }, + { keys: 'Ctrl+U / ⌘+U', description: 'underline' }, + { keys: 'Ctrl+E / ⌘+E', description: 'open_sticker_picker' }, ], }, ]; +function formatKey(key: string): string { + const isMac = + typeof navigator !== 'undefined' && navigator.platform.toUpperCase().indexOf('MAC') >= 0; + return key + .replace(/\bmod\b/g, isMac ? '⌘' : 'Ctrl') + .replace(/\balt\b/gi, isMac ? '⌥' : 'Alt') + .replace(/\bshift\b/gi, '⇧'); +} function ShortcutRow({ keys, description }: ShortcutEntry) { const parts = keys.split('/').map((k) => k.trim()); @@ -129,11 +129,13 @@ type KeyboardShortcutsProps = { requestClose: () => void; }; export function KeyboardShortcuts({ requestBack, requestClose }: KeyboardShortcutsProps) { + const { t } = useTranslation(['settings/keyboard_shortcuts']); + return ( @@ -144,14 +146,14 @@ export function KeyboardShortcuts({ requestBack, requestClose }: KeyboardShortcu {SHORTCUT_CATEGORIES.map((category) => ( - {category.name} + {t(category.name)}
{category.shortcuts.map((entry) => (
{entry.keys}
- +
))} diff --git a/src/app/hooks/timeline/useTimelineEventRenderer.tsx b/src/app/hooks/timeline/useTimelineEventRenderer.tsx index 3b8d639b6..2a0038d1f 100644 --- a/src/app/hooks/timeline/useTimelineEventRenderer.tsx +++ b/src/app/hooks/timeline/useTimelineEventRenderer.tsx @@ -382,7 +382,7 @@ export function useTimelineEventRenderer({ }, utils: { htmlReactParserOptions, linkifyOpts, getMemberPowerTag, parseMemberEvent }, }: TimelineEventRendererOptions) { - const { t } = useTranslation(); + const { t } = useTranslation('general'); const { hiddenEventEdits, hiddenEventRedactionTimeline, diff --git a/src/app/i18n.ts b/src/app/i18n.ts index 977066f9f..be7981354 100644 --- a/src/app/i18n.ts +++ b/src/app/i18n.ts @@ -18,22 +18,22 @@ i18n // init i18next // for all options read: https://www.i18next.com/overview/configuration-options .init({ - // Prefer the browser / navigator language and avoid using cached localStorage value + defaultNS: 'general', + fallbackLng: 'en', + load: 'languageOnly', + interpolation: { + escapeValue: false, + }, detection: { - // prefer querystring first (e.g. ?lng=de), then storage,System then navigator, then html tag, path, subdomain order: ['querystring', 'localStorage', 'navigator', 'htmlTag', 'path', 'subdomain'], lookupQuerystring: 'lng', - // do not cache the detected language in localStorage to avoid stale overrides caches: ['localStorage'], }, - debug: false, - fallbackLng: 'en', - interpolation: { - escapeValue: false, // not needed for react as it escapes by default - }, - load: 'languageOnly', backend: { - loadPath: `${trimTrailingSlash(import.meta.env.BASE_URL)}/public/locales/{{lng}}.json`, + loadPath: `${trimTrailingSlash(import.meta.env.BASE_URL)}/public/locales/{{lng}}/{{ns}}.json`, + }, + react: { + useSuspense: false, }, });