From 6730a242dbfa487d9bd774c28061a206002fa311 Mon Sep 17 00:00:00 2001 From: Hampus Date: Fri, 25 Sep 2026 13:43:34 +0200 Subject: [PATCH] feat(web): prepare the fluxer.com domain migration (#2952) --- fluxer_admin/openapi-admin.json | 66 ++ fluxer_admin/src/api/types/instance_config.rs | 87 ++ fluxer_admin/src/routes/system_actions.rs | 162 +++- .../src/templates/pages/instance_config.rs | 159 +++- fluxer_admin/tests/api_deserialization.rs | 19 + fluxer_admin/tests/htmx_acceptance.rs | 11 + fluxer_api/src/api/App.ts | 2 +- fluxer_api/src/api/Config.ts | 1 + .../InstanceConfigAdminController.ts | 16 + .../InstanceConfigAdminController.test.ts | 14 + fluxer_api/src/api/app/ControllerRegistry.ts | 2 + fluxer_api/src/api/auth/AuthController.ts | 1 + fluxer_api/src/api/auth/AuthRequestService.ts | 55 +- .../src/api/auth/OriginHandoffController.ts | 88 ++ .../auth/services/DesktopHandoffService.ts | 7 +- .../api/auth/services/OriginHandoffService.ts | 57 ++ .../src/api/auth/tests/OriginHandoff.test.ts | 213 +++++ fluxer_api/src/api/config/APIConfig.ts | 1 + .../api/experiment/ExperimentController.ts | 5 +- .../tests/ExperimentController.test.ts | 117 +++ .../instance/InstanceConfigRepository.test.ts | 91 ++ .../api/instance/InstanceConfigRepository.ts | 32 + .../api/instance/InstanceController.test.ts | 33 + .../src/api/instance/InstanceController.ts | 7 +- .../middleware/BlockAppOriginMiddleware.ts | 8 +- fluxer_api/src/api/openapi/openapi.json | 199 ++++- .../rate_limit_configs/AuthRateLimitConfig.ts | 8 + .../stripe/services/StripeCheckoutService.ts | 3 +- .../user/controllers/UserAccountController.ts | 10 +- .../api/user/services/UserContentService.ts | 95 +- .../user/services/WebPushOriginReplacement.ts | 113 +++ .../user/tests/PushOriginReplacement.test.ts | 383 ++++++++ fluxer_api/src/api/utils/InviteUtils.ts | 7 +- fluxer_api/src/api/utils/UnfurlerUtils.ts | 18 +- .../scripts/build/rspack/static-files.mjs | 4 +- fluxer_app/src/app/Routes.ts | 1 + .../app/router/components/RootComponent.tsx | 2 +- .../src/app/router/routes/RootRoutes.tsx | 7 + .../src/app/router/routes/RouterRoutes.ts | 3 +- .../app/components/layout/GuildsLayout.tsx | 1 + .../layout/app_layout/AppLayoutHooks.ts | 13 + .../layout/app_layout/AppLayoutTypes.ts | 2 + .../layout/app_layout/NagbarContainer.tsx | 9 + .../app_layout/nagbars/DomainMovedNagbar.tsx | 176 ++++ .../domain_migration/DomainMigration.test.ts | 818 ++++++++++++++++++ .../DomainMigrationBrowser.ts | 127 +++ .../domain_migration/DomainMigrationCore.ts | 523 +++++++++++ .../domain_migration/DomainMigrationCrypto.ts | 84 ++ .../DomainMigrationPreMount.ts | 607 +++++++++++++ .../DomainMigrationRollout.ts | 22 + .../DomainMigrationTrigger.ts | 119 +++ .../app/domain_migration/DomainMovedNotice.ts | 95 ++ .../DomainMovedStepsModal.module.css | 20 + .../DomainMovedStepsModal.tsx | 132 +++ fluxer_app/src/features/app/state/Updater.ts | 7 +- .../components/modals/PasskeyNameModal.tsx | 6 + .../modals/SudoVerificationModal.tsx | 4 + .../state/useAuthorizeFlow.ts | 3 +- .../features/auth/flow/AuthLoginLayout.tsx | 49 ++ .../auth/flow/BrowserLoginHandoffModal.tsx | 62 +- .../flow/DesktopHandoffAccountSelector.tsx | 2 +- .../features/auth/flow/HandoffCodeDisplay.tsx | 14 +- .../src/features/auth/hooks/useLoginFlow.ts | 15 +- .../src/features/auth/state/AccountStorage.ts | 7 + .../features/auth/utils/OldAppSignIn.test.ts | 45 + .../src/features/auth/utils/OldAppSignIn.ts | 24 + .../src/features/auth/utils/WebAuthnUtils.ts | 46 +- .../devtools/components/NagbarControls.ts | 17 + .../features/gateway/events/GatewayReady.ts | 2 + .../src/features/gift/utils/GiftCodeUtils.ts | 2 + .../src/features/i18n/locales/ar/messages.po | 268 ++++-- .../locales/auto-i18n-reviewed-unchanged.json | 72 ++ .../src/features/i18n/locales/bg/messages.po | 268 ++++-- .../src/features/i18n/locales/cs/messages.po | 268 ++++-- .../src/features/i18n/locales/da/messages.po | 268 ++++-- .../src/features/i18n/locales/de/messages.po | 268 ++++-- .../src/features/i18n/locales/el/messages.po | 268 ++++-- .../features/i18n/locales/en-GB/messages.po | 271 ++++-- .../features/i18n/locales/en-US/messages.po | 268 ++++-- .../features/i18n/locales/es-419/messages.po | 268 ++++-- .../features/i18n/locales/es-ES/messages.po | 268 ++++-- .../src/features/i18n/locales/fi/messages.po | 268 ++++-- .../src/features/i18n/locales/fr/messages.po | 268 ++++-- .../src/features/i18n/locales/he/messages.po | 268 ++++-- .../src/features/i18n/locales/hi/messages.po | 268 ++++-- .../src/features/i18n/locales/hr/messages.po | 268 ++++-- .../src/features/i18n/locales/hu/messages.po | 268 ++++-- .../src/features/i18n/locales/id/messages.po | 268 ++++-- .../src/features/i18n/locales/it/messages.po | 268 ++++-- .../src/features/i18n/locales/ja/messages.po | 268 ++++-- .../src/features/i18n/locales/ko/messages.po | 268 ++++-- .../src/features/i18n/locales/lt/messages.po | 268 ++++-- .../src/features/i18n/locales/nl/messages.po | 268 ++++-- .../src/features/i18n/locales/no/messages.po | 268 ++++-- .../src/features/i18n/locales/pl/messages.po | 268 ++++-- .../features/i18n/locales/pt-BR/messages.po | 268 ++++-- .../src/features/i18n/locales/ro/messages.po | 268 ++++-- .../src/features/i18n/locales/ru/messages.po | 268 ++++-- .../features/i18n/locales/sv-SE/messages.po | 268 ++++-- .../src/features/i18n/locales/th/messages.po | 268 ++++-- .../src/features/i18n/locales/tr/messages.po | 268 ++++-- .../src/features/i18n/locales/uk/messages.po | 268 ++++-- .../src/features/i18n/locales/vi/messages.po | 268 ++++-- .../features/i18n/locales/zh-CN/messages.po | 268 ++++-- .../features/i18n/locales/zh-TW/messages.po | 268 ++++-- .../src/features/invite/utils/InviteUtils.ts | 2 + .../navigation/utils/DeepLinkUtils.ts | 9 +- .../platform/push/PushSubscriptionService.ts | 4 + .../platform/service_worker/Worker.ts | 2 +- .../service_worker/WorkerAppShell.test.ts | 23 +- .../platform/service_worker/WorkerAppShell.ts | 9 +- .../service_worker/WorkerFetchRouting.test.ts | 9 + .../service_worker/WorkerNavigation.ts | 4 +- .../src/features/platform/types/Electron.ts | 1 + .../features/platform/utils/WorkerAssetUrl.ts | 7 +- .../src/features/theme/utils/ThemeUtils.ts | 2 + .../trusted_domain/state/TrustedDomain.ts | 2 + fluxer_app/src/features/ui/state/Nagbar.ts | 6 + fluxer_app/src/index.tsx | 4 + fluxer_app/src/types/web-install.d.ts | 11 + fluxer_app_proxy/src/bootstrap.rs | 65 ++ fluxer_app_proxy/src/config.rs | 152 ++++ fluxer_app_proxy/src/routes/spa_index.rs | 134 ++- fluxer_app_proxy/src/routes/spa_static.rs | 127 ++- fluxer_desktop/src/common/Constants.ts | 3 + fluxer_desktop/src/common/DesktopConfig.ts | 55 +- fluxer_desktop/src/common/Types.ts | 6 + .../src/main/DomainMigration.test.mjs | 183 ++++ fluxer_desktop/src/main/DomainMigration.ts | 43 + fluxer_desktop/src/main/IpcHandlers.ts | 2 + fluxer_desktop/src/main/Window.ts | 34 +- fluxer_desktop/src/preload/index.ts | 4 + fluxer_docs/scripts/VerifyDocsStyle.ts | 4 +- .../src/content/docs/admin-api/instance.mdx | 35 +- .../content/docs/http-api/authentication.mdx | 103 ++- .../src/content/docs/http-api/errors.md | 8 + .../src/content/docs/http-api/experiments.mdx | 17 +- .../src/content/docs/http-api/instance.mdx | 22 +- .../src/content/docs/http-api/webhooks.mdx | 18 +- .../content/docs/operator/configuration.mdx | 14 +- packages/config/src/ConfigLoader.ts | 19 + packages/config/src/MasterConfig.ts | 1 + .../config/src/__tests__/ConfigLoader.test.ts | 29 + .../src/config_loader/EnvironmentOverrides.ts | 1 + packages/constants/src/ApiErrorCodes.ts | 2 + .../auth/InvalidOriginHandoffNonceError.ts | 10 + .../domains/auth/UnknownOriginHandoffError.ts | 10 + packages/errors/src/i18n/ErrorCodeMappings.ts | 2 + packages/errors/src/i18n/ErrorI18nMessages.ts | 2 + packages/errors/src/i18n/locales/ar.ts | 2 + packages/errors/src/i18n/locales/bg.ts | 2 + packages/errors/src/i18n/locales/cs.ts | 2 + packages/errors/src/i18n/locales/da.ts | 2 + packages/errors/src/i18n/locales/de.ts | 2 + packages/errors/src/i18n/locales/el.ts | 2 + packages/errors/src/i18n/locales/en-GB.ts | 2 + packages/errors/src/i18n/locales/es-419.ts | 2 + packages/errors/src/i18n/locales/es-ES.ts | 2 + packages/errors/src/i18n/locales/fi.ts | 2 + packages/errors/src/i18n/locales/fr.ts | 2 + packages/errors/src/i18n/locales/he.ts | 2 + packages/errors/src/i18n/locales/hi.ts | 2 + packages/errors/src/i18n/locales/hr.ts | 2 + packages/errors/src/i18n/locales/hu.ts | 2 + packages/errors/src/i18n/locales/id.ts | 2 + packages/errors/src/i18n/locales/it.ts | 2 + packages/errors/src/i18n/locales/ja.ts | 2 + packages/errors/src/i18n/locales/ko.ts | 2 + packages/errors/src/i18n/locales/lt.ts | 2 + packages/errors/src/i18n/locales/nl.ts | 2 + packages/errors/src/i18n/locales/no.ts | 2 + packages/errors/src/i18n/locales/pl.ts | 2 + packages/errors/src/i18n/locales/pt-BR.ts | 2 + packages/errors/src/i18n/locales/ro.ts | 2 + packages/errors/src/i18n/locales/ru.ts | 2 + packages/errors/src/i18n/locales/sv-SE.ts | 2 + packages/errors/src/i18n/locales/th.ts | 2 + packages/errors/src/i18n/locales/tr.ts | 2 + packages/errors/src/i18n/locales/uk.ts | 2 + packages/errors/src/i18n/locales/vi.ts | 2 + packages/errors/src/i18n/locales/zh-CN.ts | 2 + packages/errors/src/i18n/locales/zh-TW.ts | 2 + .../errors/src/i18n/weblate/locales/ar.json | 2 + .../errors/src/i18n/weblate/locales/bg.json | 2 + .../errors/src/i18n/weblate/locales/cs.json | 2 + .../errors/src/i18n/weblate/locales/da.json | 2 + .../errors/src/i18n/weblate/locales/de.json | 2 + .../errors/src/i18n/weblate/locales/el.json | 2 + .../src/i18n/weblate/locales/en-GB.json | 2 + .../src/i18n/weblate/locales/es-419.json | 2 + .../src/i18n/weblate/locales/es-ES.json | 2 + .../errors/src/i18n/weblate/locales/fi.json | 2 + .../errors/src/i18n/weblate/locales/fr.json | 2 + .../errors/src/i18n/weblate/locales/he.json | 2 + .../errors/src/i18n/weblate/locales/hi.json | 2 + .../errors/src/i18n/weblate/locales/hr.json | 2 + .../errors/src/i18n/weblate/locales/hu.json | 2 + .../errors/src/i18n/weblate/locales/id.json | 2 + .../errors/src/i18n/weblate/locales/it.json | 2 + .../errors/src/i18n/weblate/locales/ja.json | 2 + .../errors/src/i18n/weblate/locales/ko.json | 2 + .../errors/src/i18n/weblate/locales/lt.json | 2 + .../errors/src/i18n/weblate/locales/nl.json | 2 + .../errors/src/i18n/weblate/locales/no.json | 2 + .../errors/src/i18n/weblate/locales/pl.json | 2 + .../src/i18n/weblate/locales/pt-BR.json | 2 + .../errors/src/i18n/weblate/locales/ro.json | 2 + .../errors/src/i18n/weblate/locales/ru.json | 2 + .../src/i18n/weblate/locales/sv-SE.json | 2 + .../errors/src/i18n/weblate/locales/th.json | 2 + .../errors/src/i18n/weblate/locales/tr.json | 2 + .../errors/src/i18n/weblate/locales/uk.json | 2 + .../errors/src/i18n/weblate/locales/vi.json | 2 + .../src/i18n/weblate/locales/zh-CN.json | 2 + .../src/i18n/weblate/locales/zh-TW.json | 2 + .../errors/src/i18n/weblate/messages.json | 2 + .../schema/src/domains/admin/AdminSchemas.ts | 6 + .../admin/DomainMigrationSchemas.test.ts | 234 +++++ .../domains/admin/DomainMigrationSchemas.ts | 94 ++ .../src/domains/auth/OriginHandoffSchemas.ts | 49 ++ .../domains/experiment/ExperimentSchemas.ts | 11 + .../src/domains/instance/InstanceSchemas.ts | 4 + .../src/domains/user/UserRequestSchemas.ts | 2 + 223 files changed, 13412 insertions(+), 2669 deletions(-) create mode 100644 fluxer_api/src/api/auth/OriginHandoffController.ts create mode 100644 fluxer_api/src/api/auth/services/OriginHandoffService.ts create mode 100644 fluxer_api/src/api/auth/tests/OriginHandoff.test.ts create mode 100644 fluxer_api/src/api/user/services/WebPushOriginReplacement.ts create mode 100644 fluxer_api/src/api/user/tests/PushOriginReplacement.test.ts create mode 100644 fluxer_app/src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMigration.test.ts create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMigrationBrowser.ts create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMigrationCore.ts create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMigrationCrypto.ts create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMigrationPreMount.ts create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMigrationRollout.ts create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMigrationTrigger.ts create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMovedNotice.ts create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMovedStepsModal.module.css create mode 100644 fluxer_app/src/features/app/domain_migration/DomainMovedStepsModal.tsx create mode 100644 fluxer_app/src/features/auth/utils/OldAppSignIn.test.ts create mode 100644 fluxer_app/src/features/auth/utils/OldAppSignIn.ts create mode 100644 fluxer_app/src/types/web-install.d.ts create mode 100644 fluxer_desktop/src/main/DomainMigration.test.mjs create mode 100644 fluxer_desktop/src/main/DomainMigration.ts create mode 100644 packages/errors/src/domains/auth/InvalidOriginHandoffNonceError.ts create mode 100644 packages/errors/src/domains/auth/UnknownOriginHandoffError.ts create mode 100644 packages/schema/src/domains/admin/DomainMigrationSchemas.test.ts create mode 100644 packages/schema/src/domains/admin/DomainMigrationSchemas.ts create mode 100644 packages/schema/src/domains/auth/OriginHandoffSchemas.ts diff --git a/fluxer_admin/openapi-admin.json b/fluxer_admin/openapi-admin.json index 46917a831..a4312b6a4 100644 --- a/fluxer_admin/openapi-admin.json +++ b/fluxer_admin/openapi-admin.json @@ -10525,6 +10525,7 @@ "gateway_rollout": {"$ref": "#/components/schemas/GatewayRolloutConfigResponse"}, "voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionConfigResponse"}, "push_service_delivery": {"$ref": "#/components/schemas/PushServiceDeliveryConfigResponse"}, + "domain_migration": {"$ref": "#/components/schemas/DomainMigrationConfigResponse"}, "experiment_delivery": {"$ref": "#/components/schemas/ExperimentDeliveryConfigResponse"}, "registration": { "type": "object", @@ -10953,6 +10954,7 @@ "gateway_rollout", "voice_noise_suppression", "push_service_delivery", + "domain_migration", "experiment_delivery", "registration", "self_hosted", @@ -11091,6 +11093,10 @@ "nullable": true, "allOf": [{"$ref": "#/components/schemas/PushServiceDeliveryConfigUpdateRequest"}] }, + "domain_migration": { + "nullable": true, + "allOf": [{"$ref": "#/components/schemas/DomainMigrationConfigUpdateRequest"}] + }, "experiment_delivery": { "nullable": true, "allOf": [{"$ref": "#/components/schemas/ExperimentDeliveryConfigUpdateRequest"}] @@ -15184,6 +15190,26 @@ "poll_jitter_percent": {"type": "integer", "minimum": 0, "maximum": 50} } }, + "DomainMigrationConfigUpdateRequest": { + "type": "object", + "properties": { + "enabled": {"type": "boolean"}, + "rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, + "rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[\\x20-\\x7e]+$"}, + "included_user_ids": { + "maxItems": 1000, + "type": "array", + "items": {"type": "string", "pattern": "^\\d{1,20}$"} + }, + "excluded_user_ids": { + "maxItems": 1000, + "type": "array", + "items": {"type": "string", "pattern": "^\\d{1,20}$"} + }, + "anonymous_rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, + "standalone_forwarding": {"type": "boolean"} + } + }, "PushServiceDeliveryConfigUpdateRequest": { "type": "object", "properties": { @@ -15267,6 +15293,46 @@ "required": ["poll_interval_seconds", "poll_jitter_percent"], "additionalProperties": false }, + "DomainMigrationConfigResponse": { + "type": "object", + "properties": { + "enabled": {"default": false, "type": "boolean"}, + "config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991}, + "rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, + "rollout_salt": { + "default": "domain-migration-v1", + "type": "string", + "minLength": 1, + "maxLength": 64, + "pattern": "^[\\x20-\\x7e]+$" + }, + "included_user_ids": { + "default": [], + "maxItems": 1000, + "type": "array", + "items": {"type": "string", "pattern": "^\\d{1,20}$"} + }, + "excluded_user_ids": { + "default": [], + "maxItems": 1000, + "type": "array", + "items": {"type": "string", "pattern": "^\\d{1,20}$"} + }, + "anonymous_rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, + "standalone_forwarding": {"default": false, "type": "boolean"} + }, + "required": [ + "enabled", + "config_version", + "rollout_basis_points", + "rollout_salt", + "included_user_ids", + "excluded_user_ids", + "anonymous_rollout_basis_points", + "standalone_forwarding" + ], + "additionalProperties": false + }, "PushServiceDeliveryConfigResponse": { "type": "object", "properties": { diff --git a/fluxer_admin/src/api/types/instance_config.rs b/fluxer_admin/src/api/types/instance_config.rs index adede16a7..f7da80b66 100644 --- a/fluxer_admin/src/api/types/instance_config.rs +++ b/fluxer_admin/src/api/types/instance_config.rs @@ -25,6 +25,8 @@ pub struct InstanceConfigResponse { #[serde(default)] pub push_service_delivery: PushServiceDeliveryConfigResponse, #[serde(default)] + pub domain_migration: DomainMigrationConfigResponse, + #[serde(default)] pub experiment_delivery: ExperimentDeliveryConfigResponse, } @@ -450,6 +452,7 @@ impl VoiceE2eeScope { pub const EXPERIMENT_MAX_TARGETED_USERS: usize = 1_000; pub const PUSH_SERVICE_DELIVERY_DEFAULT_SALT: &str = "push-service-delivery-v1"; +pub const DOMAIN_MIGRATION_DEFAULT_SALT: &str = "domain-migration-v1"; pub const VOICE_NS_MAX_GUILD_OVERRIDES: usize = 200; impl NoiseSuppressionBackend { @@ -578,6 +581,52 @@ pub struct PushServiceDeliveryConfigUpdateRequest { pub excluded_user_ids: Option>, } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(default)] +pub struct DomainMigrationConfigResponse { + pub enabled: bool, + pub config_version: u64, + pub rollout_basis_points: u32, + pub rollout_salt: String, + pub included_user_ids: Vec, + pub excluded_user_ids: Vec, + pub anonymous_rollout_basis_points: u32, + pub standalone_forwarding: bool, +} + +impl Default for DomainMigrationConfigResponse { + fn default() -> Self { + Self { + enabled: false, + config_version: 0, + rollout_basis_points: 0, + rollout_salt: DOMAIN_MIGRATION_DEFAULT_SALT.to_owned(), + included_user_ids: Vec::new(), + excluded_user_ids: Vec::new(), + anonymous_rollout_basis_points: 0, + standalone_forwarding: false, + } + } +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct DomainMigrationConfigUpdateRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rollout_basis_points: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rollout_salt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub included_user_ids: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub excluded_user_ids: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub anonymous_rollout_basis_points: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub standalone_forwarding: Option, +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(default)] pub struct ExperimentDeliveryConfigResponse { @@ -696,6 +745,8 @@ pub struct InstanceConfigUpdateRequest { #[serde(skip_serializing_if = "Option::is_none")] pub push_service_delivery: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub domain_migration: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub experiment_delivery: Option, } @@ -1033,18 +1084,30 @@ mod tests { .expect("admin schema"); let noise = serde_json::from_value::(json!({})) .expect("default noise config"); + let domain_migration = serde_json::from_value::(json!({})) + .expect("default domain migration config"); let delivery = serde_json::from_value::(json!({})) .expect("default delivery config"); let noise = serde_json::to_value(noise).expect("serializable noise config"); + let domain_migration = + serde_json::to_value(domain_migration).expect("serializable domain migration config"); let delivery = serde_json::to_value(delivery).expect("serializable delivery config"); let generated_noise: generated_types::VoiceNoiseSuppressionConfigResponse = serde_json::from_value(noise.clone()).expect("generated noise config contract"); + let generated_domain_migration: generated_types::DomainMigrationConfigResponse = + serde_json::from_value(domain_migration.clone()) + .expect("generated domain migration config contract"); let generated_delivery: generated_types::ExperimentDeliveryConfigResponse = serde_json::from_value(delivery.clone()).expect("generated delivery config contract"); assert_eq!( serde_json::to_value(generated_noise).expect("serializable generated noise config"), noise ); + assert_eq!( + serde_json::to_value(generated_domain_migration) + .expect("serializable generated domain migration config"), + domain_migration + ); assert_eq!( serde_json::to_value(generated_delivery) .expect("serializable generated delivery config"), @@ -1052,6 +1115,7 @@ mod tests { ); for (name, value) in [ ("VoiceNoiseSuppressionConfigResponse", noise), + ("DomainMigrationConfigResponse", domain_migration), ("ExperimentDeliveryConfigResponse", delivery), ] { for (field, value) in value.as_object().expect("config object") { @@ -1087,4 +1151,27 @@ mod tests { json!({}) ); } + + #[test] + fn domain_migration_update_preserves_empty_lists_and_omitted_fields() { + let update = DomainMigrationConfigUpdateRequest { + included_user_ids: Some(Vec::new()), + excluded_user_ids: Some(Vec::new()), + ..Default::default() + }; + let value = serde_json::to_value(update).expect("serializable update"); + serde_json::from_value::( + value.clone(), + ) + .expect("generated update contract"); + assert_eq!( + value, + json!({"included_user_ids": [], "excluded_user_ids": []}) + ); + assert_eq!( + serde_json::to_value(DomainMigrationConfigUpdateRequest::default()) + .expect("serializable update"), + json!({}) + ); + } } diff --git a/fluxer_admin/src/routes/system_actions.rs b/fluxer_admin/src/routes/system_actions.rs index bd8d4b4ab..1270dcab6 100644 --- a/fluxer_admin/src/routes/system_actions.rs +++ b/fluxer_admin/src/routes/system_actions.rs @@ -7,20 +7,21 @@ use crate::{ AppBrandingConfigUpdateRequest, AppLegalConfigUpdateRequest, AppPublicConfigUpdateRequest, AppRegistrationConfigUpdateRequest, AppSetupConfigUpdateRequest, CreateRegistrationUrlRequest, - DeferredPhoneGateUpdateRequest, EXPERIMENT_MAX_TARGETED_USERS, - ExperimentDeliveryConfigUpdateRequest, GatewayRolloutConfigUpdateRequest, - GatewayRolloutMode, InstanceAttachmentDecayUpdateRequest, - InstanceBlueskyIntegrationUpdateRequest, InstanceBlueskyKeyIntegrationUpdateRequest, - InstanceCaptchaIntegrationUpdateRequest, InstanceConfigUpdateRequest, - InstanceEmailIntegrationUpdateRequest, InstanceEmailSmtpIntegrationUpdateRequest, - InstanceEmailSmtpTestRequest, InstanceGifIntegrationUpdateRequest, - InstanceIntegrationsUpdateRequest, InstanceMediaUpdateRequest, - InstancePolicyUpdateRequest, InstanceRegistrationConfigUpdateRequest, - InstanceServicesUpdateRequest, InstanceYoutubeIntegrationUpdateRequest, - LimitConfigUpdateRequest, LimitRule, LimitRuleFilters, NoiseSuppressionBackend, - PremiumMode, PushServiceDeliveryConfigUpdateRequest, RegistrationMode, - SsoConfigUpdateRequest, VOICE_NS_MAX_GUILD_OVERRIDES, VoiceE2eeScope, - VoiceNoiseSuppressionConfigUpdateRequest, VoiceNoiseSuppressionGuildOverride, + DeferredPhoneGateUpdateRequest, DomainMigrationConfigUpdateRequest, + EXPERIMENT_MAX_TARGETED_USERS, ExperimentDeliveryConfigUpdateRequest, + GatewayRolloutConfigUpdateRequest, GatewayRolloutMode, + InstanceAttachmentDecayUpdateRequest, InstanceBlueskyIntegrationUpdateRequest, + InstanceBlueskyKeyIntegrationUpdateRequest, InstanceCaptchaIntegrationUpdateRequest, + InstanceConfigUpdateRequest, InstanceEmailIntegrationUpdateRequest, + InstanceEmailSmtpIntegrationUpdateRequest, InstanceEmailSmtpTestRequest, + InstanceGifIntegrationUpdateRequest, InstanceIntegrationsUpdateRequest, + InstanceMediaUpdateRequest, InstancePolicyUpdateRequest, + InstanceRegistrationConfigUpdateRequest, InstanceServicesUpdateRequest, + InstanceYoutubeIntegrationUpdateRequest, LimitConfigUpdateRequest, LimitRule, + LimitRuleFilters, NoiseSuppressionBackend, PremiumMode, + PushServiceDeliveryConfigUpdateRequest, RegistrationMode, SsoConfigUpdateRequest, + VOICE_NS_MAX_GUILD_OVERRIDES, VoiceE2eeScope, VoiceNoiseSuppressionConfigUpdateRequest, + VoiceNoiseSuppressionGuildOverride, }, }, config::AdminConfig, @@ -211,6 +212,10 @@ pub async fn instance_config_post( Ok(update) => instance_config_result(client.update_instance_config(&update).await), Err(message) => FlashData::error(message), }, + "update_domain_migration" => match build_domain_migration_update(&form) { + Ok(update) => instance_config_result(client.update_instance_config(&update).await), + Err(message) => FlashData::error(message), + }, "update_experiment_delivery" => match build_experiment_delivery_update(&form) { Ok(update) => instance_config_result(client.update_instance_config(&update).await), Err(message) => FlashData::error(message), @@ -496,7 +501,7 @@ fn parse_experiment_rollout_salt( Ok(Some(salt.to_owned())) } -fn parse_push_service_delivery_rollout_salt( +fn parse_ascii_experiment_rollout_salt( form: &MultiValueForm, key: &str, ) -> Result, String> { @@ -661,7 +666,7 @@ fn build_push_service_delivery_update( 0, EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, )?, - rollout_salt: parse_push_service_delivery_rollout_salt( + rollout_salt: parse_ascii_experiment_rollout_salt( form, "push_service_delivery_rollout_salt", )?, @@ -680,6 +685,46 @@ fn build_push_service_delivery_update( }) } +fn build_domain_migration_update( + form: &MultiValueForm, +) -> Result { + Ok(InstanceConfigUpdateRequest { + domain_migration: Some(DomainMigrationConfigUpdateRequest { + enabled: Some(form.bool_value("domain_migration_enabled")), + rollout_basis_points: parse_form_number( + form, + "domain_migration_rollout_basis_points", + "Rollout basis points", + 0, + EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, + )?, + rollout_salt: parse_ascii_experiment_rollout_salt( + form, + "domain_migration_rollout_salt", + )?, + included_user_ids: Some(parse_experiment_user_ids( + form.first("domain_migration_included_user_ids") + .unwrap_or_default(), + "Included user IDs", + )?), + excluded_user_ids: Some(parse_experiment_user_ids( + form.first("domain_migration_excluded_user_ids") + .unwrap_or_default(), + "Excluded user IDs", + )?), + anonymous_rollout_basis_points: parse_form_number( + form, + "domain_migration_anonymous_rollout_basis_points", + "Anonymous rollout basis points", + 0, + EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, + )?, + standalone_forwarding: Some(form.bool_value("domain_migration_standalone_forwarding")), + }), + ..Default::default() + }) +} + fn build_experiment_delivery_update( form: &MultiValueForm, ) -> Result { @@ -1601,6 +1646,91 @@ mod tests { } } + #[test] + fn build_domain_migration_update_reads_the_rollout_fields() { + let form = MultiValueForm::parse( + b"domain_migration_enabled=true&domain_migration_rollout_basis_points=%20250%20&domain_migration_rollout_salt=%20domain-migration-v2%20&domain_migration_included_user_ids=1500000000000000001%0A1500000000000000002&domain_migration_excluded_user_ids=1500000000000000003%2C%201500000000000000004&domain_migration_anonymous_rollout_basis_points=%20100%20&domain_migration_standalone_forwarding=true", + ); + let update = build_domain_migration_update(&form) + .expect("valid form") + .domain_migration + .expect("domain migration update"); + assert_eq!(update.enabled, Some(true)); + assert_eq!(update.rollout_basis_points, Some(250)); + assert_eq!(update.rollout_salt, Some("domain-migration-v2".to_owned())); + assert_eq!( + update.included_user_ids, + Some(vec![ + "1500000000000000001".to_owned(), + "1500000000000000002".to_owned() + ]) + ); + assert_eq!( + update.excluded_user_ids, + Some(vec![ + "1500000000000000003".to_owned(), + "1500000000000000004".to_owned() + ]) + ); + assert_eq!(update.anonymous_rollout_basis_points, Some(100)); + assert_eq!(update.standalone_forwarding, Some(true)); + } + + #[test] + fn build_domain_migration_update_leaves_the_feature_inert_when_nothing_is_submitted() { + let form = MultiValueForm::parse(b"_csrf=token"); + let request = build_domain_migration_update(&form).expect("valid form"); + assert_eq!( + serde_json::to_value(request).expect("serializable update"), + serde_json::json!({"domain_migration": { + "enabled": false, + "included_user_ids": [], + "excluded_user_ids": [], + "standalone_forwarding": false, + }}) + ); + } + + #[test] + fn build_domain_migration_update_rejects_invalid_rollout_fields() { + for (form, message) in [ + ( + "domain_migration_rollout_basis_points=10001", + "Rollout basis points must be a whole number between 0 and 10000", + ), + ( + "domain_migration_anonymous_rollout_basis_points=10001", + "Anonymous rollout basis points must be a whole number between 0 and 10000", + ), + ( + "domain_migration_anonymous_rollout_basis_points=abc", + "Anonymous rollout basis points must be a whole number between 0 and 10000", + ), + ( + "domain_migration_rollout_salt=%20%20", + "Rollout salt must be between 1 and 64 characters", + ), + ( + "domain_migration_rollout_salt=caf%C3%A9", + "Rollout salt must use printable ASCII", + ), + ( + "domain_migration_included_user_ids=123%2Cinvalid", + "Included user IDs entry 2 must contain 1 to 20 decimal digits", + ), + ( + "domain_migration_excluded_user_ids=123%2Cinvalid", + "Excluded user IDs entry 2 must contain 1 to 20 decimal digits", + ), + ] { + let form = MultiValueForm::parse(form.as_bytes()); + assert_eq!( + build_domain_migration_update(&form).expect_err("invalid rollout field"), + message + ); + } + } + #[test] fn build_experiment_delivery_update_leaves_both_fields_unchanged_when_absent() { let form = MultiValueForm::parse(b"_csrf=token"); diff --git a/fluxer_admin/src/templates/pages/instance_config.rs b/fluxer_admin/src/templates/pages/instance_config.rs index dbaa2bd13..c182d414c 100644 --- a/fluxer_admin/src/templates/pages/instance_config.rs +++ b/fluxer_admin/src/templates/pages/instance_config.rs @@ -2,7 +2,8 @@ use crate::{ api::types::{ - AppPublicConfigResponse, EXPERIMENT_MAX_TARGETED_USERS, ExperimentDeliveryConfigResponse, + AppPublicConfigResponse, DOMAIN_MIGRATION_DEFAULT_SALT, DomainMigrationConfigResponse, + EXPERIMENT_MAX_TARGETED_USERS, ExperimentDeliveryConfigResponse, GatewayRolloutConfigResponse, InstanceConfigResponse, InstanceIntegrationsResponse, InstanceMediaResponse, InstancePolicyResponse, InstanceRegistrationResponse, LimitConfigResponse, NoiseSuppressionBackend, PUSH_SERVICE_DELIVERY_DEFAULT_SALT, @@ -149,6 +150,7 @@ pub fn instance_config_page( (gateway_rollout_section(base, csrf_token, &instance_config.gateway_rollout)) (voice_noise_suppression_section(base, csrf_token, &instance_config.voice_noise_suppression)) (push_service_delivery_section(base, csrf_token, &instance_config.push_service_delivery)) + (domain_migration_section(base, csrf_token, &instance_config.domain_migration)) (experiment_delivery_section(base, csrf_token, &instance_config.experiment_delivery)) @if let Some(limit_config) = limit_config { (limit_config_section(base, limit_config)) @@ -1286,6 +1288,139 @@ fn push_service_delivery_section( ) } +fn domain_migration_section( + base: &str, + csrf_token: &str, + domain_migration: &DomainMigrationConfigResponse, +) -> Markup { + let status = if domain_migration.enabled { + ("Live", BadgeVariant::Success) + } else { + ("Inert", BadgeVariant::Default) + }; + let included_user_ids = domain_migration.included_user_ids.join("\n"); + let excluded_user_ids = domain_migration.excluded_user_ids.join("\n"); + section_card_with_description( + "Domain Migration", + "Moves web clients of the official instance from the legacy web app origin to the new \ + one. Selected accounts copy their local data across and continue on the new origin. \ + Clients of other instances read this configuration and ignore it.", + html! { + form method="post" action={(base) "/instance-config?action=update_domain_migration"} { + (csrf_input(csrf_token)) + div class="space-y-6" { + div class="flex flex-wrap items-center gap-2" { + h3 class="text-sm font-semibold text-neutral-900" { "Master switch" } + (badge(status.0, status.1)) + span class="text-xs text-neutral-500" { + "Config version " (domain_migration.config_version) + } + } + (checkbox( + "domain_migration_enabled", + "true", + "Move selected web clients to the new origin", + domain_migration.enabled, + true, + )) + p class="text-xs text-neutral-500" { + "Off is the safe state and the kill switch. With this unchecked no client \ + starts a migration and clients that already migrated stop forwarding the \ + legacy origin, so the rollout and targeting fields below have no effect at all." + } + + h3 class="text-sm font-semibold text-neutral-900" { "Installed apps" } + (checkbox( + "domain_migration_standalone_forwarding", + "true", + "Forward installed desktop web apps to the new origin", + domain_migration.standalone_forwarding, + true, + )) + p class="text-xs text-neutral-500" { + "Leave this off until the manifest scope extension and the association file \ + are live and verified. While it is off, installed Chromium desktop apps copy \ + their data across but stay on the legacy origin and offer to install the new \ + app. Installed mobile and Safari apps never forward either way." + } + + h3 class="text-sm font-semibold text-neutral-900" { "Rollout" } + (number_field( + "domain_migration_rollout_basis_points", + "Rollout (basis points)", + &domain_migration.rollout_basis_points.to_string(), + Some(0), Some(10000), "1", + Some("Share of logged-in users bucketed into the migration, in basis points: 0 is nobody, 100 is 1%, 10000 is everybody."), + )) + (number_field( + "domain_migration_anonymous_rollout_basis_points", + "Anonymous rollout (basis points)", + &domain_migration.anonymous_rollout_basis_points.to_string(), + Some(0), Some(10000), "1", + Some("Share of logged-out devices sent to the new origin, in basis points. Each device is bucketed on its own random ID."), + )) + div class="flex flex-col gap-2" { + (text_input( + "domain_migration_rollout_salt", + "Rollout Salt", + &domain_migration.rollout_salt, + DOMAIN_MIGRATION_DEFAULT_SALT, + )) + p class="text-xs text-neutral-500" { + "Seeds the bucketing hash for users and devices. Changing it reshuffles \ + which users and devices fall inside the percentages above. Leave it \ + alone to keep the current cohort stable." + } + } + div class="flex flex-col gap-2" { + (textarea_input( + "domain_migration_included_user_ids", + "Always-on User IDs", + "1500000000000000001\n1500000000000000002", + &included_user_ids, + 4, + false, + )) + (entry_count_hint( + domain_migration.included_user_ids.len(), + EXPERIMENT_MAX_TARGETED_USERS, + )) + p class="text-xs text-neutral-500" { + "One snowflake per line, or comma separated. These users are targeted \ + regardless of the percentage above. IDs must contain 1 to 20 decimal \ + digits. Invalid entries prevent the save. Blank entries and duplicate \ + IDs are ignored." + } + } + div class="flex flex-col gap-2" { + (textarea_input( + "domain_migration_excluded_user_ids", + "Never-on User IDs", + "1500000000000000003\n1500000000000000004", + &excluded_user_ids, + 4, + false, + )) + (entry_count_hint( + domain_migration.excluded_user_ids.len(), + EXPERIMENT_MAX_TARGETED_USERS, + )) + p class="text-xs text-neutral-500" { + "Same format. Exclusion wins over both the always-on list and the \ + percentage. It stops new migrations only. A user who already moved \ + stays on the new origin." + } + } + + (form_actions(html! { + (submit_button("Save Domain Migration Configuration")) + })) + } + } + }, + ) +} + fn experiment_delivery_section( base: &str, csrf_token: &str, @@ -1929,6 +2064,28 @@ mod tests { assert!(!markup.contains("at the cap")); } + #[test] + fn domain_migration_section_shows_both_rollouts_and_list_counts() { + let domain_migration = DomainMigrationConfigResponse { + anonymous_rollout_basis_points: 250, + included_user_ids: vec!["1500000000000000001".to_owned()], + excluded_user_ids: vec![ + "1500000000000000002".to_owned(), + "1500000000000000003".to_owned(), + ], + ..DomainMigrationConfigResponse::default() + }; + let markup = domain_migration_section("/admin", "csrf", &domain_migration).into_string(); + assert!(markup.contains("action=update_domain_migration")); + assert!(markup.contains("domain_migration_enabled")); + assert!(markup.contains("name=\"domain_migration_anonymous_rollout_basis_points\"")); + assert!(markup.contains("value=\"250\"")); + assert!(markup.contains("name=\"domain_migration_standalone_forwarding\"")); + assert!(markup.contains("1 of 1000 stored")); + assert!(markup.contains("2 of 1000 stored")); + assert!(!markup.contains("at the cap")); + } + #[test] fn voice_noise_suppression_section_flags_a_list_at_its_cap() { let voice_noise_suppression = VoiceNoiseSuppressionConfigResponse { diff --git a/fluxer_admin/tests/api_deserialization.rs b/fluxer_admin/tests/api_deserialization.rs index edbe74395..ed90b36da 100644 --- a/fluxer_admin/tests/api_deserialization.rs +++ b/fluxer_admin/tests/api_deserialization.rs @@ -417,6 +417,17 @@ fn deserialize_instance_config_response_with_unknown_keys() { "included_user_ids": ["1500000000000000002"], "excluded_user_ids": [] }, + "domain_migration": { + "enabled": true, + "config_version": 2, + "rollout_basis_points": 2500, + "rollout_salt": "domain-migration-v1", + "included_user_ids": ["1500000000000000001"], + "excluded_user_ids": [], + "future_migration_knob": 9, + "anonymous_rollout_basis_points": 100, + "standalone_forwarding": true + }, "experiment_delivery": {"poll_interval_seconds": 300, "poll_jitter_percent": 15}, "registration": { "mode": "open", @@ -546,6 +557,13 @@ fn deserialize_instance_config_response_with_unknown_keys() { assert_eq!(resp.voice_noise_suppression.rollout_basis_points, 10000); assert_eq!(*resp.voice_noise_suppression.rollout_salt, "voice-ns-v1"); assert_eq!(resp.voice_noise_suppression.enabled_backends.len(), 3); + assert!(resp.domain_migration.enabled); + assert_eq!(resp.domain_migration.config_version, 2); + assert_eq!(resp.domain_migration.rollout_basis_points, 2500); + assert_eq!(*resp.domain_migration.rollout_salt, "domain-migration-v1"); + assert_eq!(resp.domain_migration.included_user_ids.len(), 1); + assert_eq!(resp.domain_migration.anonymous_rollout_basis_points, 100); + assert!(resp.domain_migration.standalone_forwarding); assert_eq!(resp.experiment_delivery.poll_interval_seconds, 300); assert!(resp.policy.single_community_guild_id.is_none()); assert_eq!(resp.policy.services.gif_enabled, Some(true)); @@ -556,6 +574,7 @@ fn deserialize_instance_config_response_with_unknown_keys() { .replace("\"future_rollout_knob\": 3,", "") .replace("\"future_presentation_knob\": \"verbose\",", "") .replace("\"future_knob\": 7,", "") + .replace("\"future_migration_knob\": 9,", "") .replace("\"future_object_knob\": {\"nested\": true},", "") .replace("\"future_list_knob\": [\"a\", \"b\"],", "") .replace( diff --git a/fluxer_admin/tests/htmx_acceptance.rs b/fluxer_admin/tests/htmx_acceptance.rs index 113073e21..96b59e936 100644 --- a/fluxer_admin/tests/htmx_acceptance.rs +++ b/fluxer_admin/tests/htmx_acceptance.rs @@ -465,6 +465,7 @@ async fn mutating_admin_pages_render_usable_csrf_tokens() { "/instance-config?action=update_gateway_rollout", "/instance-config?action=update_sso", "/instance-config?action=update_voice_noise_suppression", + "/instance-config?action=update_domain_migration", "/instance-config?action=update_experiment_delivery", ][..], ), @@ -1199,6 +1200,16 @@ fn instance_config() -> Value { "guild_overrides": [], "suppression_strength": 80 }, + "domain_migration": { + "enabled": false, + "config_version": 0, + "rollout_basis_points": 0, + "rollout_salt": "domain-migration-v1", + "included_user_ids": [], + "excluded_user_ids": [], + "anonymous_rollout_basis_points": 0, + "standalone_forwarding": false + }, "experiment_delivery": { "poll_interval_seconds": 300, "poll_jitter_percent": 15 diff --git a/fluxer_api/src/api/App.ts b/fluxer_api/src/api/App.ts index a35bef59e..4b8bffb69 100644 --- a/fluxer_api/src/api/App.ts +++ b/fluxer_api/src/api/App.ts @@ -45,7 +45,7 @@ export async function createAPIApp(options: CreateAPIAppOptions): Promise { gatewayRollout, voiceNoiseSuppression, pushServiceDelivery, + domainMigration, experimentDelivery, registrationConfig, registrationUrls, @@ -74,6 +76,7 @@ async function buildInstanceConfigResponse(): Promise { instanceConfigRepository.getGatewayRolloutConfig(), instanceConfigRepository.getVoiceNoiseSuppressionConfig(), instanceConfigRepository.getPushServiceDeliveryConfig(), + instanceConfigRepository.getDomainMigrationConfig(), instanceConfigRepository.getExperimentDeliveryConfig(), instanceConfigRepository.getRegistrationConfig(), instanceConfigRepository.getRegistrationUrlsForAdmin(), @@ -106,6 +109,7 @@ async function buildInstanceConfigResponse(): Promise { gateway_rollout: gatewayRollout, voice_noise_suppression: voiceNoiseSuppression, push_service_delivery: pushServiceDelivery, + domain_migration: domainMigration, experiment_delivery: experimentDelivery, registration: { ...registrationConfig, @@ -282,6 +286,18 @@ export function InstanceConfigAdminController(app: HonoApp) { await getPushServiceDeliveryConfigPublisher().publish(landed); } } + if (data.domain_migration) { + const patch = omitUndefinedFields(data.domain_migration); + if (Object.keys(patch).length > 0) { + await instanceConfigRepository.updateDomainMigrationConfig((current) => + DomainMigrationConfigSchema.parse({ + ...current, + ...patch, + config_version: current.config_version + 1, + }), + ); + } + } if (data.experiment_delivery) { const patch = data.experiment_delivery; await instanceConfigRepository.updateExperimentDeliveryConfig((current) => diff --git a/fluxer_api/src/api/admin/tests/InstanceConfigAdminController.test.ts b/fluxer_api/src/api/admin/tests/InstanceConfigAdminController.test.ts index c4642b6aa..a035b6836 100644 --- a/fluxer_api/src/api/admin/tests/InstanceConfigAdminController.test.ts +++ b/fluxer_api/src/api/admin/tests/InstanceConfigAdminController.test.ts @@ -69,6 +69,20 @@ describe('instance config admin PATCH under concurrent writes', () => { return logs.filter((log) => log.action === 'update_instance_config'); } + it('merges a standalone forwarding patch into the stored domain migration config', async () => { + const admin = await createAdmin(); + await patchConfig(admin, {domain_migration: {enabled: true, rollout_basis_points: 250}}).execute(); + + const updated = await patchConfig(admin, {domain_migration: {standalone_forwarding: true}}).execute(); + + expect(updated.domain_migration).toMatchObject({ + enabled: true, + rollout_basis_points: 250, + standalone_forwarding: true, + config_version: 2, + }); + }); + it('answers with a conflict and neither writes, publishes nor audits once every attempt has lost the race', async () => { const publish = spyOnPushDeliveryPublishes(); const admin = await createAdmin(); diff --git a/fluxer_api/src/api/app/ControllerRegistry.ts b/fluxer_api/src/api/app/ControllerRegistry.ts index 51d466750..60c001a9e 100644 --- a/fluxer_api/src/api/app/ControllerRegistry.ts +++ b/fluxer_api/src/api/app/ControllerRegistry.ts @@ -3,6 +3,7 @@ import {registerAdminControllers} from '@app/api/admin/controllers/index'; import {AttachmentController} from '@app/api/attachment/AttachmentController'; import {AuthController} from '@app/api/auth/AuthController'; +import {OriginHandoffController} from '@app/api/auth/OriginHandoffController'; import {BlueskyOAuthController} from '@app/api/bluesky/BlueskyOAuthController'; import {Config} from '@app/api/Config'; import {ChannelController} from '@app/api/channel/ChannelController'; @@ -46,6 +47,7 @@ export function registerControllers(routes: HonoApp, config: APIConfig): void { GeolocationController(routes); registerAdminControllers(routes); AuthController(routes); + OriginHandoffController(routes); AttachmentController(routes); ChannelController(routes); ConnectionController(routes); diff --git a/fluxer_api/src/api/auth/AuthController.ts b/fluxer_api/src/api/auth/AuthController.ts index 9cffc24ba..23d25558c 100644 --- a/fluxer_api/src/api/auth/AuthController.ts +++ b/fluxer_api/src/api/auth/AuthController.ts @@ -602,6 +602,7 @@ export function AuthController(app: HonoApp) { data: ctx.req.valid('json'), clientIp, authToken: ctx.get('authToken') ?? undefined, + approverOrigin: ctx.req.header('origin'), }); return ctx.body(null, 204); }, diff --git a/fluxer_api/src/api/auth/AuthRequestService.ts b/fluxer_api/src/api/auth/AuthRequestService.ts index dbb717bb3..dc10b4001 100644 --- a/fluxer_api/src/api/auth/AuthRequestService.ts +++ b/fluxer_api/src/api/auth/AuthRequestService.ts @@ -8,12 +8,19 @@ import * as AuthMfa from '@app/api/auth/AuthMfa'; import * as AuthPassword from '@app/api/auth/AuthPassword'; import * as AuthRegistration from '@app/api/auth/AuthRegistration'; import * as AuthSession from '@app/api/auth/AuthSession'; +import {getTokenIdHash} from '@app/api/auth/AuthUtility'; import type {DesktopHandoffService} from '@app/api/auth/services/DesktopHandoffService'; import type {SsoService} from '@app/api/auth/services/SsoService'; import {createUserID, type UserID} from '@app/api/BrandedTypes'; +import {Logger} from '@app/api/Logger'; import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware'; import {getInstanceConfigRepository} from '@app/api/middleware/ServiceSingletons'; import type {User} from '@app/api/models/User'; +import { + classifyWebPushOrigin, + encodePushSessionIdHash, + recordPushSessionPredecessor, +} from '@app/api/user/services/WebPushOriginReplacement'; import {mapUserToPartialResponse} from '@app/api/user/UserMappers'; import {lookupGeoip} from '@app/api/utils/IpUtils'; import {parseJsonRecord} from '@app/api/utils/JsonBoundaryUtils'; @@ -91,6 +98,7 @@ interface AuthHandoffCompleteRequest { data: HandoffCompleteRequest; clientIp: string; authToken?: string; + approverOrigin?: string | null; } interface AuthAuthorizeIpRequest { @@ -305,7 +313,10 @@ export class AuthRequestService { async initiateHandoff({request}: AuthHandoffInitiateRequest): Promise { const origin = AuthSession.resolveSessionOrigin(this.apiContext, request); - const result = await this.desktopHandoffService.initiateHandoff({origin}); + const result = await this.desktopHandoffService.initiateHandoff({ + origin, + initiatorOrigin: request.headers.get('origin'), + }); return { code: result.code, expires_at: result.expiresAt.toISOString(), @@ -340,21 +351,53 @@ export class AuthRequestService { }; } - async completeHandoff({data, clientIp, authToken}: AuthHandoffCompleteRequest): Promise { + async completeHandoff({data, clientIp, authToken, approverOrigin}: AuthHandoffCompleteRequest): Promise { const sessionToken = data.token ?? authToken; if (!sessionToken) { throw new UnauthorizedError(); } - await this.desktopHandoffService.completeHandoff( + let createdToken: string | null = null; + const {initiatorOrigin} = await this.desktopHandoffService.completeHandoff( data.code, - (origin) => - AuthSession.createAdditionalAuthSessionFromToken(this.apiContext, { + async (origin) => { + const created = await AuthSession.createAdditionalAuthSessionFromToken(this.apiContext, { token: sessionToken, expectedUserId: data.user_id, origin, - }), + }); + createdToken = created.token; + return created; + }, clientIp, ); + if (createdToken !== null) { + await this.recordPushSessionPredecessor(createdToken, sessionToken, initiatorOrigin, approverOrigin); + } + } + + private async recordPushSessionPredecessor( + createdToken: string, + approverToken: string, + initiatorOrigin: string | null, + approverOrigin: string | null | undefined, + ): Promise { + const {config, kv} = this.apiContext.services; + const {selfHosted} = config.instance; + if ( + classifyWebPushOrigin(initiatorOrigin, selfHosted) !== 'target' || + classifyWebPushOrigin(approverOrigin, selfHosted) !== 'legacy' + ) { + return; + } + try { + await recordPushSessionPredecessor( + kv, + encodePushSessionIdHash(getTokenIdHash(this.apiContext, createdToken)), + encodePushSessionIdHash(getTokenIdHash(this.apiContext, approverToken)), + ); + } catch (error) { + Logger.warn({error}, 'Failed to record the push session predecessor'); + } } async getHandoffStatus({code, clientIp, pollSecret}: AuthHandoffStatusRequest): Promise { diff --git a/fluxer_api/src/api/auth/OriginHandoffController.ts b/fluxer_api/src/api/auth/OriginHandoffController.ts new file mode 100644 index 000000000..3199f24f3 --- /dev/null +++ b/fluxer_api/src/api/auth/OriginHandoffController.ts @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {createOriginHandoff, redeemOriginHandoff} from '@app/api/auth/services/OriginHandoffService'; +import {Config} from '@app/api/Config'; +import {DefaultUserOnly, LoginRequired} from '@app/api/middleware/AuthMiddleware'; +import {RateLimitMiddleware} from '@app/api/middleware/RateLimitMiddleware'; +import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware'; +import {RateLimitConfigs} from '@app/api/RateLimitConfig'; +import type {HonoApp} from '@app/api/types/HonoEnv'; +import {Validator} from '@app/api/Validator'; +import {FileSizeTooLargeError} from '@fluxer/errors/src/domains/core/FileSizeTooLargeError'; +import {InvalidApiOriginError} from '@fluxer/errors/src/domains/core/InvalidApiOriginError'; +import { + ORIGIN_HANDOFF_MAX_PAYLOAD_LENGTH, + OriginHandoffCreateRequest, + OriginHandoffCreateResponse, + OriginHandoffRedeemRequest, + OriginHandoffRedeemResponse, +} from '@fluxer/schema/src/domains/auth/OriginHandoffSchemas'; +import {bodyLimit} from 'hono/body-limit'; + +const ORIGIN_HANDOFF_CREATE_MAX_BODY_BYTES = ORIGIN_HANDOFF_MAX_PAYLOAD_LENGTH + 1024; + +export function OriginHandoffController(app: HonoApp) { + app.post( + '/auth/origin-handoff', + RateLimitMiddleware(RateLimitConfigs.AUTH_ORIGIN_HANDOFF_CREATE), + LoginRequired, + DefaultUserOnly, + bodyLimit({ + maxSize: ORIGIN_HANDOFF_CREATE_MAX_BODY_BYTES, + onError: () => { + throw new FileSizeTooLargeError(ORIGIN_HANDOFF_CREATE_MAX_BODY_BYTES); + }, + }), + Validator('json', OriginHandoffCreateRequest), + OpenAPI({ + operationId: 'create_origin_handoff', + summary: 'Create origin handoff', + responseSchema: OriginHandoffCreateResponse, + statusCode: 200, + security: ['sessionToken'], + tags: ['Auth'], + description: + 'Store encrypted client state for up to two minutes so another first-party web origin can redeem it once. The receiving origin must present the nonce whose SHA-256 digest is sent here.', + }), + async (ctx) => { + const body = ctx.req.valid('json'); + const handoffId = await createOriginHandoff(ctx.get('cacheService'), { + userId: ctx.get('user').id, + nonceHash: body.nonce_hash, + payload: body.payload, + }); + const response: OriginHandoffCreateResponse = {handoff_id: handoffId}; + return ctx.json(response); + }, + ); + app.post( + '/auth/origin-handoff/redeem', + RateLimitMiddleware(RateLimitConfigs.AUTH_ORIGIN_HANDOFF_REDEEM), + Validator('json', OriginHandoffRedeemRequest), + OpenAPI({ + operationId: 'redeem_origin_handoff', + summary: 'Redeem origin handoff', + responseSchema: OriginHandoffRedeemResponse, + statusCode: 200, + security: [], + tags: ['Auth'], + description: + 'Return the encrypted client state stored by create origin handoff and delete it in the same step. A wrong nonce also consumes the handoff. On the official instance the request must come from a first-party web origin.', + }), + async (ctx) => { + if (!Config.instance.selfHosted) { + const origin = ctx.req.header('origin'); + if (origin === undefined || !Config.endpoints.webAppOrigins.includes(origin)) { + throw new InvalidApiOriginError(); + } + } + const body = ctx.req.valid('json'); + const payload = await redeemOriginHandoff(ctx.get('cacheService'), { + handoffId: body.handoff_id, + nonce: body.nonce, + }); + const response: OriginHandoffRedeemResponse = {payload}; + return ctx.json(response); + }, + ); +} diff --git a/fluxer_api/src/api/auth/services/DesktopHandoffService.ts b/fluxer_api/src/api/auth/services/DesktopHandoffService.ts index dcc62e348..54d68447a 100644 --- a/fluxer_api/src/api/auth/services/DesktopHandoffService.ts +++ b/fluxer_api/src/api/auth/services/DesktopHandoffService.ts @@ -25,6 +25,7 @@ const POLL_SECRET_BYTES = 32; interface HandoffData { createdAt: number; origin: SessionOrigin; + initiatorOrigin?: string | null; infoLookupCount: number; pollSecretHash: string; } @@ -84,7 +85,7 @@ function pollSecretMatches(presented: string | undefined, storedHash: string | u export class DesktopHandoffService { constructor(private readonly apiContext: ApiContext) {} - async initiateHandoff(args: {origin: SessionOrigin}): Promise<{ + async initiateHandoff(args: {origin: SessionOrigin; initiatorOrigin?: string | null}): Promise<{ code: string; expiresAt: Date; pollSecret: string; @@ -95,6 +96,7 @@ export class DesktopHandoffService { const handoffData: HandoffData = { createdAt: Date.now(), origin: args.origin, + initiatorOrigin: args.initiatorOrigin ?? null, infoLookupCount: 0, pollSecretHash: hashPollSecret(pollSecret), }; @@ -108,7 +110,7 @@ export class DesktopHandoffService { code: string, createTokenData: (origin: SessionOrigin) => Promise<{token: string; userId: string}>, approverIp: string, - ): Promise { + ): Promise<{initiatorOrigin: string | null}> { const {cache} = this.apiContext.services; const normalizedCode = requireNormalizedHandoffCode(code); await this.checkAttemptLimit(approverIp); @@ -138,6 +140,7 @@ export class DesktopHandoffService { await cache.set(`${HANDOFF_TOKEN_PREFIX}${normalizedCode}`, tokenData, remainingSeconds); await cache.delete(`${HANDOFF_CODE_PREFIX}${normalizedCode}`); await cache.delete(`${HANDOFF_APPROVER_PREFIX}${normalizedCode}`); + return {initiatorOrigin: handoffData.initiatorOrigin ?? null}; } async getHandoffInfo( diff --git a/fluxer_api/src/api/auth/services/OriginHandoffService.ts b/fluxer_api/src/api/auth/services/OriginHandoffService.ts new file mode 100644 index 000000000..7a386225c --- /dev/null +++ b/fluxer_api/src/api/auth/services/OriginHandoffService.ts @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {createHash, randomBytes, timingSafeEqual} from 'node:crypto'; +import type {UserID} from '@app/api/BrandedTypes'; +import {InvalidOriginHandoffNonceError} from '@fluxer/errors/src/domains/auth/InvalidOriginHandoffNonceError'; +import {UnknownOriginHandoffError} from '@fluxer/errors/src/domains/auth/UnknownOriginHandoffError'; +import type {ICacheService} from '@pkgs/cache/src/ICacheService'; +import {seconds} from 'itty-time'; + +const ORIGIN_HANDOFF_KEY_PREFIX = 'origin_handoff:'; +const ORIGIN_HANDOFF_ID_BYTES = 32; + +interface OriginHandoffRecord { + nonce_hash: string; + payload: string; + user_id: string; + created_at: number; +} + +function sha256Hex(value: string): string { + return createHash('sha256').update(value).digest('hex'); +} + +function originHandoffKey(handoffId: string): string { + return `${ORIGIN_HANDOFF_KEY_PREFIX}${sha256Hex(handoffId)}`; +} + +export async function createOriginHandoff( + cache: ICacheService, + args: {userId: UserID; nonceHash: string; payload: string}, +): Promise { + const handoffId = randomBytes(ORIGIN_HANDOFF_ID_BYTES).toString('base64url'); + const record: OriginHandoffRecord = { + nonce_hash: args.nonceHash, + payload: args.payload, + user_id: args.userId.toString(), + created_at: Date.now(), + }; + await cache.set(originHandoffKey(handoffId), record, seconds('2 minutes')); + return handoffId; +} + +export async function redeemOriginHandoff( + cache: ICacheService, + args: {handoffId: string; nonce: string}, +): Promise { + const record = await cache.getAndDelete(originHandoffKey(args.handoffId)); + if (!record) { + throw new UnknownOriginHandoffError(); + } + const presented = Buffer.from(sha256Hex(args.nonce), 'hex'); + const stored = Buffer.from(record.nonce_hash, 'hex'); + if (presented.length !== stored.length || !timingSafeEqual(presented, stored)) { + throw new InvalidOriginHandoffNonceError(); + } + return record.payload; +} diff --git a/fluxer_api/src/api/auth/tests/OriginHandoff.test.ts b/fluxer_api/src/api/auth/tests/OriginHandoff.test.ts new file mode 100644 index 000000000..2a0351194 --- /dev/null +++ b/fluxer_api/src/api/auth/tests/OriginHandoff.test.ts @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {createHash, randomBytes} from 'node:crypto'; +import {createAuthHarness, createTestAccount} from '@app/api/auth/tests/AuthTestUtils'; +import {createTestBotAccount} from '@app/api/bot/tests/BotTestUtils'; +import {getConfig} from '@app/api/Config'; +import type {ApiTestHarness} from '@app/api/test/ApiTestHarness'; +import {HTTP_STATUS} from '@app/api/test/TestConstants'; +import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder'; +import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes'; +import {SuspiciousActivityFlags} from '@fluxer/constants/src/UserConstants'; +import { + ORIGIN_HANDOFF_MAX_PAYLOAD_LENGTH, + type OriginHandoffCreateResponse, + type OriginHandoffRedeemResponse, +} from '@fluxer/schema/src/domains/auth/OriginHandoffSchemas'; +import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it} from 'vitest'; + +const CREATE_PATH = '/auth/origin-handoff'; +const REDEEM_PATH = '/auth/origin-handoff/redeem'; +const PAYLOAD = randomBytes(96).toString('base64url'); + +function createNonce(): {nonce: string; nonceHash: string} { + const nonce = randomBytes(32).toString('base64url'); + return {nonce, nonceHash: createHash('sha256').update(nonce).digest('hex')}; +} + +describe('Origin handoff', () => { + let harness: ApiTestHarness; + let webAppOrigin: string; + + beforeAll(async () => { + harness = await createAuthHarness(); + webAppOrigin = getConfig().endpoints.webAppOrigins[0]; + }); + + beforeEach(async () => { + await harness.reset(); + }); + + afterEach(() => { + getConfig().instance.selfHosted = false; + getConfig().endpoints.webAppOrigins = [webAppOrigin]; + }); + + afterAll(async () => { + await harness?.shutdown(); + }); + + async function createHandoff(token: string, nonceHash: string): Promise { + const response = await createBuilder(harness, token) + .post(CREATE_PATH) + .body({nonce_hash: nonceHash, payload: PAYLOAD}) + .execute(); + expect(response.handoff_id).toMatch(/^[A-Za-z0-9_-]{43}$/); + return response.handoff_id; + } + + it('hands the payload over once to the origin that holds the nonce', async () => { + const account = await createTestAccount(harness); + const {nonce, nonceHash} = createNonce(); + const handoffId = await createHandoff(account.token, nonceHash); + + const redeemed = await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .header('origin', webAppOrigin) + .body({handoff_id: handoffId, nonce}) + .execute(); + expect(redeemed).toEqual({payload: PAYLOAD}); + + await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .header('origin', webAppOrigin) + .body({handoff_id: handoffId, nonce}) + .expect(HTTP_STATUS.NOT_FOUND, APIErrorCodes.UNKNOWN_ORIGIN_HANDOFF) + .execute(); + }); + + it('consumes the handoff when the nonce does not match', async () => { + const account = await createTestAccount(harness); + const {nonce, nonceHash} = createNonce(); + const handoffId = await createHandoff(account.token, nonceHash); + + await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .header('origin', webAppOrigin) + .body({handoff_id: handoffId, nonce: createNonce().nonce}) + .expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.INVALID_ORIGIN_HANDOFF_NONCE) + .execute(); + + await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .header('origin', webAppOrigin) + .body({handoff_id: handoffId, nonce}) + .expect(HTTP_STATUS.NOT_FOUND, APIErrorCodes.UNKNOWN_ORIGIN_HANDOFF) + .execute(); + }); + + it('answers an unknown handoff id with its own error code', async () => { + await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .header('origin', webAppOrigin) + .body({handoff_id: randomBytes(32).toString('base64url'), nonce: createNonce().nonce}) + .expect(HTTP_STATUS.NOT_FOUND, APIErrorCodes.UNKNOWN_ORIGIN_HANDOFF) + .execute(); + }); + + it('requires a logged-in user to create a handoff', async () => { + await createBuilderWithoutAuth(harness) + .post(CREATE_PATH) + .body({nonce_hash: createNonce().nonceHash, payload: PAYLOAD}) + .expect(HTTP_STATUS.UNAUTHORIZED) + .execute(); + }); + + it('refuses to create a handoff for an account flagged as suspicious', async () => { + const account = await createTestAccount(harness); + await createBuilderWithoutAuth(harness) + .post(`/test/users/${account.userId}/security-flags`) + .body({suspicious_activity_flags: SuspiciousActivityFlags.REQUIRE_VERIFIED_PHONE}) + .execute(); + await createBuilder(harness, account.token) + .post(CREATE_PATH) + .body({nonce_hash: createNonce().nonceHash, payload: PAYLOAD}) + .expect(HTTP_STATUS.FORBIDDEN, APIErrorCodes.ACCOUNT_SUSPICIOUS_ACTIVITY) + .execute(); + }); + + it('refuses a create body larger than the payload ceiling before parsing it', async () => { + const account = await createTestAccount(harness); + await createBuilder(harness, account.token) + .post(CREATE_PATH) + .body({nonce_hash: createNonce().nonceHash, payload: 'a'.repeat(ORIGIN_HANDOFF_MAX_PAYLOAD_LENGTH + 2048)}) + .expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.FILE_SIZE_TOO_LARGE) + .execute(); + }); + + it('refuses to create a handoff for a bot', async () => { + const bot = await createTestBotAccount(harness); + await createBuilder(harness, `Bot ${bot.botToken}`) + .post(CREATE_PATH) + .body({nonce_hash: createNonce().nonceHash, payload: PAYLOAD}) + .expect(HTTP_STATUS.FORBIDDEN) + .execute(); + }); + + it.each([ + {name: 'an uppercase nonce hash', body: {nonce_hash: 'A'.repeat(64), payload: PAYLOAD}}, + {name: 'a short nonce hash', body: {nonce_hash: 'a'.repeat(63), payload: PAYLOAD}}, + {name: 'a payload outside base64url', body: {nonce_hash: 'a'.repeat(64), payload: 'not+base64/url='}}, + {name: 'an empty payload', body: {nonce_hash: 'a'.repeat(64), payload: ''}}, + ])('rejects $name', async ({body}) => { + const account = await createTestAccount(harness); + await createBuilder(harness, account.token) + .post(CREATE_PATH) + .body(body) + .expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.INVALID_FORM_BODY) + .execute(); + }); + + it('refuses a redeem from an origin outside the first-party web origins', async () => { + const account = await createTestAccount(harness); + const {nonce, nonceHash} = createNonce(); + const handoffId = await createHandoff(account.token, nonceHash); + + await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .header('origin', 'https://evil.example') + .body({handoff_id: handoffId, nonce}) + .expect(HTTP_STATUS.FORBIDDEN, APIErrorCodes.INVALID_API_ORIGIN) + .execute(); + + await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .body({handoff_id: handoffId, nonce}) + .expect(HTTP_STATUS.FORBIDDEN, APIErrorCodes.INVALID_API_ORIGIN) + .execute(); + + const redeemed = await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .header('origin', webAppOrigin) + .body({handoff_id: handoffId, nonce}) + .execute(); + expect(redeemed.payload).toBe(PAYLOAD); + }); + + it('accepts a redeem from a configured web app origin alias', async () => { + getConfig().endpoints.webAppOrigins = [webAppOrigin, 'https://fluxer.com']; + const account = await createTestAccount(harness); + const {nonce, nonceHash} = createNonce(); + const handoffId = await createHandoff(account.token, nonceHash); + + const redeemed = await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .header('origin', 'https://fluxer.com') + .body({handoff_id: handoffId, nonce}) + .execute(); + expect(redeemed.payload).toBe(PAYLOAD); + }); + + it('skips the origin check on a self-hosted instance', async () => { + getConfig().instance.selfHosted = true; + const account = await createTestAccount(harness); + const {nonce, nonceHash} = createNonce(); + const handoffId = await createHandoff(account.token, nonceHash); + + const redeemed = await createBuilderWithoutAuth(harness) + .post(REDEEM_PATH) + .body({handoff_id: handoffId, nonce}) + .execute(); + expect(redeemed.payload).toBe(PAYLOAD); + }); +}); diff --git a/fluxer_api/src/api/config/APIConfig.ts b/fluxer_api/src/api/config/APIConfig.ts index e2a93f835..3a83abf8e 100644 --- a/fluxer_api/src/api/config/APIConfig.ts +++ b/fluxer_api/src/api/config/APIConfig.ts @@ -129,6 +129,7 @@ export interface APIConfig { apiPublic: string; apiClient: string; webApp: string; + webAppOrigins: Array; gateway: string; media: string; staticCdn: string; diff --git a/fluxer_api/src/api/experiment/ExperimentController.ts b/fluxer_api/src/api/experiment/ExperimentController.ts index 77570311e..7faa1f26e 100644 --- a/fluxer_api/src/api/experiment/ExperimentController.ts +++ b/fluxer_api/src/api/experiment/ExperimentController.ts @@ -8,6 +8,7 @@ import {RateLimitConfigs} from '@app/api/RateLimitConfig'; import type {HonoApp} from '@app/api/types/HonoEnv'; import {entityTagMatches} from '@app/api/utils/EntityTag'; import {Headers as HttpHeaders} from '@fluxer/constants/src/Headers'; +import {resolveDomainMigrationAssignment} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import {resolveVoiceNoiseSuppressionAssignment} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas'; import {ExperimentAssignmentsResponse} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; @@ -28,9 +29,10 @@ export function ExperimentController(app: HonoApp) { }), async (ctx) => { const instanceConfigRepository = ctx.get('instanceConfigRepository'); - const [delivery, voiceConfig] = await Promise.all([ + const [delivery, voiceConfig, domainMigrationConfig] = await Promise.all([ instanceConfigRepository.getExperimentDeliveryConfig(), instanceConfigRepository.getVoiceNoiseSuppressionConfig(), + instanceConfigRepository.getDomainMigrationConfig(), ]); const userId = ctx.get('user').id.toString(); const body: ExperimentAssignmentsResponse = { @@ -38,6 +40,7 @@ export function ExperimentController(app: HonoApp) { poll_jitter_percent: delivery.poll_jitter_percent, assignments: { voice_noise_suppression: resolveVoiceNoiseSuppressionAssignment(voiceConfig, userId), + domain_migration: resolveDomainMigrationAssignment(domainMigrationConfig, userId), }, }; const etag = `"${createHash('sha256').update(JSON.stringify(body)).digest('hex')}"`; diff --git a/fluxer_api/src/api/experiment/tests/ExperimentController.test.ts b/fluxer_api/src/api/experiment/tests/ExperimentController.test.ts index 3b20faad7..41c505a2e 100644 --- a/fluxer_api/src/api/experiment/tests/ExperimentController.test.ts +++ b/fluxer_api/src/api/experiment/tests/ExperimentController.test.ts @@ -6,6 +6,10 @@ import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHa import {HTTP_STATUS} from '@app/api/test/TestConstants'; import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder'; import {AdminACLs} from '@fluxer/constants/src/AdminACLs'; +import { + DEFAULT_DOMAIN_MIGRATION_CONFIG, + INERT_DOMAIN_MIGRATION_ASSIGNMENT, +} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import { DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG, INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, @@ -15,6 +19,7 @@ import { DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT, type ExperimentAssignmentsResponse, type ExperimentDeliveryConfigResponse, + readDomainMigrationAssignment, readVoiceNoiseSuppressionAssignment, } from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest'; @@ -51,6 +56,7 @@ describe('GET /experiments', () => { poll_jitter_percent: DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT, assignments: { voice_noise_suppression: INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, + domain_migration: INERT_DOMAIN_MIGRATION_ASSIGNMENT, }, }); }); @@ -82,6 +88,52 @@ describe('GET /experiments', () => { expect(readVoiceNoiseSuppressionAssignment(body).enabled).toBe(false); }); + it('populates the domain migration assignment key even when the rollout is disabled', async () => { + const account = await createTestAccount(harness); + + const body = await createBuilder(harness, account.token).get(ENDPOINT).execute(); + + expect(Object.hasOwn(body.assignments, 'domain_migration')).toBe(true); + expect(readDomainMigrationAssignment(body).enabled).toBe(false); + }); + + it('resolves the domain migration caller through the allowlist', async () => { + const targeted = await createTestAccount(harness); + const untargeted = await createTestAccount(harness); + await getInstanceConfigRepository().setDomainMigrationConfig({ + ...DEFAULT_DOMAIN_MIGRATION_CONFIG, + enabled: true, + config_version: 4, + rollout_basis_points: 0, + included_user_ids: [targeted.userId], + }); + + const targetedBody = await createBuilder(harness, targeted.token) + .get(ENDPOINT) + .execute(); + expect(targetedBody.assignments.domain_migration).toEqual({enabled: true}); + + const untargetedBody = await createBuilder(harness, untargeted.token) + .get(ENDPOINT) + .execute(); + expect(untargetedBody.assignments.domain_migration).toEqual({enabled: false}); + }); + + it('keeps the domain migration exclusion ahead of a full rollout', async () => { + const excluded = await createTestAccount(harness); + await getInstanceConfigRepository().setDomainMigrationConfig({ + ...DEFAULT_DOMAIN_MIGRATION_CONFIG, + enabled: true, + rollout_basis_points: 10000, + included_user_ids: [excluded.userId], + excluded_user_ids: [excluded.userId], + }); + + const body = await createBuilder(harness, excluded.token).get(ENDPOINT).execute(); + + expect(body.assignments.domain_migration).toEqual({enabled: false}); + }); + it('serves the delivery cadence from the delivery config and not from the voice config', async () => { const account = await createTestAccount(harness); await getInstanceConfigRepository().setExperimentDeliveryConfig({ @@ -196,6 +248,30 @@ describe('GET /experiments', () => { }); }); + it('serves a fresh body once the domain migration config changes', async () => { + const account = await createTestAccount(harness); + + const first = await createBuilder(harness, account.token) + .get(ENDPOINT) + .executeWithResponse(); + const staleEtag = first.response.headers.get('etag') as string; + + await getInstanceConfigRepository().setDomainMigrationConfig({ + ...DEFAULT_DOMAIN_MIGRATION_CONFIG, + enabled: true, + config_version: 1, + rollout_basis_points: 10000, + }); + + const refreshed = await createBuilder(harness, account.token) + .get(ENDPOINT) + .header('If-None-Match', staleEtag) + .executeWithResponse(); + expect(refreshed.response.status).toBe(HTTP_STATUS.OK); + expect(refreshed.response.headers.get('etag')).not.toBe(staleEtag); + expect(refreshed.json?.assignments.domain_migration).toEqual({enabled: true}); + }); + it('serves a fresh body once the delivery config changes', async () => { const account = await createTestAccount(harness); @@ -252,6 +328,47 @@ describe('GET /experiments', () => { }); }); + it('bumps the domain migration config version on every admin update without the client sending one', async () => { + const admin = await setUserACLs(harness, await createTestAccount(harness), [ + AdminACLs.AUTHENTICATE, + AdminACLs.INSTANCE_CONFIG_VIEW, + AdminACLs.INSTANCE_CONFIG_UPDATE, + ]); + + const afterFirst = await createBuilder<{domain_migration: {config_version: number; enabled: boolean}}>( + harness, + admin.token, + ) + .patch('/admin/instance/config') + .body({domain_migration: {enabled: true, rollout_basis_points: 10000}}) + .execute(); + expect(afterFirst.domain_migration).toMatchObject({config_version: 1, enabled: true}); + + const afterSecond = await createBuilder<{ + domain_migration: {config_version: number; enabled: boolean; anonymous_rollout_basis_points: number}; + }>(harness, admin.token) + .patch('/admin/instance/config') + .body({domain_migration: {anonymous_rollout_basis_points: 2500}}) + .execute(); + expect(afterSecond.domain_migration).toMatchObject({ + config_version: 2, + enabled: true, + anonymous_rollout_basis_points: 2500, + }); + + const afterEmpty = await createBuilder<{domain_migration: {config_version: number; enabled: boolean}}>( + harness, + admin.token, + ) + .patch('/admin/instance/config') + .body({domain_migration: {}}) + .execute(); + expect(afterEmpty.domain_migration).toMatchObject({config_version: 2, enabled: true}); + + const body = await createBuilder(harness, admin.token).get(ENDPOINT).execute(); + expect(body.assignments.domain_migration).toEqual({enabled: true}); + }); + it('leaves the config version alone for an admin update that sets no field', async () => { const admin = await setUserACLs(harness, await createTestAccount(harness), [ AdminACLs.AUTHENTICATE, diff --git a/fluxer_api/src/api/instance/InstanceConfigRepository.test.ts b/fluxer_api/src/api/instance/InstanceConfigRepository.test.ts index 864e2e944..66667b445 100644 --- a/fluxer_api/src/api/instance/InstanceConfigRepository.test.ts +++ b/fluxer_api/src/api/instance/InstanceConfigRepository.test.ts @@ -18,6 +18,10 @@ import {InstanceConfigWriteRaceExecutor} from '@app/api/instance/tests/InstanceC import {startDockerContainer} from '@app/api/test/DockerTestContainer'; import {InMemoryCassandraQueryExecutor} from '@app/api/test/InMemoryCassandraQueryExecutor'; import {MockKVProvider} from '@app/api/test/mocks/MockKVProvider'; +import { + DEFAULT_DOMAIN_MIGRATION_CONFIG, + type DomainMigrationConfig, +} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import { DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG, type VoiceNoiseSuppressionConfig, @@ -35,6 +39,7 @@ import { import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest'; const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config'; +const DOMAIN_MIGRATION_CONFIG_KEY = 'domain_migration_config'; const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config'; const APP_PUBLIC_CONFIG_KEY = 'app_public_config'; const INSTANCE_POLICY_CONFIG_KEY = 'instance_policy_config'; @@ -351,6 +356,92 @@ describe('InstanceConfigRepository', () => { }); }); + it('returns the default domain migration config when the key is absent', async () => { + const executor = new CountingInMemoryCassandraQueryExecutor(); + setCassandraQueryExecutorForTesting(executor); + const kvProvider = new MockKVProvider(); + const repository = createRepository(kvProvider); + + await expect(repository.getDomainMigrationConfig()).resolves.toEqual(DEFAULT_DOMAIN_MIGRATION_CONFIG); + }); + + it.each([ + {name: 'unparseable text', stored: 'not-json'}, + {name: 'a json array', stored: '[]'}, + {name: 'out-of-range values', stored: '{"rollout_basis_points":99999}'}, + {name: 'a non-boolean enabled flag', stored: '{"enabled":"yes"}'}, + ])('falls back to the default domain migration config for $name', async ({stored}) => { + const executor = new CountingInMemoryCassandraQueryExecutor(); + setCassandraQueryExecutorForTesting(executor); + const kvProvider = new MockKVProvider(); + const repository = createRepository(kvProvider); + + await repository.setConfig(DOMAIN_MIGRATION_CONFIG_KEY, stored); + + await expect(repository.getDomainMigrationConfig()).resolves.toEqual(DEFAULT_DOMAIN_MIGRATION_CONFIG); + }); + + it('round-trips a stored domain migration config', async () => { + const executor = new CountingInMemoryCassandraQueryExecutor(); + setCassandraQueryExecutorForTesting(executor); + const kvProvider = new MockKVProvider(); + const repository = createRepository(kvProvider); + + const config: DomainMigrationConfig = { + ...DEFAULT_DOMAIN_MIGRATION_CONFIG, + enabled: true, + config_version: 5, + rollout_basis_points: 2500, + rollout_salt: 'domain-migration-v2', + included_user_ids: ['1400000000000000001'], + excluded_user_ids: ['1400000000000000002'], + anonymous_rollout_basis_points: 300, + standalone_forwarding: true, + }; + await repository.setDomainMigrationConfig(config); + + await expect(repository.getDomainMigrationConfig()).resolves.toEqual(config); + }); + + it('fills newly added domain migration fields from the schema defaults', async () => { + const executor = new CountingInMemoryCassandraQueryExecutor(); + setCassandraQueryExecutorForTesting(executor); + const kvProvider = new MockKVProvider(); + const repository = createRepository(kvProvider); + + await repository.setConfig( + DOMAIN_MIGRATION_CONFIG_KEY, + JSON.stringify({enabled: true, config_version: 2, rollout_basis_points: 1000}), + ); + + await expect(repository.getDomainMigrationConfig()).resolves.toEqual({ + ...DEFAULT_DOMAIN_MIGRATION_CONFIG, + enabled: true, + config_version: 2, + rollout_basis_points: 1000, + }); + }); + + it('publishes a refresh so another repository observes the domain migration config', async () => { + const executor = new CountingInMemoryCassandraQueryExecutor(); + setCassandraQueryExecutorForTesting(executor); + const kvProvider = new MockKVProvider(); + const reader = createRepository(kvProvider); + const writer = createRepository(kvProvider); + + await expect(reader.getDomainMigrationConfig()).resolves.toEqual(DEFAULT_DOMAIN_MIGRATION_CONFIG); + + await writer.setDomainMigrationConfig({ + ...DEFAULT_DOMAIN_MIGRATION_CONFIG, + enabled: true, + config_version: 1, + }); + + await vi.waitFor(async () => { + expect(await reader.getDomainMigrationConfig()).toMatchObject({enabled: true, config_version: 1}); + }); + }); + it('returns the default experiment delivery config when the key is absent', async () => { const executor = new CountingInMemoryCassandraQueryExecutor(); setCassandraQueryExecutorForTesting(executor); diff --git a/fluxer_api/src/api/instance/InstanceConfigRepository.ts b/fluxer_api/src/api/instance/InstanceConfigRepository.ts index c28277e2c..35614b317 100644 --- a/fluxer_api/src/api/instance/InstanceConfigRepository.ts +++ b/fluxer_api/src/api/instance/InstanceConfigRepository.ts @@ -28,6 +28,10 @@ import { type PendingRegistrationResponse, type RegistrationUrlResponse, } from '@fluxer/schema/src/domains/admin/AdminSchemas'; +import { + type DomainMigrationConfig, + DomainMigrationConfigSchema, +} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import { type GatewayRolloutConfig, GatewayRolloutConfigSchema, @@ -63,6 +67,7 @@ import {z} from 'zod'; const GATEWAY_ROLLOUT_CONFIG_KEY = 'gateway_rollout_config'; const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config'; const PUSH_SERVICE_DELIVERY_CONFIG_KEY = 'push_service_delivery_config'; +const DOMAIN_MIGRATION_CONFIG_KEY = 'domain_migration_config'; const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config'; const REGISTRATION_CONFIG_KEY = 'registration_config'; const REGISTRATION_URLS_KEY = 'registration_urls'; @@ -370,6 +375,7 @@ type StoredConfigSection = | 'gateway rollout' | 'voice noise suppression' | 'push service delivery' + | 'domain migration' | 'experiment delivery' | 'instance policy' | 'integrations' @@ -512,6 +518,10 @@ function parseStoredPushServiceDeliveryConfig(raw: string | null): PushServiceDe return parseStoredConfigOrDefault(PushServiceDeliveryConfigSchema, raw, 'push service delivery'); } +function parseStoredDomainMigrationConfig(raw: string | null): DomainMigrationConfig { + return parseStoredConfigOrDefault(DomainMigrationConfigSchema, raw, 'domain migration'); +} + function parseStoredExperimentDeliveryConfig(raw: string | null): ExperimentDeliveryConfig { return parseStoredConfigOrDefault(ExperimentDeliveryConfigSchema, raw, 'experiment delivery'); } @@ -1160,6 +1170,7 @@ export class InstanceConfigRepository { ); parseStoredVoiceNoiseSuppressionConfig(snapshot.get(VOICE_NOISE_SUPPRESSION_CONFIG_KEY) ?? null); parseStoredPushServiceDeliveryConfig(snapshot.get(PUSH_SERVICE_DELIVERY_CONFIG_KEY) ?? null); + parseStoredDomainMigrationConfig(snapshot.get(DOMAIN_MIGRATION_CONFIG_KEY) ?? null); parseStoredExperimentDeliveryConfig(snapshot.get(EXPERIMENT_DELIVERY_CONFIG_KEY) ?? null); const policy = parseStoredInstancePolicyConfig(snapshot.get(INSTANCE_POLICY_CONFIG_KEY) ?? null); checkStoredConfig('registration', () => @@ -1273,6 +1284,27 @@ export class InstanceConfigRepository { ); } + async getDomainMigrationConfig(): Promise { + const raw = await this.getConfig(DOMAIN_MIGRATION_CONFIG_KEY); + return parseStoredDomainMigrationConfig(raw); + } + + async setDomainMigrationConfig(config: DomainMigrationConfig): Promise { + await this.updateDomainMigrationConfig(() => config); + } + + updateDomainMigrationConfig( + update: (current: DomainMigrationConfig) => DomainMigrationConfig, + ): Promise { + return this.updateStoredConfig(DOMAIN_MIGRATION_CONFIG_KEY, (raw) => + validateStoredConfig( + DomainMigrationConfigSchema, + update(parseStoredDomainMigrationConfig(raw)), + 'domain migration', + ), + ); + } + async getExperimentDeliveryConfig(): Promise { const raw = await this.getConfig(EXPERIMENT_DELIVERY_CONFIG_KEY); return parseStoredExperimentDeliveryConfig(raw); diff --git a/fluxer_api/src/api/instance/InstanceController.test.ts b/fluxer_api/src/api/instance/InstanceController.test.ts index d848230e7..4b29c83a5 100644 --- a/fluxer_api/src/api/instance/InstanceController.test.ts +++ b/fluxer_api/src/api/instance/InstanceController.test.ts @@ -8,6 +8,7 @@ import type {LimitConfigService} from '@app/api/limits/LimitConfigService'; import {InMemoryCassandraQueryExecutor} from '@app/api/test/InMemoryCassandraQueryExecutor'; import {MockKVProvider} from '@app/api/test/mocks/MockKVProvider'; import type {HonoEnv} from '@app/api/types/HonoEnv'; +import {DEFAULT_DOMAIN_MIGRATION_CONFIG} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import {Hono} from 'hono'; import {afterEach, describe, expect, it} from 'vitest'; @@ -96,4 +97,36 @@ describe('InstanceController discovery captcha', () => { turnstile_site_key: 'turnstile-site-key', }); }); + + it('publishes the domain migration kill switch and anonymous rollout without the targeting lists', async () => { + const repository = createRepository(); + const app = createApp(repository); + + const initial = await app.request('http://localhost/.well-known/fluxer'); + expect(((await initial.json()) as {domain_migration: unknown}).domain_migration).toEqual({ + enabled: false, + anonymous_rollout_basis_points: 0, + rollout_salt: 'domain-migration-v1', + standalone_forwarding: false, + }); + + await repository.setDomainMigrationConfig({ + ...DEFAULT_DOMAIN_MIGRATION_CONFIG, + enabled: true, + config_version: 2, + rollout_basis_points: 100, + anonymous_rollout_basis_points: 1500, + included_user_ids: ['1400000000000000001'], + standalone_forwarding: true, + }); + + const updated = await app.request('http://localhost/.well-known/fluxer'); + expect(updated.headers.get('etag')).not.toBe(initial.headers.get('etag')); + expect(((await updated.json()) as {domain_migration: unknown}).domain_migration).toEqual({ + enabled: true, + anonymous_rollout_basis_points: 1500, + rollout_salt: 'domain-migration-v1', + standalone_forwarding: true, + }); + }); }); diff --git a/fluxer_api/src/api/instance/InstanceController.ts b/fluxer_api/src/api/instance/InstanceController.ts index eaba6cca4..3a4c1ece6 100644 --- a/fluxer_api/src/api/instance/InstanceController.ts +++ b/fluxer_api/src/api/instance/InstanceController.ts @@ -16,6 +16,7 @@ import type {HonoEnv} from '@app/api/types/HonoEnv'; import {API_CODE_VERSION} from '@fluxer/constants/src/AppConstants'; import {buildDiscoveryResponse, type DiscoveryStaticInput} from '@fluxer/instance_bootstrap/src/BuildDiscovery'; import type {InstanceAppPublic} from '@fluxer/instance_bootstrap/src/Types'; +import {toDomainMigrationDiscovery} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import {WellKnownFluxerResponse} from '@fluxer/schema/src/domains/instance/InstanceSchemas'; import type {Hono} from 'hono'; @@ -102,15 +103,16 @@ export function InstanceController(app: Hono) { const limits = ctx.get('limitConfigService').getConfigWireFormat(); const sso = await ctx.get('ssoService').getPublicStatus(); const instanceConfigRepository = ctx.get('instanceConfigRepository'); - const [registration, community, services, appPublicConfig, captcha, email] = await Promise.all([ + const [registration, community, services, appPublicConfig, captcha, email, domainMigration] = await Promise.all([ instanceConfigRepository.getRegistrationPublicConfig(), instanceConfigRepository.getInstanceCommunityPublicConfig(), instanceConfigRepository.getResolvedServicesConfig(), instanceConfigRepository.getAppPublicConfig(), instanceConfigRepository.getEffectiveCaptchaConfig(), instanceConfigRepository.getEffectiveEmailConfig(), + instanceConfigRepository.getDomainMigrationConfig(), ]); - const response = buildDiscoveryResponse( + const discovery = buildDiscoveryResponse( buildDiscoveryStaticInput( gifService, { @@ -133,6 +135,7 @@ export function InstanceController(app: Hono) { limits, }, ); + const response = {...discovery, domain_migration: toDomainMigrationDiscovery(domainMigration)}; discoveryValidators = nextDiscoveryValidators(response, discoveryValidators); ctx.header('ETag', discoveryValidators.etag); ctx.header('Last-Modified', discoveryValidators.lastModified.toUTCString()); diff --git a/fluxer_api/src/api/middleware/BlockAppOriginMiddleware.ts b/fluxer_api/src/api/middleware/BlockAppOriginMiddleware.ts index 18eedbc6a..1f5a2520c 100644 --- a/fluxer_api/src/api/middleware/BlockAppOriginMiddleware.ts +++ b/fluxer_api/src/api/middleware/BlockAppOriginMiddleware.ts @@ -1,11 +1,17 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +import {Config} from '@app/api/Config'; import {InvalidApiOriginError} from '@fluxer/errors/src/domains/core/InvalidApiOriginError'; import type {Context, Next} from 'hono'; +const LEGACY_APP_ORIGINS = ['https://web.fluxer.app', 'https://web.canary.fluxer.app']; + export async function BlockAppOriginMiddleware(ctx: Context, next: Next) { const origin = ctx.req.header('origin'); - if (origin === 'https://web.fluxer.app' || origin === 'https://web.canary.fluxer.app') { + if ( + origin !== undefined && + (LEGACY_APP_ORIGINS.includes(origin) || Config.endpoints.webAppOrigins.includes(origin)) + ) { throw new InvalidApiOriginError(); } await next(); diff --git a/fluxer_api/src/api/openapi/openapi.json b/fluxer_api/src/api/openapi/openapi.json index 3ad52e8b1..1bbe60451 100644 --- a/fluxer_api/src/api/openapi/openapi.json +++ b/fluxer_api/src/api/openapi/openapi.json @@ -948,6 +948,111 @@ "security": [{"botToken": []}, {"sessionToken": []}] } }, + "/auth/origin-handoff": { + "post": { + "operationId": "create_origin_handoff", + "summary": "Create origin handoff", + "tags": ["Auth"], + "responses": { + "200": { + "description": "Success", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OriginHandoffCreateResponse"}}} + }, + "400": { + "description": "Bad Request - The request was malformed or contained invalid data", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} + }, + "401": { + "description": "Unauthorized - Authentication is required or the token is invalid", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} + }, + "403": { + "description": "Forbidden - You do not have permission to perform this action", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} + }, + "429": { + "description": "Too Many Requests - You are being rate limited", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ThrottledError"}}}, + "headers": { + "Retry-After": { + "description": "Number of seconds to wait before retrying (only on 429)", + "schema": {"type": "integer"} + }, + "X-RateLimit-Limit": { + "description": "The number of requests that can be made in the current window", + "schema": {"type": "integer"} + }, + "X-RateLimit-Remaining": { + "description": "The number of remaining requests that can be made", + "schema": {"type": "integer"} + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp when the rate limit resets", + "schema": {"type": "integer"} + } + } + }, + "500": { + "description": "Internal Server Error - An unexpected error occurred", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} + } + }, + "description": "Store encrypted client state for up to two minutes so another first-party web origin can redeem it once. The receiving origin must present the nonce whose SHA-256 digest is sent here.", + "security": [{"sessionToken": []}], + "requestBody": { + "required": true, + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OriginHandoffCreateRequest"}}} + } + } + }, + "/auth/origin-handoff/redeem": { + "post": { + "operationId": "redeem_origin_handoff", + "summary": "Redeem origin handoff", + "tags": ["Auth"], + "responses": { + "200": { + "description": "Success", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OriginHandoffRedeemResponse"}}} + }, + "400": { + "description": "Bad Request - The request was malformed or contained invalid data", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} + }, + "429": { + "description": "Too Many Requests - You are being rate limited", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ThrottledError"}}}, + "headers": { + "Retry-After": { + "description": "Number of seconds to wait before retrying (only on 429)", + "schema": {"type": "integer"} + }, + "X-RateLimit-Limit": { + "description": "The number of requests that can be made in the current window", + "schema": {"type": "integer"} + }, + "X-RateLimit-Remaining": { + "description": "The number of remaining requests that can be made", + "schema": {"type": "integer"} + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp when the rate limit resets", + "schema": {"type": "integer"} + } + } + }, + "500": { + "description": "Internal Server Error - An unexpected error occurred", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} + } + }, + "description": "Return the encrypted client state stored by create origin handoff and delete it in the same step. A wrong nonce also consumes the handoff. On the official instance the request must come from a first-party web origin.", + "requestBody": { + "required": true, + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OriginHandoffRedeemRequest"}}} + } + } + }, "/auth/register": { "post": { "operationId": "register_account", @@ -22495,7 +22600,8 @@ "required": ["p256dh", "auth"], "description": "Encryption keys for the push subscription" }, - "user_agent": {"description": "The user agent string identifying the client", "type": "string"} + "user_agent": {"description": "The user agent string identifying the client", "type": "string"}, + "installed_app": {"description": "Whether the client runs in an installed web app window", "type": "boolean"} }, "required": ["endpoint", "keys"] }, @@ -22524,7 +22630,8 @@ "required": ["p256dh", "auth"], "description": "Encryption keys for the new push subscription" }, - "user_agent": {"description": "The user agent string identifying the client", "type": "string"} + "user_agent": {"description": "The user agent string identifying the client", "type": "string"}, + "installed_app": {"description": "Whether the client runs in an installed web app window", "type": "boolean"} }, "required": ["old_endpoint", "endpoint", "keys"] }, @@ -27190,7 +27297,8 @@ "assignments": { "type": "object", "properties": { - "voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionAssignmentResponse"} + "voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionAssignmentResponse"}, + "domain_migration": {"$ref": "#/components/schemas/DomainMigrationAssignmentResponse"} }, "additionalProperties": false } @@ -28403,6 +28511,56 @@ {"$ref": "#/components/schemas/AuthRegistrationPendingApprovalResponse"} ] }, + "OriginHandoffRedeemRequest": { + "type": "object", + "properties": { + "handoff_id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{43}$", + "description": "Identifier returned when the handoff was created" + }, + "nonce": { + "type": "string", + "minLength": 16, + "maxLength": 256, + "pattern": "^[A-Za-z0-9_-]+$", + "description": "Nonce whose SHA-256 digest was sent when the handoff was created" + } + }, + "required": ["handoff_id", "nonce"] + }, + "OriginHandoffRedeemResponse": { + "type": "object", + "properties": {"payload": {"type": "string", "description": "Encrypted client state encoded as base64url"}}, + "required": ["payload"], + "additionalProperties": false + }, + "OriginHandoffCreateRequest": { + "type": "object", + "properties": { + "nonce_hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "Lowercase hex SHA-256 digest of the nonce the receiving origin holds" + }, + "payload": { + "type": "string", + "minLength": 1, + "maxLength": 8388608, + "pattern": "^[A-Za-z0-9_-]+$", + "description": "Encrypted client state encoded as base64url" + } + }, + "required": ["nonce_hash", "payload"] + }, + "OriginHandoffCreateResponse": { + "type": "object", + "properties": { + "handoff_id": {"type": "string", "description": "Single-use identifier the receiving origin redeems"} + }, + "required": ["handoff_id"], + "additionalProperties": false + }, "MfaTicketRequest": { "type": "object", "properties": {"ticket": {"description": "The MFA ticket from the login response", "type": "string"}}, @@ -28766,6 +28924,10 @@ }, "description": "Public application configuration for client-side features", "$ref": "#/components/schemas/InstanceAppPublicSchema" + }, + "domain_migration": { + "description": "Web domain migration switch and anonymous rollout, only acted on by official instance clients", + "$ref": "#/components/schemas/DomainMigrationDiscoveryResponse" } }, "required": [ @@ -28784,6 +28946,31 @@ ], "additionalProperties": false }, + "DomainMigrationDiscoveryResponse": { + "type": "object", + "properties": { + "enabled": {"type": "boolean", "description": "Whether the domain migration is switched on"}, + "anonymous_rollout_basis_points": { + "type": "integer", + "minimum": 0, + "maximum": 10000, + "description": "Share of logged-out devices, in basis points, that move to the new domain" + }, + "rollout_salt": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "pattern": "^[\\x20-\\x7e]+$", + "description": "Salt used to bucket devices and users" + }, + "standalone_forwarding": { + "type": "boolean", + "description": "Whether installed desktop web apps forward to the new domain after moving their session" + } + }, + "required": ["enabled", "anonymous_rollout_basis_points", "rollout_salt", "standalone_forwarding"], + "additionalProperties": false + }, "InstanceAppPublicSchema": { "type": "object", "properties": { @@ -30694,6 +30881,12 @@ "additionalProperties": false }, "DonationCurrency": {"type": "string", "enum": ["usd", "eur", "brl", "inr", "pln", "try", "sek", "dkk", "nok"]}, + "DomainMigrationAssignmentResponse": { + "type": "object", + "properties": {"enabled": {"type": "boolean"}}, + "required": ["enabled"], + "additionalProperties": false + }, "VoiceNoiseSuppressionAssignmentResponse": { "type": "object", "properties": { diff --git a/fluxer_api/src/api/rate_limit_configs/AuthRateLimitConfig.ts b/fluxer_api/src/api/rate_limit_configs/AuthRateLimitConfig.ts index 07234c01a..8173e469b 100644 --- a/fluxer_api/src/api/rate_limit_configs/AuthRateLimitConfig.ts +++ b/fluxer_api/src/api/rate_limit_configs/AuthRateLimitConfig.ts @@ -132,6 +132,14 @@ export const AuthRateLimitConfigs = { bucket: 'auth:handoff:cancel', config: {limit: 10, windowMs: ms('1 minute')}, } as RouteRateLimitConfig, + AUTH_ORIGIN_HANDOFF_CREATE: { + bucket: 'auth:origin_handoff:create', + config: {limit: 3, windowMs: ms('10 minutes')}, + } as RouteRateLimitConfig, + AUTH_ORIGIN_HANDOFF_REDEEM: { + bucket: 'auth:origin_handoff:redeem', + config: {limit: 10, windowMs: ms('1 minute')}, + } as RouteRateLimitConfig, SUDO_WEBAUTHN_OPTIONS: { bucket: 'sudo:webauthn:options', config: {limit: 10, windowMs: ms('1 minute')}, diff --git a/fluxer_api/src/api/stripe/services/StripeCheckoutService.ts b/fluxer_api/src/api/stripe/services/StripeCheckoutService.ts index 9aaee7465..4d2a3d720 100644 --- a/fluxer_api/src/api/stripe/services/StripeCheckoutService.ts +++ b/fluxer_api/src/api/stripe/services/StripeCheckoutService.ts @@ -39,7 +39,6 @@ import type Stripe from 'stripe'; const PRODUCT_NAME = 'Fluxer'; const PREMIUM_TIER_NAME = 'Plutonium'; -const TERMS_URL = 'https://fluxer.app/terms'; export const EU_WITHDRAWAL_WAIVER_TEXT_VERSION = '2026-04-23'; type CheckoutSessionCreateParams = Stripe.Checkout.SessionCreateParams; @@ -226,7 +225,7 @@ export class StripeCheckoutService { message: getContentMessage('billing.eu_withdrawal_waiver_checkout', user.locale, { product_name: PRODUCT_NAME, premium_tier_name: PREMIUM_TIER_NAME, - terms_url: TERMS_URL, + terms_url: `${Config.endpoints.marketing}/terms`, }), }, }, diff --git a/fluxer_api/src/api/user/controllers/UserAccountController.ts b/fluxer_api/src/api/user/controllers/UserAccountController.ts index c430b63c1..bb35cee14 100644 --- a/fluxer_api/src/api/user/controllers/UserAccountController.ts +++ b/fluxer_api/src/api/user/controllers/UserAccountController.ts @@ -3,6 +3,7 @@ import * as AuthSession from '@app/api/auth/AuthSession'; import {requireSudoMode} from '@app/api/auth/services/SudoVerificationService'; import {createGuildID, createUserID} from '@app/api/BrandedTypes'; +import {Config} from '@app/api/Config'; import {DefaultUserOnly, LoginRequired, LoginRequiredAllowSuspicious} from '@app/api/middleware/AuthMiddleware'; import {requireOAuth2ScopeForBearer} from '@app/api/middleware/OAuth2ScopeMiddleware'; import {RateLimitMiddleware} from '@app/api/middleware/RateLimitMiddleware'; @@ -10,6 +11,7 @@ import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware'; import {SudoModeMiddleware} from '@app/api/middleware/SudoModeMiddleware'; import {RateLimitConfigs} from '@app/api/RateLimitConfig'; import type {HonoApp} from '@app/api/types/HonoEnv'; +import {classifyWebPushOrigin} from '@app/api/user/services/WebPushOriginReplacement'; import {getCachedUserPartialResponse} from '@app/api/user/UserCacheHelpers'; import { mapUserGuildSettingsToResponse, @@ -854,7 +856,7 @@ export function UserAccountController(app: HonoApp) { 'Registers a new push notification subscription for the current user. Takes push endpoint and encryption keys from a Web Push API subscription. Returns subscription ID for future reference.', }), async (ctx) => { - const {endpoint, keys, user_agent} = ctx.req.valid('json'); + const {endpoint, keys, user_agent, installed_app} = ctx.req.valid('json'); const authSession = ctx.get('authSession'); const subscription = await ctx.get('userService').contentService.registerPushSubscription({ userId: ctx.get('user').id, @@ -862,6 +864,8 @@ export function UserAccountController(app: HonoApp) { endpoint, keys, userAgent: user_agent, + originKind: classifyWebPushOrigin(ctx.req.header('origin'), Config.instance.selfHosted), + installedApp: installed_app, }); return ctx.json({subscription_id: subscription.subscriptionId}); }, @@ -883,7 +887,7 @@ export function UserAccountController(app: HonoApp) { 'Replaces an existing push subscription whose endpoint has been rotated by the browser (pushsubscriptionchange). Deletes the row keyed by the old endpoint and inserts a new one for the new endpoint.', }), async (ctx) => { - const {old_endpoint, endpoint, keys, user_agent} = ctx.req.valid('json'); + const {old_endpoint, endpoint, keys, user_agent, installed_app} = ctx.req.valid('json'); const authSession = ctx.get('authSession'); const subscription = await ctx.get('userService').contentService.rotatePushSubscription({ userId: ctx.get('user').id, @@ -892,6 +896,8 @@ export function UserAccountController(app: HonoApp) { endpoint, keys, userAgent: user_agent, + originKind: classifyWebPushOrigin(ctx.req.header('origin'), Config.instance.selfHosted), + installedApp: installed_app, }); return ctx.json({subscription_id: subscription.subscriptionId}); }, diff --git a/fluxer_api/src/api/user/services/UserContentService.ts b/fluxer_api/src/api/user/services/UserContentService.ts index 932454c69..2b5fd8b2c 100644 --- a/fluxer_api/src/api/user/services/UserContentService.ts +++ b/fluxer_api/src/api/user/services/UserContentService.ts @@ -20,12 +20,23 @@ import {resolveLimitSafe} from '@app/api/limits/LimitConfigUtils'; import {createLimitMatchContext} from '@app/api/limits/LimitMatchContextBuilder'; import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware'; import type {Message} from '@app/api/models/Message'; -import type {PushSubscription} from '@app/api/models/PushSubscription'; +import {PushSubscription} from '@app/api/models/PushSubscription'; import type {IUserAccountRepository} from '@app/api/user/repositories/IUserAccountRepository'; import type {IUserContentRepository} from '@app/api/user/repositories/IUserContentRepository'; import {BaseUserUpdatePropagator} from '@app/api/user/services/BaseUserUpdatePropagator'; import {verifyHarvestDownloadToken} from '@app/api/user/services/HarvestDownloadToken'; import {buildHarvestDownloadUrl} from '@app/api/user/services/HarvestDownloadUrl'; +import { + findInstalledLegacyPushSubscriptionIds, + findTargetPushSubscriptionIds, + getPushOriginReplacement, + getPushSessionPredecessor, + markInstalledLegacyPushSubscription, + markPushOriginReplaced, + markTargetPushSubscription, + sameUserAgentFamily, + type WebPushOriginKind, +} from '@app/api/user/services/WebPushOriginReplacement'; import {UserHarvest} from '@app/api/user/UserHarvestModel'; import {UserHarvestRepository} from '@app/api/user/UserHarvestRepository'; import {serializeSelfMessageFilter} from '@app/api/worker/utils/SelfMessageFilterPayload'; @@ -56,6 +67,7 @@ import type { import type {SavedMessageStatus} from '@fluxer/schema/src/domains/user/UserResponseSchemas'; import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake'; import {isPubliclyRoutableUrlShape} from '@pkgs/http_client/src/PublicInternetRequestUrlPolicy'; +import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider'; import type {IWorkerService} from '@pkgs/worker/src/contracts/IWorkerService'; import {ms} from 'itty-time'; @@ -159,6 +171,7 @@ export class UserContentService { private readonly gatewayService: IGatewayService; private readonly workerService: IWorkerService; private readonly snowflakeService: ISnowflakeService; + private readonly kv: IKVProvider; constructor( apiContext: ApiContext, @@ -168,11 +181,12 @@ export class UserContentService { private bulkMessageDeletionQueue: KVBulkMessageDeletionQueueService, private limitConfigService: LimitConfigService, ) { - const {users, gateway, worker, snowflake} = apiContext.services; + const {users, gateway, worker, snowflake, kv} = apiContext.services; this.userRepository = users; this.gatewayService = gateway; this.workerService = worker; this.snowflakeService = snowflake; + this.kv = kv; this.updatePropagator = new BaseUserUpdatePropagator({ userCacheService, gatewayService: this.gatewayService, @@ -359,8 +373,10 @@ export class UserContentService { auth: string; }; userAgent?: string; + originKind?: WebPushOriginKind | null; + installedApp?: boolean; }): Promise { - const {userId, authSessionIdHash, endpoint, keys, userAgent} = params; + const {userId, authSessionIdHash, endpoint, keys, userAgent, originKind, installedApp} = params; assertPublicPushEndpoint(endpoint, 'endpoint'); const subscriptionId = createWebPushSubscriptionId(endpoint); const data: PushSubscriptionRow = { @@ -375,11 +391,76 @@ export class UserContentService { app_id: null, provider_environment: null, }; - const subscription = await this.userRepository.createPushSubscription(data); + const subscription = await this.storeWebPushSubscription(data, originKind ?? null, installedApp === true); await this.gatewayService.invalidatePushSubscriptions({userId}); return subscription; } + private async storeWebPushSubscription( + data: PushSubscriptionRow, + originKind: WebPushOriginKind | null, + installedApp: boolean, + ): Promise { + if (originKind === 'legacy' && (await this.isLegacyWebPushReplaced(data, installedApp))) { + return new PushSubscription(data); + } + const subscription = await this.userRepository.createPushSubscription(data); + if (originKind === 'legacy' && installedApp) { + await this.bestEffortPushOriginWrite(() => markInstalledLegacyPushSubscription(this.kv, data.subscription_id)); + } + if (originKind === 'target') { + await this.bestEffortPushOriginWrite(() => this.replaceLegacyWebPushSubscriptions(data, installedApp)); + } + return subscription; + } + + private async isLegacyWebPushReplaced(data: PushSubscriptionRow, installedApp: boolean): Promise { + const sessionIdHash = data.auth_session_id_hash; + if (!sessionIdHash) return false; + try { + const replacement = await getPushOriginReplacement(this.kv, sessionIdHash); + return replacement === 'installed' || (replacement === 'browser' && !installedApp); + } catch (error) { + Logger.warn({error}, 'Failed to read the web push origin replacement'); + return false; + } + } + + private async bestEffortPushOriginWrite(write: () => Promise): Promise { + try { + await write(); + } catch (error) { + Logger.warn({error}, 'Failed to apply the web push origin replacement'); + } + } + + private async replaceLegacyWebPushSubscriptions(data: PushSubscriptionRow, installedApp: boolean): Promise { + await markTargetPushSubscription(this.kv, data.subscription_id); + const sessionIdHash = data.auth_session_id_hash; + if (!sessionIdHash) return; + await markPushOriginReplaced(this.kv, sessionIdHash, installedApp ? 'installed' : 'browser'); + const predecessor = await getPushSessionPredecessor(this.kv, sessionIdHash); + const candidates = (await this.userRepository.listPushSubscriptions(data.user_id)).filter( + (subscription) => + subscription.platform === WEB_PUSH_PLATFORM && + subscription.endpoint !== data.endpoint && + (subscription.authSessionIdHash === sessionIdHash || + (predecessor !== null && + subscription.authSessionIdHash === predecessor && + sameUserAgentFamily(subscription.userAgent, data.user_agent))), + ); + const candidateIds = candidates.map((subscription) => subscription.subscriptionId); + const [targetSubscriptionIds, installedLegacySubscriptionIds] = await Promise.all([ + findTargetPushSubscriptionIds(this.kv, candidateIds), + installedApp ? Promise.resolve(new Set()) : findInstalledLegacyPushSubscriptionIds(this.kv, candidateIds), + ]); + for (const subscription of candidates) { + if (targetSubscriptionIds.has(subscription.subscriptionId)) continue; + if (installedLegacySubscriptionIds.has(subscription.subscriptionId)) continue; + await this.userRepository.deletePushSubscription(data.user_id, subscription.subscriptionId); + } + } + async listPushSubscriptions(userId: UserID): Promise> { const subscriptions = await this.userRepository.listPushSubscriptions(userId); return subscriptions.filter((subscription) => subscription.platform === WEB_PUSH_PLATFORM); @@ -400,8 +481,10 @@ export class UserContentService { auth: string; }; userAgent?: string; + originKind?: WebPushOriginKind | null; + installedApp?: boolean; }): Promise { - const {userId, authSessionIdHash, oldEndpoint, endpoint, keys, userAgent} = params; + const {userId, authSessionIdHash, oldEndpoint, endpoint, keys, userAgent, originKind, installedApp} = params; assertPublicPushEndpoint(endpoint, 'endpoint'); const oldSubscriptionId = createWebPushSubscriptionId(oldEndpoint); const newSubscriptionId = createWebPushSubscriptionId(endpoint); @@ -420,7 +503,7 @@ export class UserContentService { app_id: null, provider_environment: null, }; - const subscription = await this.userRepository.createPushSubscription(data); + const subscription = await this.storeWebPushSubscription(data, originKind ?? null, installedApp === true); await this.gatewayService.invalidatePushSubscriptions({userId}); return subscription; } diff --git a/fluxer_api/src/api/user/services/WebPushOriginReplacement.ts b/fluxer_api/src/api/user/services/WebPushOriginReplacement.ts new file mode 100644 index 000000000..90a0e4a3b --- /dev/null +++ b/fluxer_api/src/api/user/services/WebPushOriginReplacement.ts @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider'; +import {seconds} from 'itty-time'; +import {uint8ArrayToBase64} from 'uint8array-extras'; + +export type WebPushOriginKind = 'legacy' | 'target'; + +export type WebPushOriginReplacement = 'installed' | 'browser'; + +const WEB_PUSH_ORIGIN_KINDS: ReadonlyMap = new Map([ + ['https://web.fluxer.app', 'legacy'], + ['https://web.canary.fluxer.app', 'legacy'], + ['https://fluxer.com', 'target'], + ['https://canary.fluxer.com', 'target'], +]); + +const PUSH_ORIGIN_REPLACED_PREFIX = 'push_origin_replaced:'; +const PUSH_SESSION_PREDECESSOR_PREFIX = 'push_session_predecessor:'; +const PUSH_TARGET_SUBSCRIPTION_PREFIX = 'push_target_subscription:'; +const PUSH_INSTALLED_LEGACY_SUBSCRIPTION_PREFIX = 'push_installed_legacy_subscription:'; +const USER_AGENT_VERSION_PATTERN = /\d+(?:[._]\d+)*/g; + +export const WEB_PUSH_ORIGIN_RECORD_TTL_SECONDS = seconds('400 days'); + +export function classifyWebPushOrigin( + origin: string | null | undefined, + selfHosted: boolean, +): WebPushOriginKind | null { + if (selfHosted || !origin) return null; + return WEB_PUSH_ORIGIN_KINDS.get(origin) ?? null; +} + +export function encodePushSessionIdHash(sessionIdHash: Uint8Array): string { + return uint8ArrayToBase64(sessionIdHash, {urlSafe: true}); +} + +export function sameUserAgentFamily(a: string | null | undefined, b: string | null | undefined): boolean { + if (!a || !b) return false; + return a.replace(USER_AGENT_VERSION_PATTERN, '') === b.replace(USER_AGENT_VERSION_PATTERN, ''); +} + +export async function recordPushSessionPredecessor( + kv: IKVProvider, + sessionIdHash: string, + predecessorSessionIdHash: string, +): Promise { + if (sessionIdHash === predecessorSessionIdHash) return; + await kv.setex( + `${PUSH_SESSION_PREDECESSOR_PREFIX}${sessionIdHash}`, + WEB_PUSH_ORIGIN_RECORD_TTL_SECONDS, + predecessorSessionIdHash, + ); +} + +export async function getPushSessionPredecessor(kv: IKVProvider, sessionIdHash: string): Promise { + return kv.get(`${PUSH_SESSION_PREDECESSOR_PREFIX}${sessionIdHash}`); +} + +export async function markPushOriginReplaced( + kv: IKVProvider, + sessionIdHash: string, + replacement: WebPushOriginReplacement, +): Promise { + const key = `${PUSH_ORIGIN_REPLACED_PREFIX}${sessionIdHash}`; + if (replacement === 'browser' && (await kv.get(key)) === 'installed') return; + await kv.setex(key, WEB_PUSH_ORIGIN_RECORD_TTL_SECONDS, replacement); +} + +export async function getPushOriginReplacement( + kv: IKVProvider, + sessionIdHash: string, +): Promise { + const value = await kv.get(`${PUSH_ORIGIN_REPLACED_PREFIX}${sessionIdHash}`); + if (value === null) return null; + return value === 'browser' ? 'browser' : 'installed'; +} + +async function markSubscription(kv: IKVProvider, prefix: string, subscriptionId: string): Promise { + await kv.setex(`${prefix}${subscriptionId}`, WEB_PUSH_ORIGIN_RECORD_TTL_SECONDS, '1'); +} + +async function findMarkedSubscriptionIds( + kv: IKVProvider, + prefix: string, + subscriptionIds: Array, +): Promise> { + if (subscriptionIds.length === 0) return new Set(); + const markers = await kv.mget(...subscriptionIds.map((id) => `${prefix}${id}`)); + return new Set(subscriptionIds.filter((_, index) => markers[index] !== null)); +} + +export async function markTargetPushSubscription(kv: IKVProvider, subscriptionId: string): Promise { + await markSubscription(kv, PUSH_TARGET_SUBSCRIPTION_PREFIX, subscriptionId); +} + +export async function findTargetPushSubscriptionIds( + kv: IKVProvider, + subscriptionIds: Array, +): Promise> { + return findMarkedSubscriptionIds(kv, PUSH_TARGET_SUBSCRIPTION_PREFIX, subscriptionIds); +} + +export async function markInstalledLegacyPushSubscription(kv: IKVProvider, subscriptionId: string): Promise { + await markSubscription(kv, PUSH_INSTALLED_LEGACY_SUBSCRIPTION_PREFIX, subscriptionId); +} + +export async function findInstalledLegacyPushSubscriptionIds( + kv: IKVProvider, + subscriptionIds: Array, +): Promise> { + return findMarkedSubscriptionIds(kv, PUSH_INSTALLED_LEGACY_SUBSCRIPTION_PREFIX, subscriptionIds); +} diff --git a/fluxer_api/src/api/user/tests/PushOriginReplacement.test.ts b/fluxer_api/src/api/user/tests/PushOriginReplacement.test.ts new file mode 100644 index 000000000..2f8a30dd1 --- /dev/null +++ b/fluxer_api/src/api/user/tests/PushOriginReplacement.test.ts @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {createAuthHarness, createTestAccount, loginAccount} from '@app/api/auth/tests/AuthTestUtils'; +import {getConfig} from '@app/api/Config'; +import type {ApiTestHarness} from '@app/api/test/ApiTestHarness'; +import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder'; +import {classifyWebPushOrigin, sameUserAgentFamily} from '@app/api/user/services/WebPushOriginReplacement'; +import {listPushSubscriptions} from '@app/api/user/tests/UserTestUtils'; +import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest'; + +const LEGACY_ORIGIN = 'https://web.fluxer.app'; +const TARGET_ORIGIN = 'https://fluxer.com'; +const IPHONE_UA = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1'; +const IPHONE_UPDATED_UA = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1'; +const DESKTOP_CHROME_UA = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'; +const ANDROID_CHROME_UA = + 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36'; + +interface SubscribeOptions { + userAgent?: string; + installedApp?: boolean; +} + +interface PushSubscribeResponse { + subscription_id: string; +} + +interface HandoffInitiateResponse { + code: string; + poll_secret: string; +} + +interface HandoffStatusResponse { + status: 'pending' | 'completed' | 'expired'; + token?: string; +} + +describe('classifyWebPushOrigin', () => { + it.each([ + {origin: 'https://web.fluxer.app', kind: 'legacy'}, + {origin: 'https://web.canary.fluxer.app', kind: 'legacy'}, + {origin: 'https://fluxer.com', kind: 'target'}, + {origin: 'https://canary.fluxer.com', kind: 'target'}, + {origin: 'https://fluxer.app', kind: null}, + {origin: 'https://example.com', kind: null}, + {origin: undefined, kind: null}, + {origin: null, kind: null}, + ])('classifies $origin as $kind on the official instance', ({origin, kind}) => { + expect(classifyWebPushOrigin(origin, false)).toBe(kind); + }); + + it('never classifies an origin on a self-hosted instance', () => { + expect(classifyWebPushOrigin(LEGACY_ORIGIN, true)).toBeNull(); + expect(classifyWebPushOrigin(TARGET_ORIGIN, true)).toBeNull(); + }); +}); + +describe('sameUserAgentFamily', () => { + it('matches the same browser across version updates', () => { + expect(sameUserAgentFamily(IPHONE_UA, IPHONE_UPDATED_UA)).toBe(true); + }); + + it('tells devices and browsers apart', () => { + expect(sameUserAgentFamily(DESKTOP_CHROME_UA, ANDROID_CHROME_UA)).toBe(false); + expect(sameUserAgentFamily(IPHONE_UA, DESKTOP_CHROME_UA)).toBe(false); + }); + + it('never matches a missing user agent', () => { + expect(sameUserAgentFamily(null, null)).toBe(false); + expect(sameUserAgentFamily(IPHONE_UA, undefined)).toBe(false); + }); +}); + +describe('web push origin replacement', () => { + let harness: ApiTestHarness; + beforeAll(async () => { + harness = await createAuthHarness(); + }); + beforeEach(async () => { + await harness.reset(); + }); + afterAll(async () => { + await harness?.shutdown(); + }); + afterEach(() => { + vi.restoreAllMocks(); + }); + + async function subscribeFrom( + token: string, + origin: string | null, + endpoint: string, + options: SubscribeOptions = {}, + ): Promise { + const builder = createBuilder(harness, token).post('/users/@me/push/subscribe'); + if (origin) builder.header('Origin', origin); + const response = await builder + .body({ + endpoint, + keys: {p256dh: 'test-p256dh-key', auth: 'test-auth-key'}, + user_agent: options.userAgent, + installed_app: options.installedApp, + }) + .execute(); + return response.subscription_id; + } + + async function rotateFrom( + token: string, + origin: string, + oldEndpoint: string, + endpoint: string, + installedApp?: boolean, + ): Promise { + const response = await createBuilder(harness, token) + .post('/users/@me/push/rotate') + .header('Origin', origin) + .body({ + old_endpoint: oldEndpoint, + endpoint, + keys: {p256dh: 'test-p256dh-key', auth: 'test-auth-key'}, + installed_app: installedApp, + }) + .execute(); + return response.subscription_id; + } + + async function listSubscriptionIds(token: string): Promise> { + const result = await listPushSubscriptions(harness, token); + return result.subscriptions.map((subscription) => subscription.subscription_id).sort(); + } + + async function pairNewSession( + approverToken: string, + approverUserId: string, + approverOrigin: string, + initiatorOrigin: string | null = TARGET_ORIGIN, + ) { + const initiate = createBuilderWithoutAuth(harness).post('/auth/handoff/initiate'); + if (initiatorOrigin) initiate.header('Origin', initiatorOrigin); + const initiated = await initiate.body(null).execute(); + await createBuilderWithoutAuth(harness).get(`/auth/handoff/${initiated.code}/info`).execute(); + await createBuilderWithoutAuth(harness) + .post('/auth/handoff/complete') + .header('Origin', approverOrigin) + .body({code: initiated.code, token: approverToken, user_id: approverUserId}) + .expect(204) + .execute(); + const completed = await createBuilderWithoutAuth(harness) + .post(`/auth/handoff/${initiated.code}/status`) + .body({poll_secret: initiated.poll_secret}) + .execute(); + expect(completed.status).toBe('completed'); + return completed.token!; + } + + async function withSelfHosted(callback: () => Promise): Promise { + const config = getConfig(); + const original = config.instance.selfHosted; + try { + config.instance.selfHosted = true; + await callback(); + } finally { + config.instance.selfHosted = original; + } + } + + it('replaces the legacy subscription of the same session when the new origin subscribes', async () => { + const account = await createTestAccount(harness); + await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy'); + const target = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target'); + expect(await listSubscriptionIds(account.token)).toEqual([target]); + }); + + it('turns a later legacy subscribe for the replaced session into a no-op', async () => { + const account = await createTestAccount(harness); + const target = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target'); + const legacy = await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy'); + expect(legacy).toMatch(/^[a-f0-9]{32}$/); + expect(legacy).not.toBe(target); + expect(await listSubscriptionIds(account.token)).toEqual([target]); + }); + + it('does not store a legacy rotation for a replaced session', async () => { + const account = await createTestAccount(harness); + await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy-old'); + const target = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target'); + await rotateFrom( + account.token, + LEGACY_ORIGIN, + 'https://push.example.com/legacy-old', + 'https://push.example.com/legacy-new', + ); + expect(await listSubscriptionIds(account.token)).toEqual([target]); + }); + + it('keeps legacy subscriptions working until the new origin subscribes', async () => { + const account = await createTestAccount(harness); + const first = await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy-a'); + const second = await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy-b'); + expect(await listSubscriptionIds(account.token)).toEqual([first, second].sort()); + }); + + it('leaves subscriptions from other sessions alone', async () => { + const account = await createTestAccount(harness); + const other = await loginAccount(harness, account); + const otherLegacy = await subscribeFrom(other.token, LEGACY_ORIGIN, 'https://push.example.com/other-legacy'); + const target = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target'); + expect(await listSubscriptionIds(account.token)).toEqual([otherLegacy, target].sort()); + }); + + it('never removes another new-origin subscription of the same session', async () => { + const account = await createTestAccount(harness); + const first = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target-a'); + const second = await subscribeFrom(account.token, 'https://canary.fluxer.com', 'https://push.example.com/target-b'); + expect(await listSubscriptionIds(account.token)).toEqual([first, second].sort()); + }); + + it('treats unclassified rows as legacy without ever skipping an unclassified subscribe', async () => { + const account = await createTestAccount(harness); + const unknown = await subscribeFrom(account.token, null, 'https://push.example.com/no-origin'); + const target = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target'); + expect(await listSubscriptionIds(account.token)).toEqual([target]); + const legacyAfter = await subscribeFrom(account.token, null, 'https://push.example.com/no-origin'); + expect(legacyAfter).toBe(unknown); + expect(await listSubscriptionIds(account.token)).toEqual([unknown, target].sort()); + }); + + it('replaces the approving legacy session on the same device once a paired session subscribes', async () => { + const account = await createTestAccount(harness); + const approver = await loginAccount(harness, account); + const approverLegacy = 'https://push.example.com/approver-legacy'; + await subscribeFrom(approver.token, LEGACY_ORIGIN, approverLegacy, {userAgent: IPHONE_UA, installedApp: true}); + const pairedToken = await pairNewSession(approver.token, approver.userId, LEGACY_ORIGIN); + const paired = await subscribeFrom(pairedToken, TARGET_ORIGIN, 'https://push.example.com/paired', { + userAgent: IPHONE_UPDATED_UA, + installedApp: true, + }); + expect(await listSubscriptionIds(approver.token)).toEqual([paired]); + }); + + it('never silences the approving session for good', async () => { + const account = await createTestAccount(harness); + const approver = await loginAccount(harness, account); + const approverLegacy = 'https://push.example.com/approver-legacy'; + await subscribeFrom(approver.token, LEGACY_ORIGIN, approverLegacy, {userAgent: IPHONE_UA}); + const pairedToken = await pairNewSession(approver.token, approver.userId, LEGACY_ORIGIN); + const paired = await subscribeFrom(pairedToken, TARGET_ORIGIN, 'https://push.example.com/paired', { + userAgent: IPHONE_UA, + }); + const restored = await subscribeFrom(approver.token, LEGACY_ORIGIN, approverLegacy, {userAgent: IPHONE_UA}); + expect(await listSubscriptionIds(approver.token)).toEqual([paired, restored].sort()); + }); + + it('leaves the approving session alone when it runs on another device', async () => { + const account = await createTestAccount(harness); + const approver = await loginAccount(harness, account); + const approverLegacy = await subscribeFrom(approver.token, LEGACY_ORIGIN, 'https://push.example.com/desktop', { + userAgent: DESKTOP_CHROME_UA, + installedApp: true, + }); + const pairedToken = await pairNewSession(approver.token, approver.userId, LEGACY_ORIGIN); + const paired = await subscribeFrom(pairedToken, TARGET_ORIGIN, 'https://push.example.com/phone', { + userAgent: ANDROID_CHROME_UA, + installedApp: true, + }); + expect(await listSubscriptionIds(approver.token)).toEqual([approverLegacy, paired].sort()); + const desktopAgain = await subscribeFrom(approver.token, LEGACY_ORIGIN, 'https://push.example.com/desktop', { + userAgent: DESKTOP_CHROME_UA, + installedApp: true, + }); + expect(desktopAgain).toBe(approverLegacy); + expect(await listSubscriptionIds(approver.token)).toEqual([approverLegacy, paired].sort()); + }); + + it.each([ + {label: 'the approval came from the new origin', approverOrigin: TARGET_ORIGIN, initiatorOrigin: TARGET_ORIGIN}, + {label: 'the new session did not start on the new origin', approverOrigin: LEGACY_ORIGIN, initiatorOrigin: null}, + { + label: 'the new session started on the old origin', + approverOrigin: LEGACY_ORIGIN, + initiatorOrigin: LEGACY_ORIGIN, + }, + ])('does not link sessions when $label', async ({approverOrigin, initiatorOrigin}) => { + const account = await createTestAccount(harness); + const approver = await loginAccount(harness, account); + const approverSubscription = await subscribeFrom( + approver.token, + LEGACY_ORIGIN, + 'https://push.example.com/approver-legacy', + {userAgent: IPHONE_UA}, + ); + const pairedToken = await pairNewSession(approver.token, approver.userId, approverOrigin, initiatorOrigin); + const paired = await subscribeFrom(pairedToken, TARGET_ORIGIN, 'https://push.example.com/paired', { + userAgent: IPHONE_UA, + }); + expect(await listSubscriptionIds(approver.token)).toEqual([approverSubscription, paired].sort()); + }); + + it('completes the approval when the predecessor link cannot be written', async () => { + const account = await createTestAccount(harness); + const approver = await loginAccount(harness, account); + const setex = harness.kvProvider.setex.bind(harness.kvProvider); + vi.spyOn(harness.kvProvider, 'setex').mockImplementation(async (key, ttl, value) => { + if (key.startsWith('push_session_predecessor:')) throw new Error('kv down'); + return setex(key, ttl, value); + }); + const pairedToken = await pairNewSession(approver.token, approver.userId, LEGACY_ORIGIN); + expect(pairedToken).toBeTruthy(); + }); + + it('stores a subscribe when the replacement marker cannot be read or written', async () => { + const account = await createTestAccount(harness); + const get = harness.kvProvider.get.bind(harness.kvProvider); + vi.spyOn(harness.kvProvider, 'get').mockImplementation(async (key) => { + if (key.startsWith('push_origin_replaced:')) throw new Error('kv down'); + return get(key); + }); + const target = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target'); + const legacy = await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy'); + expect(await listSubscriptionIds(account.token)).toEqual([legacy, target].sort()); + }); + + it('keeps an installed legacy app subscribed when only a browser tab moved', async () => { + const account = await createTestAccount(harness); + const installed = await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy-app', { + userAgent: DESKTOP_CHROME_UA, + installedApp: true, + }); + const target = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target-tab', { + userAgent: DESKTOP_CHROME_UA, + }); + expect(await listSubscriptionIds(account.token)).toEqual([installed, target].sort()); + const rotated = await rotateFrom( + account.token, + LEGACY_ORIGIN, + 'https://push.example.com/legacy-app', + 'https://push.example.com/legacy-app-2', + true, + ); + expect(await listSubscriptionIds(account.token)).toEqual([rotated, target].sort()); + await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy-tab', { + userAgent: DESKTOP_CHROME_UA, + }); + expect(await listSubscriptionIds(account.token)).toEqual([rotated, target].sort()); + }); + + it('replaces an installed legacy app once the new app is installed', async () => { + const account = await createTestAccount(harness); + await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy-app', { + userAgent: DESKTOP_CHROME_UA, + installedApp: true, + }); + await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target-tab', { + userAgent: DESKTOP_CHROME_UA, + }); + const targetApp = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target-app', { + userAgent: DESKTOP_CHROME_UA, + installedApp: true, + }); + const ids = await listSubscriptionIds(account.token); + expect(ids).toContain(targetApp); + expect(ids).toHaveLength(2); + await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy-app', { + userAgent: DESKTOP_CHROME_UA, + installedApp: true, + }); + expect(await listSubscriptionIds(account.token)).toEqual(ids); + }); + + it('does nothing new on a self-hosted instance', async () => { + await withSelfHosted(async () => { + const account = await createTestAccount(harness); + const legacy = await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy'); + const target = await subscribeFrom(account.token, TARGET_ORIGIN, 'https://push.example.com/target'); + const legacyAgain = await subscribeFrom(account.token, LEGACY_ORIGIN, 'https://push.example.com/legacy-2'); + expect(await listSubscriptionIds(account.token)).toEqual([legacy, target, legacyAgain].sort()); + }); + }); +}); diff --git a/fluxer_api/src/api/utils/InviteUtils.ts b/fluxer_api/src/api/utils/InviteUtils.ts index be0969fcb..26960b45f 100644 --- a/fluxer_api/src/api/utils/InviteUtils.ts +++ b/fluxer_api/src/api/utils/InviteUtils.ts @@ -10,6 +10,11 @@ function getInviteEndpointBase(): string { return `${url.hostname}${url.pathname.replace(/\/+$/, '')}`; } +function getWebAppHostsPattern(): string { + const hostnames = new Set(Config.endpoints.webAppOrigins.map((origin) => new URL(origin).hostname)); + return [...hostnames].map((hostname) => RegexUtils.escapeRegex(hostname)).join('|'); +} + function getInvitePattern(): RegExp { if (!_invitePattern) { _invitePattern = new RegExp( @@ -18,7 +23,7 @@ function getInvitePattern(): RegExp { '(?:', `${RegexUtils.escapeRegex(getInviteEndpointBase())}(?:\\/#)?\\/(?!invite\\/)([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`, '|', - `${RegexUtils.escapeRegex(new URL(Config.endpoints.webApp).hostname)}(?:\\/#)?\\/invite\\/([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`, + `(?:${getWebAppHostsPattern()})(?:\\/#)?\\/invite\\/([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`, ')', ].join(''), 'gi', diff --git a/fluxer_api/src/api/utils/UnfurlerUtils.ts b/fluxer_api/src/api/utils/UnfurlerUtils.ts index 2cfd7f598..1f8388d00 100644 --- a/fluxer_api/src/api/utils/UnfurlerUtils.ts +++ b/fluxer_api/src/api/utils/UnfurlerUtils.ts @@ -8,7 +8,7 @@ import * as InviteUtils from '@app/api/utils/InviteUtils'; import {URL_REGEX} from '@fluxer/constants/src/Core'; import * as idna from 'idna-uts46-hx'; -const CLIENT_ROUTE_PATH_PREFIXES = ['/channels/', '/theme/']; +const CLIENT_ROUTE_PATH_PREFIXES = ['/channels/', '/theme/', '/invite/', '/gift/', '/oauth2/', '/users/']; interface ExcludedLinkBase { hostname: string; @@ -19,12 +19,14 @@ function normalizeHostname(hostname: string | undefined) { return hostname?.trim().toLowerCase() || ''; } -function getWebAppHostname() { - try { - return new URL(Config.endpoints.webApp).hostname; - } catch { - return ''; - } +function getWebAppHostnames(): Array { + return Config.endpoints.webAppOrigins.flatMap((origin) => { + try { + return [new URL(origin).hostname]; + } catch { + return []; + } + }); } function endpointLinkBase(endpoint: string): ExcludedLinkBase | null { @@ -45,7 +47,7 @@ function getExcludedLinkBases(): Array { endpointLinkBase(Config.endpoints.invite), endpointLinkBase(Config.endpoints.gift), ]; - for (const hostname of [getWebAppHostname(), Config.hosts.marketing]) { + for (const hostname of [...getWebAppHostnames(), Config.hosts.marketing]) { for (const pathPrefix of CLIENT_ROUTE_PATH_PREFIXES) { bases.push({hostname: normalizeHostname(hostname), pathPrefix}); } diff --git a/fluxer_app/scripts/build/rspack/static-files.mjs b/fluxer_app/scripts/build/rspack/static-files.mjs index cd938d69a..e2351e4b0 100644 --- a/fluxer_app/scripts/build/rspack/static-files.mjs +++ b/fluxer_app/scripts/build/rspack/static-files.mjs @@ -35,7 +35,8 @@ function generateManifest(staticCdnEndpoint) { short_name: 'Fluxer', description: 'Fluxer is a free and open source instant messaging and VoIP platform built for friends, groups, and communities.', - start_url: '/', + id: '/', + start_url: '/app', display: 'standalone', orientation: 'portrait-primary', theme_color: '#4641D9', @@ -43,6 +44,7 @@ function generateManifest(staticCdnEndpoint) { categories: ['social', 'communication'], lang: 'en', scope: '/', + scope_extensions: [], icons: [ { src: `${cdn}/web/android-chrome-192x192.png`, diff --git a/fluxer_app/src/app/Routes.ts b/fluxer_app/src/app/Routes.ts index 5f7082c4f..65a4ca7b7 100644 --- a/fluxer_app/src/app/Routes.ts +++ b/fluxer_app/src/app/Routes.ts @@ -4,6 +4,7 @@ import {marketingUrl} from '@app/features/messaging/utils/MessagingUrlUtils'; export const Routes = { HOME: '/', + APP: '/app', LOGIN: '/login', REGISTER: '/register', FORGOT_PASSWORD: '/forgot', diff --git a/fluxer_app/src/app/router/components/RootComponent.tsx b/fluxer_app/src/app/router/components/RootComponent.tsx index 017a04da4..03434d530 100644 --- a/fluxer_app/src/app/router/components/RootComponent.tsx +++ b/fluxer_app/src/app/router/components/RootComponent.tsx @@ -214,7 +214,7 @@ export const RootComponent: React.FC<{children?: React.ReactNode}> = observer(({ ) { return; } - if (location.pathname === Routes.HOME) { + if (location.pathname === Routes.HOME || location.pathname === Routes.APP) { return; } hasStartedRestoreRef.current = true; diff --git a/fluxer_app/src/app/router/routes/RootRoutes.tsx b/fluxer_app/src/app/router/routes/RootRoutes.tsx index ab6fbf303..4ecad96ac 100644 --- a/fluxer_app/src/app/router/routes/RootRoutes.tsx +++ b/fluxer_app/src/app/router/routes/RootRoutes.tsx @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +import {Routes} from '@app/app/Routes'; import {RootComponent} from '@app/app/router/components/RootComponent'; import {NotFoundPage} from '@app/features/app/components/pages/NotFoundPage'; import {getDefaultLandingPath} from '@app/features/navigation/utils/DefaultLandingUtils'; @@ -22,3 +23,9 @@ export const homeRoute = createRoute({ path: '/', onEnter: () => new Redirect(getDefaultLandingPath()), }); +export const appRoute = createRoute({ + getParentRoute: () => rootRoute, + id: 'app', + path: Routes.APP, + onEnter: () => new Redirect(getDefaultLandingPath()), +}); diff --git a/fluxer_app/src/app/router/routes/RouterRoutes.ts b/fluxer_app/src/app/router/routes/RouterRoutes.ts index a3fe1ec91..cbf4ebc1e 100644 --- a/fluxer_app/src/app/router/routes/RouterRoutes.ts +++ b/fluxer_app/src/app/router/routes/RouterRoutes.ts @@ -7,11 +7,12 @@ import { premiumCallbackRoute, } from '@app/app/router/routes/AppRoutes'; import {authRouteTree} from '@app/app/router/routes/AuthRoutes'; -import {homeRoute, notFoundRoute, rootRoute} from '@app/app/router/routes/RootRoutes'; +import {appRoute, homeRoute, notFoundRoute, rootRoute} from '@app/app/router/routes/RootRoutes'; import type {RouteConfig} from '@app/features/platform/components/router/RouterTypes'; const routeTree = rootRoute.addChildren([ homeRoute, + appRoute, notFoundRoute, premiumCallbackRoute, matureContentCheckCallbackRoute, diff --git a/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx b/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx index 2946470ae..8ad336ff5 100644 --- a/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx +++ b/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx @@ -1287,6 +1287,7 @@ const SKELETON_NAGBAR_ROW_SHAPES: Record = { [NagbarType.LINUX_INPUT_ACCESS]: {tone: SkeletonNagbarTone.BRAND, hasActions: true}, [NagbarType.SOFTWARE_ENCODER]: {tone: SkeletonNagbarTone.ENCODER, hasActions: true}, [NagbarType.STREAMER_MODE]: {tone: SkeletonNagbarTone.STREAMER, hasActions: true}, + [NagbarType.DOMAIN_MOVED]: {tone: SkeletonNagbarTone.BRAND, hasActions: true}, }; const CONNECTION_SKELETON_NAGBAR_TONES: Record = { diff --git a/fluxer_app/src/features/app/components/layout/app_layout/AppLayoutHooks.ts b/fluxer_app/src/features/app/components/layout/app_layout/AppLayoutHooks.ts index 96f65186f..e4f044325 100644 --- a/fluxer_app/src/features/app/components/layout/app_layout/AppLayoutHooks.ts +++ b/fluxer_app/src/features/app/components/layout/app_layout/AppLayoutHooks.ts @@ -8,6 +8,7 @@ import { } from '@app/features/app/components/layout/app_layout/AppLayoutTypes'; import {isScheduledMaintenanceNagbarDismissed} from '@app/features/app/components/layout/app_layout/ScheduledMaintenanceDismissal'; import Config from '@app/features/app/config/Config'; +import DomainMovedNotice from '@app/features/app/domain_migration/DomainMovedNotice'; import {isClientReconnecting} from '@app/features/app/state/ClientReadiness'; import Initialization from '@app/features/app/state/Initialization'; import RuntimeConfig from '@app/features/app/state/RuntimeConfig'; @@ -255,6 +256,11 @@ export const useNagbarConditions = (): NagbarConditions => { const canShowSoftwareEncoder = SoftwareEncoderWarning.showWarning; const canShowStreamerMode = StreamerMode.shouldShowNagbar; const canShowDesktopUpdateReady = Updater.shouldShowUpdateReadyNagbar; + const canShowDomainMoved = nagbarState.forceHideDomainMoved + ? false + : nagbarState.forceDomainMoved + ? true + : DomainMovedNotice.shouldShow(Date.now()); const canShowBuildEnvironment = !BUILD_ENVIRONMENT_HIDDEN_RELEASE_CHANNELS.has(Config.PUBLIC_RELEASE_CHANNEL) && !nagbarState.buildEnvironmentDismissedThisSession; @@ -316,6 +322,7 @@ export const useNagbarConditions = (): NagbarConditions => { canShowSoftwareEncoder, canShowStreamerMode, canShowDesktopUpdateReady, + canShowDomainMoved, }; }; export const useActiveNagbars = (conditions: NagbarConditions): Array => { @@ -453,6 +460,12 @@ export const useActiveNagbars = (conditions: NagbarConditions): Array; @@ -63,4 +64,5 @@ export interface NagbarConditions { canShowLinuxInputAccess: boolean; canShowSoftwareEncoder: boolean; canShowStreamerMode: boolean; + canShowDomainMoved: boolean; } diff --git a/fluxer_app/src/features/app/components/layout/app_layout/NagbarContainer.tsx b/fluxer_app/src/features/app/components/layout/app_layout/NagbarContainer.tsx index 021904ac6..21ebc2d09 100644 --- a/fluxer_app/src/features/app/components/layout/app_layout/NagbarContainer.tsx +++ b/fluxer_app/src/features/app/components/layout/app_layout/NagbarContainer.tsx @@ -8,6 +8,7 @@ import {CorruptedInstallationNagbar} from '@app/features/app/components/layout/a import {DesktopDownloadNagbar} from '@app/features/app/components/layout/app_layout/nagbars/DesktopDownloadNagbar'; import {DesktopNotificationNagbar} from '@app/features/app/components/layout/app_layout/nagbars/DesktopNotificationNagbar'; import {DesktopUpdateReadyNagbar} from '@app/features/app/components/layout/app_layout/nagbars/DesktopUpdateReadyNagbar'; +import {DomainMovedNagbar} from '@app/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar'; import {EmailVerificationNagbar} from '@app/features/app/components/layout/app_layout/nagbars/EmailVerificationNagbar'; import {GiftInventoryNagbar} from '@app/features/app/components/layout/app_layout/nagbars/GiftInventoryNagbar'; import {GuildMembershipCtaNagbar} from '@app/features/app/components/layout/app_layout/nagbars/GuildMembershipCtaNagbar'; @@ -230,6 +231,14 @@ export const NagbarContainer: React.FC = observer(({nagbar data-flx="app.app-layout.nagbar-container.streamer-mode-nagbar" /> ); + case NagbarType.DOMAIN_MOVED: + return ( + + ); default: throw new UnexpectedNagbarTypeError(nagbar.type); } diff --git a/fluxer_app/src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx b/fluxer_app/src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx new file mode 100644 index 000000000..e8e474ecb --- /dev/null +++ b/fluxer_app/src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {Routes} from '@app/app/Routes'; +import {Nagbar} from '@app/features/app/components/layout/Nagbar'; +import {NagbarButton} from '@app/features/app/components/layout/NagbarButton'; +import {NagbarContent} from '@app/features/app/components/layout/NagbarContent'; +import {NAGBAR_TONES, NagbarToneKind} from '@app/features/app/components/layout/NagbarTones'; +import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; +import { + installDomainMovedApp, + openDomainMovedBrowserMigration, +} from '@app/features/app/domain_migration/DomainMigrationBrowser'; +import type {DomainMigrationInstallKind} from '@app/features/app/domain_migration/DomainMigrationCore'; +import DomainMovedNotice from '@app/features/app/domain_migration/DomainMovedNotice'; +import { + type DomainMovedStepsPlatform, + showDomainMovedStepsModal, +} from '@app/features/app/domain_migration/DomainMovedStepsModal'; +import * as RouterUtils from '@app/features/navigation/utils/RouterUtils'; +import {isIOSMobileOrTabletUserAgent} from '@app/features/platform/notifications/NotificationAlertOptions'; +import {msg} from '@lingui/core/macro'; +import {useLingui} from '@lingui/react/macro'; +import {observer} from 'mobx-react-lite'; +import {useCallback} from 'react'; + +type DomainMovedPresentation = 'install' | 'browser' | 'apple' | 'generic'; + +const INSTALL_MESSAGE_DESCRIPTOR = msg({ + message: '{productName} has moved to {host}. Install the new app and you will already be signed in.', + comment: + 'Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com.', +}); +const BROWSER_MESSAGE_DESCRIPTOR = msg({ + message: '{productName} has moved to {host}. Open it in your browser to install the new app.', + comment: + 'Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com.', +}); +const APPLE_MESSAGE_DESCRIPTOR = msg({ + message: '{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app.', + comment: + 'Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names.', +}); +const GENERIC_MESSAGE_DESCRIPTOR = msg({ + message: '{productName} has moved to {host}. Install it from your browser, then sign in with this app.', + comment: + 'Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com.', +}); +const INSTALL_NEW_APP_DESCRIPTOR = msg({ + message: 'Install the new app', + comment: 'Button on the domain moved banner that installs the app from the new domain.', +}); +const OPEN_IN_BROWSER_DESCRIPTOR = msg({ + message: 'Open {productName} in your browser', + comment: 'Button on the domain moved banner that opens the new domain in the browser. productName is the app name.', +}); +const SHOW_ME_HOW_DESCRIPTOR = msg({ + message: 'Show me how', + comment: 'Button on the domain moved banner that opens the install steps.', +}); +const LINK_NEW_DEVICE_DESCRIPTOR = msg({ + message: 'Link a new device', + comment: + 'Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions.', +}); + +const MESSAGE_DESCRIPTORS = { + install: INSTALL_MESSAGE_DESCRIPTOR, + browser: BROWSER_MESSAGE_DESCRIPTOR, + apple: APPLE_MESSAGE_DESCRIPTOR, + generic: GENERIC_MESSAGE_DESCRIPTOR, +} as const; + +function presentationFor(installKind: DomainMigrationInstallKind): DomainMovedPresentation { + switch (installKind) { + case 'chromium-desktop': + return 'install'; + case 'chromium-android': + return 'browser'; + case 'webkit': + case 'none': + return 'apple'; + case 'firefox': + case 'other': + return 'generic'; + } +} + +function stepsPlatform(presentation: DomainMovedPresentation): DomainMovedStepsPlatform { + if (presentation !== 'apple') { + return 'generic'; + } + return isIOSMobileOrTabletUserAgent(navigator.userAgent, navigator.maxTouchPoints) ? 'ios' : 'mac'; +} + +export const DomainMovedNagbar = observer(({isMobile}: {isMobile: boolean}) => { + const {i18n} = useLingui(); + const target = DomainMovedNotice.target; + const presentation = presentationFor(DomainMovedNotice.installKind); + const handleDismiss = useCallback(() => { + DomainMovedNotice.dismiss(Date.now()); + }, []); + const handleInstall = useCallback(() => { + installDomainMovedApp(target, () => showDomainMovedStepsModal(target, 'install')); + }, [target]); + const handleOpenInBrowser = useCallback(() => { + openDomainMovedBrowserMigration(target); + }, [target]); + const handleShowSteps = useCallback(() => { + showDomainMovedStepsModal(target, stepsPlatform(presentation)); + }, [presentation, target]); + const handleLinkDevice = useCallback(() => { + RouterUtils.transitionTo(`${Routes.LOGIN}?handoff=1`); + }, []); + const tone = NAGBAR_TONES[NagbarToneKind.BRAND]; + const values = {productName: PRODUCT_NAME, host: DomainMovedNotice.targetHost}; + const linkDeviceButton = ( + + {i18n._(LINK_NEW_DEVICE_DESCRIPTOR)} + + ); + return ( + + + {i18n._(INSTALL_NEW_APP_DESCRIPTOR)} + + ) : presentation === 'browser' ? ( + <> + {linkDeviceButton} + + {i18n._(OPEN_IN_BROWSER_DESCRIPTOR, values)} + + + ) : ( + <> + {linkDeviceButton} + + {i18n._(SHOW_ME_HOW_DESCRIPTOR)} + + + ) + } + data-flx="app.app-layout.nagbars.domain-moved-nagbar.nagbar-content" + /> + + ); +}); diff --git a/fluxer_app/src/features/app/domain_migration/DomainMigration.test.ts b/fluxer_app/src/features/app/domain_migration/DomainMigration.test.ts new file mode 100644 index 000000000..f76991dd3 --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMigration.test.ts @@ -0,0 +1,818 @@ +// @vitest-environment happy-dom +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + detectDomainMigrationInstallKind, + installDomainMovedApp, +} from '@app/features/app/domain_migration/DomainMigrationBrowser'; +import * as core from '@app/features/app/domain_migration/DomainMigrationCore'; +import { + decryptDomainMigrationPayload, + encryptDomainMigrationPayload, +} from '@app/features/app/domain_migration/DomainMigrationCrypto'; +import {runDomainMigrationPreMount} from '@app/features/app/domain_migration/DomainMigrationPreMount'; +import type {RuntimeConfigSnapshot} from '@app/features/app/state/RuntimeConfig'; +import type {StoredAccount} from '@app/features/auth/state/AccountStorage'; +import type {DomainMigrationDiscoveryResponse} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; +import {experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket'; +import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; + +vi.mock('@app/features/platform/utils/AppLogger', () => ({ + Logger: class { + debug = vi.fn(); + info = vi.fn(); + warn = vi.fn(); + error = vi.fn(); + }, +})); + +vi.mock('@app/features/auth/state/AccountStorage', () => ({ + default: {getAllAccounts: async () => []}, +})); + +const ENABLED_DISCOVERY: DomainMigrationDiscoveryResponse = { + enabled: true, + anonymous_rollout_basis_points: 0, + rollout_salt: 'domain-migration-v1', + standalone_forwarding: false, +}; + +const NOW = 1_800_000_000_000; + +function memoryStorage(initial: Record = {}): core.StorageLike { + const entries = new Map(Object.entries(initial)); + return { + getItem: (key) => entries.get(key) ?? null, + setItem: (key, value) => { + entries.set(key, value); + }, + removeItem: (key) => { + entries.delete(key); + }, + key: (index) => [...entries.keys()][index] ?? null, + get length() { + return entries.size; + }, + }; +} + +function environment( + installKind: core.DomainMigrationInstallKind, + overrides: Partial = {}, +): core.DomainMigrationEnvironment { + return {installKind, electron: false, electronMigrationVersion: null, ...overrides}; +} + +function gateInput(overrides: Partial = {}): core.DomainMigrationGateInput { + return { + environment: environment('none'), + assignmentEnabled: true, + discovery: ENABLED_DISCOVERY, + marker: null, + now: NOW, + relatedOriginsSupported: true, + voiceActive: false, + oneShotRoute: false, + ...overrides, + }; +} + +describe('sanitizeNextPath', () => { + it.each([ + ['/channels/123/456?x=1#y', '/channels/123/456?x=1#y'], + ['/reset#token=abc', '/reset#token=abc'], + ['/', '/app'], + ['/?a=1', '/app?a=1'], + ['//evil.example', '/channels/@me'], + ['/\\evil.example', '/channels/@me'], + ['/migrate/begin', '/channels/@me'], + ['/migrate', '/channels/@me'], + ['/\t/evil.example', '/channels/@me'], + ['/\n/evil.example', '/channels/@me'], + ['/\r/evil.example', '/channels/@me'], + ['/\tmigrate/done', '/channels/@me'], + ['/channels/\u0000', '/channels/@me'], + ['/a/../migrate/done', '/channels/@me'], + ['/%09/evil.example', '/%09/evil.example'], + ['https://evil.example/', '/channels/@me'], + ['channels/@me', '/channels/@me'], + [null, '/channels/@me'], + [42, '/channels/@me'], + ])('maps %j to %j', (input, expected) => { + expect(core.sanitizeNextPath(input)).toBe(expected); + }); + + it('never leaves the origin it is resolved against', () => { + for (const input of ['/\t/evil.example', '/\n/evil.example', '/%09/evil.example', '/@evil.example']) { + expect(new URL(core.sanitizeNextPath(input), 'https://web.fluxer.app/x').origin).toBe('https://web.fluxer.app'); + } + }); +}); + +describe('resolveDomainMigrationSide', () => { + it('recognises only the four official origins', () => { + expect(core.resolveDomainMigrationSide('https://web.fluxer.app')).toEqual({ + role: 'source', + source: 'https://web.fluxer.app', + target: 'https://fluxer.com', + }); + expect(core.resolveDomainMigrationSide('https://canary.fluxer.com')).toEqual({ + role: 'target', + source: 'https://web.canary.fluxer.app', + target: 'https://canary.fluxer.com', + }); + for (const origin of [ + 'http://localhost:3000', + 'https://chat.example.com', + 'http://web.fluxer.app', + 'https://fluxer.app', + 'https://web.fluxer.com', + ]) { + expect(core.resolveDomainMigrationSide(origin)).toBeNull(); + } + }); +}); + +describe('payload encryption', () => { + it('round trips through encrypt and decrypt', async () => { + const payload = { + version: 1, + source_origin: 'https://web.fluxer.app', + local_storage: {token: 'abc', big: 'x'.repeat(200_000)}, + }; + const sealed = await encryptDomainMigrationPayload(payload); + expect(sealed.payload).toMatch(/^[A-Za-z0-9_-]+$/u); + expect(sealed.key).toMatch(/^[A-Za-z0-9_-]{43}$/u); + expect(sealed.payload.length).toBeLessThan(200_000); + expect(await decryptDomainMigrationPayload(sealed.payload, sealed.key)).toEqual(payload); + }); + + it('rejects a payload opened with another key', async () => { + const sealed = await encryptDomainMigrationPayload({version: 1}); + const other = await encryptDomainMigrationPayload({version: 1}); + await expect(decryptDomainMigrationPayload(sealed.payload, other.key)).rejects.toThrow(); + }); + + it('rejects payloads from another source or version', () => { + const payload = { + version: 1, + source_origin: 'https://web.fluxer.app', + exported_at: NOW, + local_storage: {}, + accounts: [], + notification_permission: 'granted', + }; + expect(core.parseDomainMigrationPayload(payload, 'https://web.fluxer.app')).not.toBeNull(); + expect(core.parseDomainMigrationPayload(payload, 'https://web.canary.fluxer.app')).toBeNull(); + expect(core.parseDomainMigrationPayload({...payload, version: 2}, 'https://web.fluxer.app')).toBeNull(); + expect( + core.parseDomainMigrationPayload({...payload, accounts: [{userId: 1}]}, 'https://web.fluxer.app'), + ).toBeNull(); + }); + + it('accepts a theme library and rejects a malformed one', () => { + const payload = { + version: 1, + source_origin: 'https://web.fluxer.app', + exported_at: NOW, + local_storage: {}, + accounts: [], + notification_permission: 'default', + }; + const themeLibrary: core.DomainMigrationThemeLibrary = { + themes: [{id: 'quick-css', css: 'body{}'}], + assets: [ + { + id: 'asset', + name: 'bg.png', + mime_type: 'image/png', + size: 3, + data: 'AQID', + created_at: NOW, + updated_at: NOW, + }, + ], + local_files: [], + enabled_theme_ids: ['quick-css'], + }; + expect( + core.parseDomainMigrationPayload({...payload, theme_library: themeLibrary}, 'https://web.fluxer.app'), + ).not.toBeNull(); + expect( + core.parseDomainMigrationPayload( + {...payload, theme_library: {...themeLibrary, enabled_theme_ids: [1]}}, + 'https://web.fluxer.app', + ), + ).toBeNull(); + const trimmed = core.withoutOptionalPayloadData({ + ...payload, + version: 1, + custom_sounds: [], + theme_library: themeLibrary, + }); + expect(trimmed.custom_sounds).toBeUndefined(); + expect(trimmed.theme_library).toEqual({...themeLibrary, assets: []}); + }); +}); + +describe('rewriteImportedAccount', () => { + it('points the account at the current instance and drops runtimeConfig', () => { + const current = {apiEndpoint: 'https://fluxer.com/api'} as RuntimeConfigSnapshot; + const record: StoredAccount = { + userId: '1', + token: 'token', + localStorageData: {runtimeConfig: '{}', token: 'token'}, + managedStorageData: {runtimeConfig: '{}', token: 'token', 'fluxer.theme': 'dark'}, + lastActive: NOW, + instance: {apiEndpoint: 'https://web.fluxer.app/api'} as RuntimeConfigSnapshot, + }; + const rewritten = core.rewriteImportedAccount(record, current); + expect(rewritten.instance).toBe(current); + expect(rewritten.managedStorageData).toEqual({token: 'token', 'fluxer.theme': 'dark'}); + expect(rewritten.localStorageData).toEqual({token: 'token', 'fluxer.theme': 'dark'}); + expect(rewritten.token).toBe('token'); + }); +}); + +describe('local storage export', () => { + it('skips push, test, runtime config and migration keys', () => { + const storage = memoryStorage({ + token: 't', + 'fluxer.lastPushEndpoint': 'https://push', + 'fluxer.pushSubscription': '{}', + __test__: '1', + runtimeConfig: '{}', + [core.DOMAIN_MIGRATION_MARKER_KEY]: '{}', + [core.DOMAIN_MIGRATION_DEVICE_KEY]: 'device', + 'mobx-persist:Theme': '{}', + }); + expect(core.collectExportableLocalStorage(storage)).toEqual({token: 't', 'mobx-persist:Theme': '{}'}); + }); +}); + +describe('migration gate', () => { + it('passes when every condition holds', () => { + expect(core.shouldStartDomainMigration(gateInput())).toBe(true); + expect( + core.shouldStartDomainMigration( + gateInput({environment: environment('none', {electron: true, electronMigrationVersion: 1})}), + ), + ).toBe(true); + }); + + it.each<[string, Partial]>([ + ['assignment off', {assignmentEnabled: false}], + ['kill switch', {discovery: {...ENABLED_DISCOVERY, enabled: false}}], + ['no discovery', {discovery: null}], + ['already completed', {marker: {state: 'completed', target: 'https://fluxer.com', at: NOW}}], + ['failed recently', {marker: {state: 'failed', at: NOW - 60_000, attempts: 1}}], + ['failed too often', {marker: {state: 'failed', at: NOW - 3 * 24 * 60 * 60 * 1000, attempts: 3}}], + ['Android web app', {environment: environment('chromium-android')}], + ['Apple web app', {environment: environment('webkit')}], + ['Firefox web app', {environment: environment('firefox')}], + ['other web app', {environment: environment('other')}], + ['old desktop', {environment: environment('none', {electron: true})}], + ['no related origins', {relatedOriginsSupported: false}], + ['in a voice call', {voiceActive: true}], + ['on a one-shot token route', {oneShotRoute: true}], + ])('blocks when %s', (_label, overrides) => { + expect(core.shouldStartDomainMigration(gateInput(overrides))).toBe(false); + }); + + it('runs the handoff inside a Chromium desktop web app', () => { + expect(core.shouldStartDomainMigration(gateInput({environment: environment('chromium-desktop')}))).toBe(true); + }); + + it('retries a failure after a day', () => { + const marker: core.DomainMigrationMarker = {state: 'failed', at: NOW - 25 * 60 * 60 * 1000, attempts: 2}; + expect(core.shouldStartDomainMigration(gateInput({marker}))).toBe(true); + }); +}); + +describe('marker state', () => { + it('counts failures and records completion', () => { + const storage = memoryStorage(); + expect(core.readDomainMigrationMarker(storage)).toBeNull(); + core.markDomainMigrationFailed(storage, NOW); + core.markDomainMigrationFailed(storage, NOW + 1); + expect(core.readDomainMigrationMarker(storage)).toEqual({state: 'failed', at: NOW + 1, attempts: 2}); + core.markDomainMigrationCompleted(storage, 'https://fluxer.com', NOW + 2); + expect(core.readDomainMigrationMarker(storage)).toEqual({ + state: 'completed', + target: 'https://fluxer.com', + at: NOW + 2, + }); + core.markDomainMigrationFailed(storage, NOW + 3); + expect(core.readDomainMigrationMarker(storage)).toEqual({state: 'failed', at: NOW + 3, attempts: 1}); + }); + + it('ignores malformed markers', () => { + expect(core.parseDomainMigrationMarker('not json')).toBeNull(); + expect(core.parseDomainMigrationMarker('{"state":"completed","at":1}')).toBeNull(); + }); +}); + +describe('migration intent', () => { + it('expires and ties completion to the exported handoff', () => { + const storage = memoryStorage(); + expect(core.readDomainMigrationIntent(storage, NOW)).toBeNull(); + core.writeDomainMigrationIntent(storage, {at: NOW}); + expect(core.readDomainMigrationIntent(storage, NOW + 1000)).toEqual({at: NOW}); + expect(core.readDomainMigrationIntent(storage, NOW + core.DOMAIN_MIGRATION_PENDING_MAX_AGE_MS + 1)).toBeNull(); + expect(core.intentConfirmsCompletion(null, null)).toBe(false); + expect(core.intentConfirmsCompletion({at: NOW}, null)).toBe(true); + expect(core.intentConfirmsCompletion({at: NOW, handoff_id: 'abc'}, 'abc')).toBe(true); + expect(core.intentConfirmsCompletion({at: NOW, handoff_id: 'abc'}, 'xyz')).toBe(false); + expect(core.intentConfirmsCompletion({at: NOW, handoff_id: 'abc'}, null)).toBe(false); + core.clearDomainMigrationIntent(storage); + expect(core.readDomainMigrationIntent(storage, NOW)).toBeNull(); + }); +}); + +describe('forwarding', () => { + it('keeps the path, query and hash', () => { + expect(core.buildTargetUrl('https://fluxer.com', '/reset', '?a=1', '#token=abc')).toBe( + 'https://fluxer.com/reset?a=1#token=abc', + ); + expect(core.buildTargetUrl('https://fluxer.com', '/', '', '')).toBe('https://fluxer.com/app'); + }); + + it('honours the kill switch', () => { + const completed: core.DomainMigrationMarker = {state: 'completed', target: 'https://fluxer.com', at: NOW}; + const browser = environment('none'); + expect(core.shouldForwardCompletedSource(ENABLED_DISCOVERY, completed, browser)).toBe(true); + expect(core.shouldForwardCompletedSource({...ENABLED_DISCOVERY, enabled: false}, completed, browser)).toBe(false); + expect(core.shouldForwardCompletedSource(null, completed, browser)).toBe(false); + expect(core.shouldForwardCompletedSource(ENABLED_DISCOVERY, null, browser)).toBe(false); + }); + + it('forwards installed apps only when standalone forwarding is on', () => { + const completed: core.DomainMigrationMarker = {state: 'completed', target: 'https://fluxer.com', at: NOW}; + const forwarding = {...ENABLED_DISCOVERY, standalone_forwarding: true}; + const desktop = environment('chromium-desktop'); + expect(core.shouldForwardCompletedSource(ENABLED_DISCOVERY, completed, desktop)).toBe(false); + expect(core.shouldForwardCompletedSource(forwarding, completed, desktop)).toBe(true); + const legacyDiscovery = {...ENABLED_DISCOVERY} as Partial; + delete legacyDiscovery.standalone_forwarding; + expect( + core.shouldForwardCompletedSource(legacyDiscovery as DomainMigrationDiscoveryResponse, completed, desktop), + ).toBe(false); + for (const kind of ['chromium-android', 'webkit', 'firefox', 'other'] as const) { + expect(core.shouldForwardCompletedSource(forwarding, completed, environment(kind))).toBe(false); + expect(core.environmentMayForward(environment(kind), forwarding)).toBe(false); + } + expect(core.environmentMayForward(environment('none'), ENABLED_DISCOVERY)).toBe(true); + expect(core.environmentMayForward(environment('none', {electron: true}), ENABLED_DISCOVERY)).toBe(false); + }); + + it('buckets anonymous devices by basis points', () => { + expect(core.anonymousRolloutIsOpen(ENABLED_DISCOVERY)).toBe(false); + expect( + core.anonymousRolloutIsOpen({...ENABLED_DISCOVERY, enabled: false, anonymous_rollout_basis_points: 10000}), + ).toBe(false); + const all = {...ENABLED_DISCOVERY, anonymous_rollout_basis_points: 10000}; + expect(core.anonymousRolloutIsOpen(all)).toBe(true); + expect(core.deviceIsInAnonymousRollout(all, 'device-a')).toBe(true); + const half = {...ENABLED_DISCOVERY, anonymous_rollout_basis_points: 5000}; + for (const deviceId of ['device-a', 'device-b', 'device-c', 'device-d']) { + expect(core.deviceIsInAnonymousRollout(half, deviceId)).toBe( + experimentBucket(deviceId, half.rollout_salt) < 5000, + ); + } + }); +}); + +const CHROME_DESKTOP_UA = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'; +const EDGE_DESKTOP_UA = `${CHROME_DESKTOP_UA} Edg/140.0.0.0`; +const CHROME_ANDROID_UA = + 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36'; +const CHROME_ANDROID_TABLET_UA = + 'Mozilla/5.0 (Linux; Android 14; SM-X910) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'; +const SAMSUNG_UA = + 'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/27.0 Chrome/125.0.0.0 Mobile Safari/537.36'; +const IPHONE_UA = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1'; +const IPAD_DESKTOP_UA = + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15'; +const MAC_SAFARI_UA = IPAD_DESKTOP_UA; +const MAC_CHROME_UA = + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'; +const FIREFOX_DESKTOP_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0'; +const FIREFOX_ANDROID_UA = 'Mozilla/5.0 (Android 14; Mobile; rv:143.0) Gecko/143.0 Firefox/143.0'; +const UNKNOWN_UA = 'SomeBrowser/1.0'; +const CHROMIUM_BRANDS = [{brand: 'Chromium'}, {brand: 'Google Chrome'}, {brand: 'Not=A?Brand'}]; + +function signals(overrides: Partial): core.DomainMigrationInstallSignals { + return { + displayMode: 'standalone', + navigatorStandalone: false, + userAgent: CHROME_DESKTOP_UA, + userAgentData: null, + maxTouchPoints: 0, + electron: false, + ...overrides, + }; +} + +describe('classifyDomainMigrationInstallKind', () => { + it.each<[string, Partial, core.DomainMigrationInstallKind]>([ + ['a browser tab', {displayMode: 'browser'}, 'none'], + ['a Firefox taskbar tab', {displayMode: 'minimal-ui', userAgent: FIREFOX_DESKTOP_UA}, 'none'], + ['Electron', {electron: true}, 'none'], + ['Safari on iPhone in a tab', {displayMode: 'browser', userAgent: IPHONE_UA}, 'none'], + ['Chrome desktop by brand', {userAgentData: {brands: CHROMIUM_BRANDS, mobile: false}}, 'chromium-desktop'], + ['Chrome desktop by user agent', {}, 'chromium-desktop'], + ['Edge desktop', {userAgent: EDGE_DESKTOP_UA}, 'chromium-desktop'], + ['Chrome on a Mac', {userAgent: MAC_CHROME_UA}, 'chromium-desktop'], + ['window controls overlay', {displayMode: 'window-controls-overlay'}, 'chromium-desktop'], + [ + 'Chrome Android by brand', + {userAgent: CHROME_ANDROID_UA, userAgentData: {brands: CHROMIUM_BRANDS, mobile: true}}, + 'chromium-android', + ], + ['Chrome Android by user agent', {userAgent: CHROME_ANDROID_UA}, 'chromium-android'], + [ + 'Chrome on an Android tablet by brand', + { + userAgent: CHROME_ANDROID_TABLET_UA, + userAgentData: {brands: CHROMIUM_BRANDS, mobile: false, platform: 'Android'}, + }, + 'chromium-android', + ], + ['Chrome on an Android tablet by user agent', {userAgent: CHROME_ANDROID_TABLET_UA}, 'chromium-android'], + ['Samsung Internet', {userAgent: SAMSUNG_UA}, 'chromium-android'], + ['an iPhone home screen app', {displayMode: 'browser', navigatorStandalone: true, userAgent: IPHONE_UA}, 'webkit'], + ['an iPad home screen app', {userAgent: IPAD_DESKTOP_UA, maxTouchPoints: 5}, 'webkit'], + ['a Safari Dock app', {userAgent: MAC_SAFARI_UA}, 'webkit'], + ['a Firefox desktop app', {userAgent: FIREFOX_DESKTOP_UA}, 'firefox'], + ['a Firefox Android app', {userAgent: FIREFOX_ANDROID_UA}, 'firefox'], + ['an unknown browser', {userAgent: UNKNOWN_UA}, 'other'], + ])('classifies %s', (_label, overrides, expected) => { + expect(core.classifyDomainMigrationInstallKind(signals(overrides))).toBe(expected); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it.each([CHROME_DESKTOP_UA, FIREFOX_DESKTOP_UA, MAC_SAFARI_UA])( + 'treats a full screen browser window as a tab', + (userAgent) => { + vi.stubGlobal('matchMedia', (query: string) => ({matches: query === '(display-mode: fullscreen)'})); + vi.stubGlobal('navigator', {userAgent, maxTouchPoints: 0}); + expect(detectDomainMigrationInstallKind()).toBe('none'); + }, + ); + + it('reads an installed app window from the display mode', () => { + vi.stubGlobal('matchMedia', (query: string) => ({matches: query === '(display-mode: standalone)'})); + vi.stubGlobal('navigator', {userAgent: CHROME_DESKTOP_UA, maxTouchPoints: 0}); + expect(detectDomainMigrationInstallKind()).toBe('chromium-desktop'); + }); +}); + +describe('shouldShowDomainMovedNotice', () => { + const source = core.resolveDomainMigrationSide('https://web.fluxer.app'); + const completed: core.DomainMigrationMarker = {state: 'completed', target: 'https://fluxer.com', at: NOW}; + + function notice(overrides: Partial): core.DomainMovedNoticeInput { + return { + side: source, + installKind: 'webkit', + discovery: ENABLED_DISCOVERY, + assignmentEnabled: true, + marker: null, + dismissedAt: null, + now: NOW, + ...overrides, + }; + } + + it('shows in installed apps on the source once the user is in the rollout', () => { + for (const installKind of ['chromium-android', 'webkit', 'firefox', 'other'] as const) { + expect(core.shouldShowDomainMovedNotice(notice({installKind}))).toBe(true); + expect(core.shouldShowDomainMovedNotice(notice({installKind, assignmentEnabled: false}))).toBe(false); + expect(core.shouldShowDomainMovedNotice(notice({installKind, assignmentEnabled: false, marker: completed}))).toBe( + true, + ); + } + }); + + it('waits for the handoff in Chromium desktop apps', () => { + expect(core.shouldShowDomainMovedNotice(notice({installKind: 'chromium-desktop'}))).toBe(false); + expect(core.shouldShowDomainMovedNotice(notice({installKind: 'chromium-desktop', marker: completed}))).toBe(true); + }); + + it.each<[string, Partial]>([ + ['a browser tab', {installKind: 'none', marker: completed}], + ['the target', {side: core.resolveDomainMigrationSide('https://fluxer.com')}], + ['a self-hosted origin', {side: core.resolveDomainMigrationSide('https://chat.example.com')}], + ['the kill switch', {discovery: {...ENABLED_DISCOVERY, enabled: false}}], + ['missing discovery', {discovery: null}], + ])('stays hidden for %s', (_label, overrides) => { + expect(core.shouldShowDomainMovedNotice(notice(overrides))).toBe(false); + }); + + it('comes back seven days after a dismissal', () => { + expect(core.shouldShowDomainMovedNotice(notice({dismissedAt: NOW - 1000}))).toBe(false); + expect( + core.shouldShowDomainMovedNotice(notice({dismissedAt: NOW - core.DOMAIN_MIGRATION_MOVED_DISMISS_MS + 1})), + ).toBe(false); + expect(core.shouldShowDomainMovedNotice(notice({dismissedAt: NOW - core.DOMAIN_MIGRATION_MOVED_DISMISS_MS}))).toBe( + true, + ); + }); + + it('builds the new app links from the side target', () => { + expect(core.domainMovedInstallUrl('https://canary.fluxer.com')).toBe('https://canary.fluxer.com/app'); + expect(core.domainMovedManifestId('https://fluxer.com')).toBe('https://fluxer.com/'); + expect(core.domainMovedBrowserMigrationUrl('https://fluxer.com')).toBe( + 'https://fluxer.com/migrate/begin?start=1&next=%2Fapp', + ); + }); +}); + +describe('installDomainMovedApp', () => { + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('opens the new app in the browser straight from the click without the install API', () => { + const open = vi.spyOn(window, 'open').mockReturnValue(null); + const onUnavailable = vi.fn(); + vi.stubGlobal('navigator', {userAgent: CHROME_DESKTOP_UA}); + installDomainMovedApp('https://fluxer.com', onUnavailable); + expect(open).toHaveBeenCalledWith('https://fluxer.com/app', '_blank', 'noopener'); + expect(onUnavailable).not.toHaveBeenCalled(); + }); + + it('shows the fallback instead of a late popup when the install fails', async () => { + const open = vi.spyOn(window, 'open').mockReturnValue(null); + const onUnavailable = vi.fn(); + const install = vi.fn().mockRejectedValue(new DOMException('denied', 'NotAllowedError')); + vi.stubGlobal('navigator', {userAgent: CHROME_DESKTOP_UA, install}); + installDomainMovedApp('https://fluxer.com', onUnavailable); + await vi.waitFor(() => expect(onUnavailable).toHaveBeenCalledOnce()); + expect(install).toHaveBeenCalledWith('https://fluxer.com/app', 'https://fluxer.com/'); + expect(open).not.toHaveBeenCalled(); + }); + + it('does nothing more when the user cancels the install', async () => { + const onUnavailable = vi.fn(); + const install = vi.fn().mockRejectedValue(new DOMException('cancelled', 'AbortError')); + vi.stubGlobal('navigator', {userAgent: CHROME_DESKTOP_UA, install}); + installDomainMovedApp('https://fluxer.com', onUnavailable); + await Promise.resolve(); + await Promise.resolve(); + expect(onUnavailable).not.toHaveBeenCalled(); + }); +}); + +describe('runDomainMigrationPreMount', () => { + let replace: ReturnType; + + function visit(url: string, discovery: DomainMigrationDiscoveryResponse | undefined): void { + const parsed = new URL(url); + replace = vi.fn(); + vi.stubGlobal('location', { + origin: parsed.origin, + pathname: parsed.pathname, + search: parsed.search, + hash: parsed.hash, + replace, + }); + (window as unknown as Record).__FLUXER_BOOTSTRAP__ = {instance: {domain_migration: discovery}}; + } + + function readMarker(): core.DomainMigrationMarker | null { + return core.parseDomainMigrationMarker(window.localStorage.getItem(core.DOMAIN_MIGRATION_MARKER_KEY)); + } + + function writeIntent(intent: core.DomainMigrationIntent): void { + window.sessionStorage.setItem(core.DOMAIN_MIGRATION_INTENT_KEY, JSON.stringify(intent)); + } + + function writeCompletedMarker(): void { + window.localStorage.setItem( + core.DOMAIN_MIGRATION_MARKER_KEY, + JSON.stringify({state: 'completed', target: 'https://fluxer.com', at: NOW}), + ); + } + + beforeEach(() => { + window.localStorage.clear(); + window.sessionStorage.clear(); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('does nothing on a self-hosted origin', async () => { + visit('https://chat.example.com/migrate/done?next=/channels/@me', ENABLED_DISCOVERY); + writeCompletedMarker(); + expect(await runDomainMigrationPreMount()).toBe(false); + expect(replace).not.toHaveBeenCalled(); + }); + + it('forwards a migrated source tab with its hash', async () => { + visit('https://web.fluxer.app/reset#token=abc', ENABLED_DISCOVERY); + writeCompletedMarker(); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://fluxer.com/reset#token=abc'); + }); + + it('stays put when the kill switch is off', async () => { + visit('https://web.fluxer.app/reset#token=abc', {...ENABLED_DISCOVERY, enabled: false}); + writeCompletedMarker(); + expect(await runDomainMigrationPreMount()).toBe(false); + expect(replace).not.toHaveBeenCalled(); + }); + + it('marks the source completed when the intent matches the handoff', async () => { + writeIntent({at: Date.now(), handoff_id: 'handoff'}); + visit('https://web.fluxer.app/migrate/done?h=handoff&next=%2Fchannels%2F1%2F2', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://fluxer.com/channels/1/2'); + expect(readMarker()).toMatchObject({state: 'completed', target: 'https://fluxer.com'}); + expect(window.sessionStorage.getItem(core.DOMAIN_MIGRATION_INTENT_KEY)).toBeNull(); + }); + + it('ignores a done link without a matching intent', async () => { + visit('https://web.fluxer.app/migrate/done?next=%2Fchannels%2F1%2F2', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/1/2'); + expect(readMarker()).toBeNull(); + + writeIntent({at: Date.now(), handoff_id: 'handoff'}); + visit('https://web.fluxer.app/migrate/done?h=other&next=%2Fchannels%2F1%2F2', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/1/2'); + expect(readMarker()).toBeNull(); + }); + + it('ignores migrate links while the kill switch is off', async () => { + writeIntent({at: Date.now()}); + visit('https://web.fluxer.app/migrate/done?next=%2Fchannels%2F1%2F2', {...ENABLED_DISCOVERY, enabled: false}); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/1/2'); + expect(readMarker()).toBeNull(); + }); + + it('refuses to export without an intent from the source trigger', async () => { + const fetchSpy = vi.fn(); + vi.stubGlobal('fetch', fetchSpy); + visit(`https://web.fluxer.app/migrate/export?n=${'a'.repeat(43)}`, ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/@me'); + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it('leaves the marker alone on a failed link and refuses an open redirect', async () => { + visit('https://web.fluxer.app/migrate/failed?reason=nonce_mismatch&next=%2F%09%2Fevil.example', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/@me'); + expect(readMarker()).toBeNull(); + }); + + it('reopens a completed source when a resumed migration fails', async () => { + writeCompletedMarker(); + writeIntent({at: Date.now()}); + visit('https://web.fluxer.app/migrate/failed?reason=redeem_failed&next=%2Fchannels%2F%40me', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/@me'); + expect(readMarker()).toMatchObject({state: 'failed', attempts: 1}); + }); + + it('resumes through the target when a migrated source still holds a session', async () => { + writeCompletedMarker(); + window.localStorage.setItem('token', 'session-token'); + visit('https://web.fluxer.app/reset#token=abc', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith( + `https://fluxer.com/migrate/begin?resume=1&next=${encodeURIComponent('/reset#token=abc')}`, + ); + expect(core.readDomainMigrationIntent(window.sessionStorage, Date.now())).not.toBeNull(); + }); + + function installApp(userAgent: string): void { + vi.stubGlobal('matchMedia', (query: string) => ({matches: query === '(display-mode: standalone)'})); + vi.stubGlobal('navigator', {userAgent, maxTouchPoints: 0}); + } + + it('returns a Chromium desktop app to the source after the handoff', async () => { + installApp(CHROME_DESKTOP_UA); + writeIntent({at: Date.now(), handoff_id: 'handoff'}); + visit('https://web.fluxer.app/migrate/done?h=handoff&next=%2Fchannels%2F1%2F2', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/1/2'); + expect(readMarker()).toMatchObject({state: 'completed', target: 'https://fluxer.com'}); + }); + + it('forwards a Chromium desktop app when standalone forwarding is on', async () => { + installApp(CHROME_DESKTOP_UA); + writeIntent({at: Date.now(), handoff_id: 'handoff'}); + visit('https://web.fluxer.app/migrate/done?h=handoff&next=%2Fchannels%2F1%2F2', { + ...ENABLED_DISCOVERY, + standalone_forwarding: true, + }); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://fluxer.com/channels/1/2'); + }); + + it('keeps a migrated Chromium desktop app on the source', async () => { + installApp(CHROME_DESKTOP_UA); + writeCompletedMarker(); + visit('https://web.fluxer.app/channels/1/2', {...ENABLED_DISCOVERY, anonymous_rollout_basis_points: 10000}); + expect(await runDomainMigrationPreMount()).toBe(false); + expect(replace).not.toHaveBeenCalled(); + + visit('https://web.fluxer.app/channels/1/2', {...ENABLED_DISCOVERY, standalone_forwarding: true}); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://fluxer.com/channels/1/2'); + }); + + it('does not forward logged-out installed apps in the anonymous rollout', async () => { + installApp(CHROME_DESKTOP_UA); + visit('https://web.fluxer.app/login', {...ENABLED_DISCOVERY, anonymous_rollout_basis_points: 10000}); + expect(await runDomainMigrationPreMount()).toBe(false); + expect(replace).not.toHaveBeenCalled(); + }); + + it('never runs the handoff or forwards in an Apple web app', async () => { + installApp(IPHONE_UA); + writeCompletedMarker(); + visit('https://web.fluxer.app/channels/1/2', {...ENABLED_DISCOVERY, standalone_forwarding: true}); + expect(await runDomainMigrationPreMount()).toBe(false); + expect(replace).not.toHaveBeenCalled(); + + window.localStorage.clear(); + writeIntent({at: Date.now(), handoff_id: 'handoff'}); + visit('https://web.fluxer.app/migrate/done?h=handoff&next=%2Fchannels%2F1%2F2', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/1/2'); + expect(readMarker()).toBeNull(); + }); + + it('never runs the handoff in an Android web app', async () => { + installApp(CHROME_ANDROID_UA); + writeIntent({at: Date.now()}); + visit('https://web.fluxer.app/migrate/done?next=%2Fchannels%2F1%2F2', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/channels/1/2'); + expect(readMarker()).toBeNull(); + }); + + it('starts a browser migration opened from an installed Android app on the source', async () => { + visit('https://fluxer.com/migrate/begin?start=1&next=%2Fapp', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/migrate/start?next=%2Fapp'); + expect(window.sessionStorage.getItem(core.DOMAIN_MIGRATION_PENDING_KEY)).toBeNull(); + + visit('https://web.fluxer.app/migrate/start?next=%2Fapp', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://fluxer.com/migrate/begin?next=%2Fapp'); + expect(core.readDomainMigrationIntent(window.sessionStorage, Date.now())).toMatchObject({at: expect.any(Number)}); + + visit('https://fluxer.com/migrate/begin?next=%2Fapp', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace.mock.calls[0]?.[0]).toMatch(/^https:\/\/web\.fluxer\.app\/migrate\/export\?n=[\w-]+$/u); + }); + + it('opens the target directly when the browser already migrated', async () => { + window.localStorage.setItem('token', 'session-token'); + visit('https://fluxer.com/migrate/begin?start=1&next=%2Fapp', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://fluxer.com/app'); + }); + + it('does not start a migration inside an installed Android app', async () => { + installApp(CHROME_ANDROID_UA); + visit('https://web.fluxer.app/migrate/start?next=%2Fapp', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/app'); + expect(core.readDomainMigrationIntent(window.sessionStorage, Date.now())).toBeNull(); + }); + + it('sends a target completion without a pending nonce back as failed', async () => { + visit('https://fluxer.com/migrate/complete#h=abc&k=def', ENABLED_DISCOVERY); + vi.stubGlobal('history', {state: null, replaceState: vi.fn()}); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith( + 'https://web.fluxer.app/migrate/failed?reason=no_pending&next=%2Fchannels%2F%40me', + ); + }); + + it('reports back to the source when the target already holds a session', async () => { + window.localStorage.setItem('token', 'session-token'); + visit('https://fluxer.com/migrate/begin?next=%2Fchannels%2F1', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://web.fluxer.app/migrate/done?next=%2Fchannels%2F1'); + + visit('https://fluxer.com/migrate/begin?resume=1&next=%2Fchannels%2F1', ENABLED_DISCOVERY); + expect(await runDomainMigrationPreMount()).toBe(true); + expect(replace).toHaveBeenCalledWith('https://fluxer.com/channels/1'); + }); +}); diff --git a/fluxer_app/src/features/app/domain_migration/DomainMigrationBrowser.ts b/fluxer_app/src/features/app/domain_migration/DomainMigrationBrowser.ts new file mode 100644 index 000000000..9d265c10a --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMigrationBrowser.ts @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + classifyDomainMigrationInstallKind, + type DomainMigrationDisplayMode, + type DomainMigrationEnvironment, + type DomainMigrationInstallKind, + domainMovedBrowserMigrationUrl, + domainMovedInstallUrl, + domainMovedManifestId, +} from '@app/features/app/domain_migration/DomainMigrationCore'; +import { + AuthSessionStorageKey, + parseStoredSessionValue, +} from '@app/features/platform/state/auth_session/AuthSessionStorage'; +import {getProtectedLocalStorage} from '@app/features/platform/state/ProtectedWebStorage'; +import {hasUnavailableElectronNativeContext, isElectron} from '@app/features/ui/utils/NativeUtils'; +import type {DomainMigrationDiscoveryResponse} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; + +interface NavigatorWithStandalone extends Navigator { + standalone?: boolean; +} + +const DISPLAY_MODES: ReadonlyArray = [ + 'window-controls-overlay', + 'standalone', + 'minimal-ui', +]; + +interface PublicKeyCredentialWithCapabilities { + getClientCapabilities?: () => Promise>; +} + +export function readDomainMigrationDiscovery(): DomainMigrationDiscoveryResponse | null { + return window.__FLUXER_BOOTSTRAP__?.instance.domain_migration ?? null; +} + +function readDisplayMode(): DomainMigrationDisplayMode { + for (const mode of DISPLAY_MODES) { + if (window.matchMedia?.(`(display-mode: ${mode})`).matches) { + return mode; + } + } + return 'browser'; +} + +function isElectronEnvironment(): boolean { + return isElectron() || hasUnavailableElectronNativeContext(); +} + +export function detectDomainMigrationInstallKind(): DomainMigrationInstallKind { + if (typeof window === 'undefined') { + return 'none'; + } + const navigator = window.navigator as NavigatorWithStandalone; + return classifyDomainMigrationInstallKind({ + displayMode: readDisplayMode(), + navigatorStandalone: navigator.standalone === true, + userAgent: navigator.userAgent, + userAgentData: navigator.userAgentData ?? null, + maxTouchPoints: navigator.maxTouchPoints ?? 0, + electron: isElectronEnvironment(), + }); +} + +export function readDomainMigrationEnvironment(): DomainMigrationEnvironment { + return { + installKind: detectDomainMigrationInstallKind(), + electron: isElectronEnvironment(), + electronMigrationVersion: window.electron?.domainMigration?.version ?? null, + }; +} + +export async function browserSupportsRelatedOrigins(): Promise { + if (typeof PublicKeyCredential === 'undefined') { + return false; + } + const credential = PublicKeyCredential as unknown as PublicKeyCredentialWithCapabilities; + if (typeof credential.getClientCapabilities !== 'function') { + return false; + } + try { + const capabilities = await credential.getClientCapabilities(); + return capabilities.relatedOrigins === true; + } catch { + return false; + } +} + +export function readActiveSessionToken(): string | null { + try { + return parseStoredSessionValue(getProtectedLocalStorage()?.getItem(AuthSessionStorageKey.Token) ?? null); + } catch { + return null; + } +} + +export async function hasStoredAccount(): Promise { + if (readActiveSessionToken() !== null) { + return true; + } + const {default: accountStorage} = await import('@app/features/auth/state/AccountStorage'); + const accounts = await accountStorage.getAllAccounts(); + return accounts.some((account) => Boolean(account.token)); +} + +function openInBrowser(url: string): void { + window.open(url, '_blank', 'noopener'); +} + +export function installDomainMovedApp(target: string, onUnavailable: () => void): void { + const installUrl = domainMovedInstallUrl(target); + if (typeof navigator.install !== 'function') { + openInBrowser(installUrl); + return; + } + navigator.install(installUrl, domainMovedManifestId(target)).catch((err: unknown) => { + if (err instanceof DOMException && err.name === 'AbortError') { + return; + } + onUnavailable(); + }); +} + +export function openDomainMovedBrowserMigration(target: string): void { + openInBrowser(domainMovedBrowserMigrationUrl(target)); +} diff --git a/fluxer_app/src/features/app/domain_migration/DomainMigrationCore.ts b/fluxer_app/src/features/app/domain_migration/DomainMigrationCore.ts new file mode 100644 index 000000000..fe470c8bd --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMigrationCore.ts @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import type {RuntimeConfigSnapshot} from '@app/features/app/state/RuntimeConfig'; +import type {StoredAccount} from '@app/features/auth/state/AccountStorage'; +import {isIOSMobileOrTabletUserAgent} from '@app/features/platform/notifications/NotificationAlertOptions'; +import type {DomainMigrationDiscoveryResponse} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; +import {experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket'; + +export const DOMAIN_MIGRATION_SOURCE_TO_TARGET: Readonly> = { + 'https://web.fluxer.app': 'https://fluxer.com', + 'https://web.canary.fluxer.app': 'https://canary.fluxer.com', +}; + +export const DOMAIN_MIGRATION_TARGET_TO_SOURCE: Readonly> = Object.fromEntries( + Object.entries(DOMAIN_MIGRATION_SOURCE_TO_TARGET).map(([source, target]) => [target, source]), +); + +export const DOMAIN_MIGRATION_MARKER_KEY = 'fluxer:domain-migration'; +export const DOMAIN_MIGRATION_DEVICE_KEY = 'fluxer:domain-migration:device'; +export const DOMAIN_MIGRATION_PENDING_KEY = 'fluxer:domain-migration:pending'; +export const DOMAIN_MIGRATION_INTENT_KEY = 'fluxer:domain-migration:intent'; +export const DOMAIN_MIGRATION_NOTIFICATIONS_KEY = 'fluxer:domain-migration:notifications'; +export const DOMAIN_MIGRATION_MOVED_DISMISSED_KEY = 'fluxer:domain-migration:moved-dismissed-at'; + +export const DOMAIN_MIGRATION_PAYLOAD_VERSION = 1; +export const DOMAIN_MIGRATION_DEFAULT_NEXT_PATH = '/channels/@me'; +export const DOMAIN_MIGRATION_MAX_FAILED_ATTEMPTS = 3; +export const DOMAIN_MIGRATION_FAILED_RETRY_DELAY_MS = 24 * 60 * 60 * 1000; +export const DOMAIN_MIGRATION_PENDING_MAX_AGE_MS = 10 * 60 * 1000; +export const DOMAIN_MIGRATION_CUSTOM_SOUNDS_MAX_BYTES = 4 * 1024 * 1024; +export const DOMAIN_MIGRATION_THEME_ASSETS_MAX_BYTES = 2 * 1024 * 1024; +export const DOMAIN_MIGRATION_MOVED_DISMISS_MS = 7 * 24 * 60 * 60 * 1000; + +const NEXT_PATH_BASE = 'https://next.invalid'; + +function hasUnsafeNextPathCharacter(value: string): boolean { + for (let index = 0; index < value.length; index++) { + const code = value.charCodeAt(index); + if (code <= 0x1f || code === 0x7f || code === 0x5c) { + return true; + } + } + return false; +} + +const DENIED_LOCAL_STORAGE_KEYS: ReadonlySet = new Set([ + 'fluxer.lastPushEndpoint', + '__test__', + 'runtimeConfig', +]); +const PUSH_SUBSCRIPTION_KEY_PATTERN = /push[-_.:]?(?:subscription|endpoint)/iu; + +export interface StorageLike { + getItem(key: string): string | null; + setItem(key: string, value: string): void; + removeItem(key: string): void; + key(index: number): string | null; + readonly length: number; +} + +export type DomainMigrationSide = + | {role: 'source'; source: string; target: string} + | {role: 'target'; source: string; target: string}; + +export function resolveDomainMigrationSide(origin: string): DomainMigrationSide | null { + const target = DOMAIN_MIGRATION_SOURCE_TO_TARGET[origin]; + if (target !== undefined) { + return {role: 'source', source: origin, target}; + } + const source = DOMAIN_MIGRATION_TARGET_TO_SOURCE[origin]; + if (source !== undefined) { + return {role: 'target', source, target: origin}; + } + return null; +} + +export function sanitizeNextPath(value: unknown): string { + if (typeof value !== 'string' || !value.startsWith('/') || hasUnsafeNextPathCharacter(value)) { + return DOMAIN_MIGRATION_DEFAULT_NEXT_PATH; + } + let url: URL; + try { + url = new URL(value, NEXT_PATH_BASE); + } catch { + return DOMAIN_MIGRATION_DEFAULT_NEXT_PATH; + } + if (url.origin !== NEXT_PATH_BASE || url.pathname.startsWith('/migrate')) { + return DOMAIN_MIGRATION_DEFAULT_NEXT_PATH; + } + const pathname = url.pathname === '/' ? '/app' : url.pathname; + return `${pathname}${url.search}${url.hash}`; +} + +export function buildTargetUrl(target: string, pathname: string, search: string, hash: string): string { + return `${target}${sanitizeNextPath(`${pathname}${search}${hash}`)}`; +} + +export type DomainMigrationMarker = + | {state: 'completed'; target: string; at: number} + | {state: 'failed'; at: number; attempts: number}; + +export function parseDomainMigrationMarker(raw: string | null): DomainMigrationMarker | null { + if (!raw) { + return null; + } + try { + const value = JSON.parse(raw) as Record; + if (typeof value !== 'object' || value === null || typeof value.at !== 'number') { + return null; + } + if (value.state === 'completed' && typeof value.target === 'string') { + return {state: 'completed', target: value.target, at: value.at}; + } + if (value.state === 'failed') { + const attempts = typeof value.attempts === 'number' && value.attempts > 0 ? value.attempts : 1; + return {state: 'failed', at: value.at, attempts}; + } + return null; + } catch { + return null; + } +} + +export function readDomainMigrationMarker(storage: StorageLike | null): DomainMigrationMarker | null { + try { + return parseDomainMigrationMarker(storage?.getItem(DOMAIN_MIGRATION_MARKER_KEY) ?? null); + } catch { + return null; + } +} + +function writeDomainMigrationMarker(storage: StorageLike | null, marker: DomainMigrationMarker): void { + try { + storage?.setItem(DOMAIN_MIGRATION_MARKER_KEY, JSON.stringify(marker)); + } catch {} +} + +export function markDomainMigrationCompleted(storage: StorageLike | null, target: string, now: number): void { + writeDomainMigrationMarker(storage, {state: 'completed', target, at: now}); +} + +export function markDomainMigrationFailed(storage: StorageLike | null, now: number): void { + const previous = readDomainMigrationMarker(storage); + const attempts = previous?.state === 'failed' ? previous.attempts + 1 : 1; + writeDomainMigrationMarker(storage, {state: 'failed', at: now, attempts}); +} + +export function markerAllowsDomainMigration(marker: DomainMigrationMarker | null, now: number): boolean { + if (marker === null) { + return true; + } + if (marker.state === 'completed') { + return false; + } + return ( + marker.attempts < DOMAIN_MIGRATION_MAX_FAILED_ATTEMPTS && now - marker.at >= DOMAIN_MIGRATION_FAILED_RETRY_DELAY_MS + ); +} + +export interface DomainMigrationIntent { + at: number; + handoff_id?: string; +} + +export function readDomainMigrationIntent(storage: StorageLike | null, now: number): DomainMigrationIntent | null { + try { + const raw = storage?.getItem(DOMAIN_MIGRATION_INTENT_KEY) ?? null; + if (!raw) { + return null; + } + const value = JSON.parse(raw) as unknown; + if (!isRecord(value) || typeof value.at !== 'number' || now - value.at > DOMAIN_MIGRATION_PENDING_MAX_AGE_MS) { + return null; + } + return typeof value.handoff_id === 'string' ? {at: value.at, handoff_id: value.handoff_id} : {at: value.at}; + } catch { + return null; + } +} + +export function writeDomainMigrationIntent(storage: StorageLike | null, intent: DomainMigrationIntent): void { + storage?.setItem(DOMAIN_MIGRATION_INTENT_KEY, JSON.stringify(intent)); +} + +export function clearDomainMigrationIntent(storage: StorageLike | null): void { + try { + storage?.removeItem(DOMAIN_MIGRATION_INTENT_KEY); + } catch {} +} + +export function intentConfirmsCompletion(intent: DomainMigrationIntent | null, handoffId: string | null): boolean { + if (intent === null) { + return false; + } + return intent.handoff_id === undefined || intent.handoff_id === handoffId; +} + +export type DomainMigrationInstallKind = + | 'none' + | 'chromium-desktop' + | 'chromium-android' + | 'webkit' + | 'firefox' + | 'other'; + +export type DomainMigrationDisplayMode = 'browser' | 'minimal-ui' | 'standalone' | 'window-controls-overlay'; + +export interface DomainMigrationInstallSignals { + displayMode: DomainMigrationDisplayMode; + navigatorStandalone: boolean; + userAgent: string; + userAgentData: {brands?: ReadonlyArray<{brand: string}>; mobile?: boolean; platform?: string} | null; + maxTouchPoints: number; + electron: boolean; +} + +const INSTALLED_DISPLAY_MODES: ReadonlySet = new Set([ + 'standalone', + 'window-controls-overlay', +]); +const CHROMIUM_USER_AGENT_PATTERN = /\b(?:Chrome|Chromium|CriOS|EdgA|Edg|OPR|SamsungBrowser)\//u; +const ANDROID_USER_AGENT_PATTERN = /\bAndroid\b/u; + +export function classifyDomainMigrationInstallKind(signals: DomainMigrationInstallSignals): DomainMigrationInstallKind { + if (signals.electron) { + return 'none'; + } + const installed = signals.navigatorStandalone || INSTALLED_DISPLAY_MODES.has(signals.displayMode); + if (!installed) { + return 'none'; + } + const {userAgent} = signals; + if (isIOSMobileOrTabletUserAgent(userAgent, signals.maxTouchPoints)) { + return 'webkit'; + } + const android = + signals.userAgentData?.platform === 'Android' || + signals.userAgentData?.mobile === true || + ANDROID_USER_AGENT_PATTERN.test(userAgent); + if (signals.userAgentData?.brands?.some((entry) => entry.brand === 'Chromium')) { + return android ? 'chromium-android' : 'chromium-desktop'; + } + if (/\bFirefox\//u.test(userAgent)) { + return 'firefox'; + } + if (CHROMIUM_USER_AGENT_PATTERN.test(userAgent)) { + return android ? 'chromium-android' : 'chromium-desktop'; + } + if (/\bMacintosh\b/u.test(userAgent) && /\bSafari\//u.test(userAgent)) { + return 'webkit'; + } + return 'other'; +} + +export interface DomainMigrationEnvironment { + installKind: DomainMigrationInstallKind; + electron: boolean; + electronMigrationVersion: number | null; +} + +export function environmentAllowsDomainMigration(environment: DomainMigrationEnvironment): boolean { + if (environment.installKind !== 'none' && environment.installKind !== 'chromium-desktop') { + return false; + } + if (environment.electron) { + return environment.electronMigrationVersion !== null && environment.electronMigrationVersion >= 1; + } + return true; +} + +export function environmentMayForward( + environment: DomainMigrationEnvironment, + discovery: DomainMigrationDiscoveryResponse | null, +): boolean { + if (!environmentAllowsDomainMigration(environment)) { + return false; + } + return environment.installKind === 'none' || discovery?.standalone_forwarding === true; +} + +export interface DomainMigrationGateInput { + environment: DomainMigrationEnvironment; + assignmentEnabled: boolean; + discovery: DomainMigrationDiscoveryResponse | null; + marker: DomainMigrationMarker | null; + now: number; + relatedOriginsSupported: boolean; + voiceActive: boolean; + oneShotRoute: boolean; +} + +export function shouldStartDomainMigration(input: DomainMigrationGateInput): boolean { + return ( + input.assignmentEnabled && + input.discovery?.enabled === true && + markerAllowsDomainMigration(input.marker, input.now) && + environmentAllowsDomainMigration(input.environment) && + input.relatedOriginsSupported && + !input.voiceActive && + !input.oneShotRoute + ); +} + +export function shouldForwardCompletedSource( + discovery: DomainMigrationDiscoveryResponse | null, + marker: DomainMigrationMarker | null, + environment: DomainMigrationEnvironment, +): boolean { + return discovery?.enabled === true && marker?.state === 'completed' && environmentMayForward(environment, discovery); +} + +export interface DomainMovedNoticeInput { + side: DomainMigrationSide | null; + installKind: DomainMigrationInstallKind; + discovery: DomainMigrationDiscoveryResponse | null; + assignmentEnabled: boolean; + marker: DomainMigrationMarker | null; + dismissedAt: number | null; + now: number; +} + +export function shouldShowDomainMovedNotice(input: DomainMovedNoticeInput): boolean { + if (input.side?.role !== 'source' || input.installKind === 'none' || input.discovery?.enabled !== true) { + return false; + } + if (input.dismissedAt !== null && input.now - input.dismissedAt < DOMAIN_MIGRATION_MOVED_DISMISS_MS) { + return false; + } + const completed = input.marker?.state === 'completed'; + if (input.installKind === 'chromium-desktop') { + return completed; + } + return completed || input.assignmentEnabled; +} + +export function domainMovedInstallUrl(target: string): string { + return `${target}/app`; +} + +export function domainMovedManifestId(target: string): string { + return `${target}/`; +} + +export function domainMovedBrowserMigrationUrl(target: string): string { + return `${target}/migrate/begin?start=1&next=${encodeURIComponent('/app')}`; +} + +export function anonymousRolloutIsOpen(discovery: DomainMigrationDiscoveryResponse | null): boolean { + return discovery?.enabled === true && discovery.anonymous_rollout_basis_points > 0; +} + +export function deviceIsInAnonymousRollout(discovery: DomainMigrationDiscoveryResponse, deviceId: string): boolean { + return experimentBucket(deviceId, discovery.rollout_salt) < discovery.anonymous_rollout_basis_points; +} + +export function isExportableLocalStorageKey(key: string): boolean { + return ( + !DENIED_LOCAL_STORAGE_KEYS.has(key) && + !key.startsWith(DOMAIN_MIGRATION_MARKER_KEY) && + !PUSH_SUBSCRIPTION_KEY_PATTERN.test(key) + ); +} + +export function collectExportableLocalStorage(storage: StorageLike | null): Record { + const entries: Record = {}; + if (!storage) { + return entries; + } + for (let index = 0; index < storage.length; index++) { + const key = storage.key(index); + if (key === null || !isExportableLocalStorageKey(key)) { + continue; + } + const value = storage.getItem(key); + if (value !== null) { + entries[key] = value; + } + } + return entries; +} + +export interface DomainMigrationCustomSound { + sound_type: string; + file_name: string; + mime_type: string; + data: string; +} + +export interface DomainMigrationThemeAsset { + id: string; + name: string; + mime_type: string; + size: number; + data?: string; + desktop_path?: string; + created_at: number; + updated_at: number; +} + +export interface DomainMigrationThemeLibrary { + themes: Array>; + assets: Array; + local_files: Array>; + enabled_theme_ids: Array; +} + +export interface DomainMigrationPayload { + version: typeof DOMAIN_MIGRATION_PAYLOAD_VERSION; + source_origin: string; + exported_at: number; + local_storage: Record; + accounts: Array; + custom_sounds?: Array; + theme_library?: DomainMigrationThemeLibrary; + notification_permission: string; +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function isStringRecord(value: unknown): value is Record { + return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string'); +} + +function isStoredAccount(value: unknown): value is StoredAccount { + return ( + isRecord(value) && + typeof value.userId === 'string' && + value.userId.length > 0 && + (typeof value.token === 'string' || value.token === null) && + typeof value.lastActive === 'number' + ); +} + +function isCustomSound(value: unknown): value is DomainMigrationCustomSound { + return ( + isRecord(value) && + typeof value.sound_type === 'string' && + typeof value.file_name === 'string' && + typeof value.mime_type === 'string' && + typeof value.data === 'string' + ); +} + +function isIdentifiedRecord(value: unknown): value is Record { + return isRecord(value) && typeof value.id === 'string'; +} + +function isThemeAsset(value: unknown): value is DomainMigrationThemeAsset { + return ( + isIdentifiedRecord(value) && + typeof value.name === 'string' && + typeof value.mime_type === 'string' && + typeof value.size === 'number' && + (value.data === undefined || typeof value.data === 'string') && + (value.desktop_path === undefined || typeof value.desktop_path === 'string') && + typeof value.created_at === 'number' && + typeof value.updated_at === 'number' + ); +} + +function isThemeLibrary(value: unknown): value is DomainMigrationThemeLibrary { + return ( + isRecord(value) && + Array.isArray(value.themes) && + value.themes.every(isIdentifiedRecord) && + Array.isArray(value.assets) && + value.assets.every(isThemeAsset) && + Array.isArray(value.local_files) && + value.local_files.every(isIdentifiedRecord) && + Array.isArray(value.enabled_theme_ids) && + value.enabled_theme_ids.every((id) => typeof id === 'string') + ); +} + +export function withoutOptionalPayloadData(payload: DomainMigrationPayload): DomainMigrationPayload { + return { + ...payload, + custom_sounds: undefined, + theme_library: payload.theme_library && {...payload.theme_library, assets: []}, + }; +} + +export function parseDomainMigrationPayload(value: unknown, expectedSource: string): DomainMigrationPayload | null { + if ( + !isRecord(value) || + value.version !== DOMAIN_MIGRATION_PAYLOAD_VERSION || + value.source_origin !== expectedSource || + typeof value.exported_at !== 'number' || + !isStringRecord(value.local_storage) || + !Array.isArray(value.accounts) || + !value.accounts.every(isStoredAccount) || + typeof value.notification_permission !== 'string' + ) { + return null; + } + if ( + value.custom_sounds !== undefined && + !(Array.isArray(value.custom_sounds) && value.custom_sounds.every(isCustomSound)) + ) { + return null; + } + if (value.theme_library !== undefined && !isThemeLibrary(value.theme_library)) { + return null; + } + return value as unknown as DomainMigrationPayload; +} + +function withoutRuntimeConfig(snapshot: Record | undefined): Record { + const {runtimeConfig: _runtimeConfig, ...rest} = snapshot ?? {}; + return rest; +} + +export function rewriteImportedAccount(record: StoredAccount, instance: RuntimeConfigSnapshot): StoredAccount { + const managed = withoutRuntimeConfig(record.managedStorageData ?? record.localStorageData); + return { + ...record, + localStorageData: managed, + managedStorageData: managed, + instance, + }; +} diff --git a/fluxer_app/src/features/app/domain_migration/DomainMigrationCrypto.ts b/fluxer_app/src/features/app/domain_migration/DomainMigrationCrypto.ts new file mode 100644 index 000000000..63272326e --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMigrationCrypto.ts @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +const IV_BYTES = 12; +const KEY_BYTES = 32; +const GZIP_MAGIC_FIRST = 0x1f; +const GZIP_MAGIC_SECOND = 0x8b; +const BASE64_CHUNK = 0x8000; + +export function bytesToBase64Url(bytes: Uint8Array): string { + let binary = ''; + for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK) { + binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK)); + } + return btoa(binary).replace(/\+/gu, '-').replace(/\//gu, '_').replace(/=+$/u, ''); +} + +export function base64UrlToBytes(value: string): Uint8Array { + const base64 = value.replace(/-/gu, '+').replace(/_/gu, '/'); + const binary = atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '=')); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index++) { + bytes[index] = binary.charCodeAt(index); + } + return bytes; +} + +export function randomBase64Url(byteLength: number): string { + return bytesToBase64Url(crypto.getRandomValues(new Uint8Array(byteLength))); +} + +export async function sha256Hex(value: string): Promise { + const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))); + return Array.from(digest, (byte) => byte.toString(16).padStart(2, '0')).join(''); +} + +async function pipeBytes( + bytes: Uint8Array, + transform: GenericTransformStream, +): Promise> { + const stream = new Blob([bytes]).stream().pipeThrough(transform as TransformStream); + return new Uint8Array(await new Response(stream).arrayBuffer()); +} + +async function compress(bytes: Uint8Array): Promise> { + if (typeof CompressionStream === 'undefined') { + return bytes; + } + return pipeBytes(bytes, new CompressionStream('gzip')); +} + +async function decompress(bytes: Uint8Array): Promise> { + if (bytes[0] !== GZIP_MAGIC_FIRST || bytes[1] !== GZIP_MAGIC_SECOND) { + return bytes; + } + if (typeof DecompressionStream === 'undefined') { + throw new Error('DecompressionStream unavailable'); + } + return pipeBytes(bytes, new DecompressionStream('gzip')); +} + +export async function encryptDomainMigrationPayload(value: unknown): Promise<{payload: string; key: string}> { + const plaintext = await compress(new TextEncoder().encode(JSON.stringify(value))); + const rawKey = crypto.getRandomValues(new Uint8Array(KEY_BYTES)); + const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES)); + const key = await crypto.subtle.importKey('raw', rawKey, 'AES-GCM', false, ['encrypt']); + const ciphertext = new Uint8Array(await crypto.subtle.encrypt({name: 'AES-GCM', iv}, key, plaintext)); + const sealed = new Uint8Array(IV_BYTES + ciphertext.length); + sealed.set(iv, 0); + sealed.set(ciphertext, IV_BYTES); + return {payload: bytesToBase64Url(sealed), key: bytesToBase64Url(rawKey)}; +} + +export async function decryptDomainMigrationPayload(payload: string, encodedKey: string): Promise { + const sealed = base64UrlToBytes(payload); + const rawKey = base64UrlToBytes(encodedKey); + if (rawKey.length !== KEY_BYTES || sealed.length <= IV_BYTES) { + throw new Error('Malformed handoff payload'); + } + const key = await crypto.subtle.importKey('raw', rawKey, 'AES-GCM', false, ['decrypt']); + const plaintext = new Uint8Array( + await crypto.subtle.decrypt({name: 'AES-GCM', iv: sealed.subarray(0, IV_BYTES)}, key, sealed.subarray(IV_BYTES)), + ); + return JSON.parse(new TextDecoder().decode(await decompress(plaintext))); +} diff --git a/fluxer_app/src/features/app/domain_migration/DomainMigrationPreMount.ts b/fluxer_app/src/features/app/domain_migration/DomainMigrationPreMount.ts new file mode 100644 index 000000000..c7b1afa20 --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMigrationPreMount.ts @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + hasStoredAccount, + readActiveSessionToken, + readDomainMigrationDiscovery, + readDomainMigrationEnvironment, +} from '@app/features/app/domain_migration/DomainMigrationBrowser'; +import { + anonymousRolloutIsOpen, + buildTargetUrl, + clearDomainMigrationIntent, + collectExportableLocalStorage, + DOMAIN_MIGRATION_CUSTOM_SOUNDS_MAX_BYTES, + DOMAIN_MIGRATION_DEFAULT_NEXT_PATH, + DOMAIN_MIGRATION_DEVICE_KEY, + DOMAIN_MIGRATION_MARKER_KEY, + DOMAIN_MIGRATION_NOTIFICATIONS_KEY, + DOMAIN_MIGRATION_PAYLOAD_VERSION, + DOMAIN_MIGRATION_PENDING_KEY, + DOMAIN_MIGRATION_PENDING_MAX_AGE_MS, + DOMAIN_MIGRATION_THEME_ASSETS_MAX_BYTES, + type DomainMigrationCustomSound, + type DomainMigrationPayload, + type DomainMigrationSide, + type DomainMigrationThemeLibrary, + deviceIsInAnonymousRollout, + environmentAllowsDomainMigration, + environmentMayForward, + intentConfirmsCompletion, + isExportableLocalStorageKey, + markDomainMigrationCompleted, + markDomainMigrationFailed, + parseDomainMigrationMarker, + parseDomainMigrationPayload, + readDomainMigrationIntent, + readDomainMigrationMarker, + resolveDomainMigrationSide, + rewriteImportedAccount, + type StorageLike, + sanitizeNextPath, + shouldForwardCompletedSource, + withoutOptionalPayloadData, + writeDomainMigrationIntent, +} from '@app/features/app/domain_migration/DomainMigrationCore'; +import { + base64UrlToBytes, + bytesToBase64Url, + decryptDomainMigrationPayload, + encryptDomainMigrationPayload, + randomBase64Url, + sha256Hex, +} from '@app/features/app/domain_migration/DomainMigrationCrypto'; +import type {SoundType} from '@app/features/notification/utils/SoundUtils'; +import {getProtectedLocalStorage, getProtectedSessionStorage} from '@app/features/platform/state/ProtectedWebStorage'; +import {Logger} from '@app/features/platform/utils/AppLogger'; +import type { + ThemeLibraryAsset, + ThemeLibraryLocalFileReference, + ThemeLibraryTheme, +} from '@app/features/theme/state/ThemeLibrary'; +import {when} from 'mobx'; + +const logger = new Logger('DomainMigration'); + +const NONCE_BYTES = 32; +const DEVICE_ID_BYTES = 16; +const BASE64URL_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u; +const MAX_HANDOFF_PAYLOAD_LENGTH = 8 * 1024 * 1024; + +type DomainMigrationFailureReason = + | 'disabled' + | 'no_pending' + | 'missing_handoff' + | 'handoff_expired' + | 'nonce_mismatch' + | 'redeem_failed' + | 'invalid_payload' + | 'import_failed' + | 'target_error'; + +class DomainMigrationImportError extends Error { + constructor(readonly reason: DomainMigrationFailureReason) { + super(`Domain migration import failed: ${reason}`); + this.name = 'DomainMigrationImportError'; + } +} + +interface PendingHandoff { + nonce: string; + next: string; + at: number; +} + +function navigate(url: string): true { + window.location.replace(url); + return true; +} + +function readCurrentPath(): {pathname: string; search: string; hash: string} { + return {pathname: window.location.pathname, search: window.location.search, hash: window.location.hash}; +} + +function readNotificationPermission(): string { + return typeof Notification === 'undefined' ? 'unsupported' : Notification.permission; +} + +function readOrCreateDeviceId(): string { + const storage = getProtectedLocalStorage(); + const existing = storage?.getItem(DOMAIN_MIGRATION_DEVICE_KEY); + if (existing) { + return existing; + } + const deviceId = randomBase64Url(DEVICE_ID_BYTES); + try { + storage?.setItem(DOMAIN_MIGRATION_DEVICE_KEY, deviceId); + } catch {} + return deviceId; +} + +async function collectCustomSounds(): Promise | undefined> { + try { + const {getAllCustomSounds} = await import('@app/features/notification/utils/CustomSoundDB'); + const sounds = await getAllCustomSounds(); + const totalBytes = sounds.reduce((sum, sound) => sum + sound.blob.size, 0); + if (totalBytes > DOMAIN_MIGRATION_CUSTOM_SOUNDS_MAX_BYTES) { + return undefined; + } + return await Promise.all( + sounds.map(async (sound) => ({ + sound_type: sound.soundType, + file_name: sound.fileName, + mime_type: sound.blob.type, + data: bytesToBase64Url(new Uint8Array(await sound.blob.arrayBuffer())), + })), + ); + } catch (err) { + logger.warn('Skipping custom sounds in the domain migration export:', err); + return undefined; + } +} + +async function restoreCustomSounds(sounds: ReadonlyArray | undefined): Promise { + if (!sounds || sounds.length === 0) { + return; + } + const {saveCustomSound} = await import('@app/features/notification/utils/CustomSoundDB'); + for (const sound of sounds) { + try { + const blob = new Blob([base64UrlToBytes(sound.data)], {type: sound.mime_type}); + await saveCustomSound(sound.sound_type as SoundType, blob, sound.file_name); + } catch (err) { + logger.warn(`Failed to restore custom sound ${sound.sound_type}:`, err); + } + } +} + +async function collectThemeLibrary(): Promise { + try { + const db = await import('@app/features/theme/utils/ThemeLibraryDb'); + const [themes, assets, localFiles, enabledThemeIds] = await Promise.all([ + db.listThemeLibraryThemes(), + db.listThemeLibraryAssets(), + db.listThemeLibraryLocalFiles(), + db.getEnabledThemeIds(), + ]); + const assetBytes = assets.reduce((sum, asset) => sum + (asset.data?.size ?? 0), 0); + const portableAssets = assetBytes > DOMAIN_MIGRATION_THEME_ASSETS_MAX_BYTES ? [] : assets; + return { + themes: themes.map((theme) => ({...theme})), + assets: await Promise.all( + portableAssets.map(async (asset) => ({ + id: asset.id, + name: asset.name, + mime_type: asset.mimeType, + size: asset.size, + data: asset.data ? bytesToBase64Url(new Uint8Array(await asset.data.arrayBuffer())) : undefined, + desktop_path: asset.desktopPath, + created_at: asset.createdAt, + updated_at: asset.updatedAt, + })), + ), + local_files: localFiles.map((file) => ({...file})), + enabled_theme_ids: enabledThemeIds, + }; + } catch (err) { + logger.warn('Skipping the theme library in the domain migration export:', err); + return undefined; + } +} + +async function restoreThemeLibrary(library: DomainMigrationThemeLibrary | undefined): Promise { + if (!library) { + return; + } + try { + const db = await import('@app/features/theme/utils/ThemeLibraryDb'); + for (const theme of library.themes) { + await db.saveThemeLibraryTheme(theme as unknown as ThemeLibraryTheme); + } + for (const asset of library.assets) { + const restored: ThemeLibraryAsset = { + id: asset.id, + name: asset.name, + mimeType: asset.mime_type, + size: asset.size, + data: asset.data === undefined ? undefined : new Blob([base64UrlToBytes(asset.data)], {type: asset.mime_type}), + desktopPath: asset.desktop_path, + createdAt: asset.created_at, + updatedAt: asset.updated_at, + }; + await db.saveThemeLibraryAsset(restored); + } + for (const file of library.local_files) { + await db.saveThemeLibraryLocalFile(file as unknown as ThemeLibraryLocalFileReference); + } + if (library.enabled_theme_ids.length > 0) { + await db.setEnabledThemeIds(library.enabled_theme_ids); + } + } catch (err) { + logger.warn('Failed to restore the theme library:', err); + } +} + +async function createHandoff(apiEndpoint: string, token: string, nonceHash: string, payload: string): Promise { + const response = await fetch(`${apiEndpoint}/v1/auth/origin-handoff`, { + method: 'POST', + credentials: 'omit', + headers: {'Content-Type': 'application/json', Authorization: token}, + body: JSON.stringify({nonce_hash: nonceHash, payload}), + }); + if (!response.ok) { + throw new Error(`Origin handoff was rejected with status ${response.status}`); + } + const body = (await response.json()) as {handoff_id?: unknown}; + if (typeof body.handoff_id !== 'string' || !BASE64URL_TOKEN_PATTERN.test(body.handoff_id)) { + throw new Error('Origin handoff response is malformed'); + } + return body.handoff_id; +} + +async function redeemHandoff(target: string, handoffId: string, nonce: string): Promise { + let response: Response; + try { + response = await fetch(`${target}/api/v1/auth/origin-handoff/redeem`, { + method: 'POST', + credentials: 'omit', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({handoff_id: handoffId, nonce}), + }); + } catch { + throw new DomainMigrationImportError('redeem_failed'); + } + if (response.status === 404) { + throw new DomainMigrationImportError('handoff_expired'); + } + if (response.status === 400) { + throw new DomainMigrationImportError('nonce_mismatch'); + } + if (!response.ok) { + throw new DomainMigrationImportError('redeem_failed'); + } + const body = (await response.json()) as {payload?: unknown}; + if (typeof body.payload !== 'string') { + throw new DomainMigrationImportError('redeem_failed'); + } + return body.payload; +} + +function sourceUrl(side: DomainMigrationSide, next: unknown): string { + return `${side.source}${sanitizeNextPath(next)}`; +} + +function discoveryAllowsMigration(): boolean { + return ( + readDomainMigrationDiscovery()?.enabled === true && + environmentAllowsDomainMigration(readDomainMigrationEnvironment()) + ); +} + +function revokeCompletedMarker(storage: StorageLike | null): void { + if (readDomainMigrationMarker(storage)?.state === 'completed') { + markDomainMigrationFailed(storage, Date.now()); + } +} + +async function exportFromSource(side: DomainMigrationSide, nonce: string | null): Promise { + const storage = getProtectedLocalStorage(); + const sessionStorage = getProtectedSessionStorage(); + const intent = readDomainMigrationIntent(sessionStorage, Date.now()); + if (intent === null || intent.handoff_id !== undefined) { + clearDomainMigrationIntent(sessionStorage); + return navigate(sourceUrl(side, DOMAIN_MIGRATION_DEFAULT_NEXT_PATH)); + } + try { + if (nonce === null || !BASE64URL_TOKEN_PATTERN.test(nonce)) { + throw new Error('Missing or malformed nonce'); + } + const [{default: accountStorage}, {default: RuntimeConfig}] = await Promise.all([ + import('@app/features/auth/state/AccountStorage'), + import('@app/features/app/state/RuntimeConfig'), + ]); + const accounts = (await accountStorage.getAllAccounts()).filter((account) => Boolean(account.token)); + const token = readActiveSessionToken() ?? accounts.find((account) => account.isValid !== false)?.token ?? null; + if (!token) { + throw new Error('No stored account to export'); + } + const payload: DomainMigrationPayload = { + version: DOMAIN_MIGRATION_PAYLOAD_VERSION, + source_origin: side.source, + exported_at: Date.now(), + local_storage: collectExportableLocalStorage(storage), + accounts, + custom_sounds: await collectCustomSounds(), + theme_library: await collectThemeLibrary(), + notification_permission: readNotificationPermission(), + }; + let sealed = await encryptDomainMigrationPayload(payload); + if (sealed.payload.length > MAX_HANDOFF_PAYLOAD_LENGTH) { + sealed = await encryptDomainMigrationPayload(withoutOptionalPayloadData(payload)); + } + const handoffId = await createHandoff(RuntimeConfig.apiEndpoint, token, await sha256Hex(nonce), sealed.payload); + writeDomainMigrationIntent(sessionStorage, {at: intent.at, handoff_id: handoffId}); + return navigate(`${side.target}/migrate/complete#h=${handoffId}&k=${sealed.key}`); + } catch (err) { + logger.warn('Domain migration export failed:', err); + clearDomainMigrationIntent(sessionStorage); + revokeCompletedMarker(storage); + return navigate(sourceUrl(side, DOMAIN_MIGRATION_DEFAULT_NEXT_PATH)); + } +} + +async function forwardFromSource(side: DomainMigrationSide): Promise { + const environment = readDomainMigrationEnvironment(); + const discovery = readDomainMigrationDiscovery(); + if (!environmentMayForward(environment, discovery)) { + return false; + } + const {pathname, search, hash} = readCurrentPath(); + if (shouldForwardCompletedSource(discovery, readDomainMigrationMarker(getProtectedLocalStorage()), environment)) { + if (!(await hasStoredAccount())) { + return navigate(buildTargetUrl(side.target, pathname, search, hash)); + } + writeDomainMigrationIntent(getProtectedSessionStorage(), {at: Date.now()}); + const next = sanitizeNextPath(`${pathname}${search}${hash}`); + return navigate(`${side.target}/migrate/begin?resume=1&next=${encodeURIComponent(next)}`); + } + if ( + discovery !== null && + anonymousRolloutIsOpen(discovery) && + !(await hasStoredAccount()) && + deviceIsInAnonymousRollout(discovery, readOrCreateDeviceId()) + ) { + return navigate(buildTargetUrl(side.target, pathname, search, hash)); + } + return false; +} + +async function forwardWhenIdle(side: DomainMigrationSide): Promise { + const {default: MediaEngine} = await import('@app/features/voice/engine/MediaEngineFacade'); + await when(() => !MediaEngine.connected && !MediaEngine.connecting); + if ( + !shouldForwardCompletedSource( + readDomainMigrationDiscovery(), + readDomainMigrationMarker(getProtectedLocalStorage()), + readDomainMigrationEnvironment(), + ) + ) { + return; + } + const {pathname, search, hash} = readCurrentPath(); + navigate(buildTargetUrl(side.target, pathname, search, hash)); +} + +function installSourceMarkerListener(side: DomainMigrationSide): void { + let waiting = false; + window.addEventListener('storage', (event) => { + if (event.key !== DOMAIN_MIGRATION_MARKER_KEY || waiting) { + return; + } + if ( + !shouldForwardCompletedSource( + readDomainMigrationDiscovery(), + parseDomainMigrationMarker(event.newValue), + readDomainMigrationEnvironment(), + ) + ) { + return; + } + waiting = true; + forwardWhenIdle(side) + .catch((err) => { + logger.warn('Failed to forward a migrated source tab:', err); + }) + .finally(() => { + waiting = false; + }); + }); +} + +function completeOnSource(side: DomainMigrationSide, params: URLSearchParams): boolean { + const sessionStorage = getProtectedSessionStorage(); + const intent = readDomainMigrationIntent(sessionStorage, Date.now()); + clearDomainMigrationIntent(sessionStorage); + if (!intentConfirmsCompletion(intent, params.get('h'))) { + return navigate(sourceUrl(side, params.get('next'))); + } + markDomainMigrationCompleted(getProtectedLocalStorage(), side.target, Date.now()); + if (!environmentMayForward(readDomainMigrationEnvironment(), readDomainMigrationDiscovery())) { + return navigate(sourceUrl(side, params.get('next'))); + } + return navigate(`${side.target}${sanitizeNextPath(params.get('next'))}`); +} + +function startOnSource(side: DomainMigrationSide, params: URLSearchParams): boolean { + writeDomainMigrationIntent(getProtectedSessionStorage(), {at: Date.now()}); + return navigate(`${side.target}/migrate/begin?next=${encodeURIComponent(sanitizeNextPath(params.get('next')))}`); +} + +function failOnSource(side: DomainMigrationSide, params: URLSearchParams): boolean { + const sessionStorage = getProtectedSessionStorage(); + const intent = readDomainMigrationIntent(sessionStorage, Date.now()); + clearDomainMigrationIntent(sessionStorage); + if (intent !== null) { + logger.warn(`Domain migration failed on ${side.target}: ${params.get('reason') ?? 'unknown'}`); + revokeCompletedMarker(getProtectedLocalStorage()); + } + return navigate(sourceUrl(side, params.get('next'))); +} + +async function handleSource(side: DomainMigrationSide): Promise { + const params = new URLSearchParams(window.location.search); + const {pathname} = window.location; + if (pathname.startsWith('/migrate/') && !discoveryAllowsMigration()) { + clearDomainMigrationIntent(getProtectedSessionStorage()); + return navigate(sourceUrl(side, params.get('next'))); + } + switch (pathname) { + case '/migrate/export': + return exportFromSource(side, params.get('n')); + case '/migrate/start': + return startOnSource(side, params); + case '/migrate/done': + return completeOnSource(side, params); + case '/migrate/failed': + return failOnSource(side, params); + } + if (await forwardFromSource(side)) { + return true; + } + installSourceMarkerListener(side); + return false; +} + +function takePendingHandoff(): PendingHandoff | null { + const storage = getProtectedSessionStorage(); + try { + const raw = storage?.getItem(DOMAIN_MIGRATION_PENDING_KEY) ?? null; + storage?.removeItem(DOMAIN_MIGRATION_PENDING_KEY); + if (!raw) { + return null; + } + const value = JSON.parse(raw) as Partial; + if (typeof value.nonce !== 'string' || typeof value.next !== 'string' || typeof value.at !== 'number') { + return null; + } + return {nonce: value.nonce, next: value.next, at: value.at}; + } catch { + return null; + } +} + +async function importHandoff(side: DomainMigrationSide, fragment: URLSearchParams, nonce: string): Promise { + const handoffId = fragment.get('h'); + const key = fragment.get('k'); + if (!handoffId || !key || !BASE64URL_TOKEN_PATTERN.test(handoffId)) { + throw new DomainMigrationImportError('missing_handoff'); + } + const sealed = await redeemHandoff(side.target, handoffId, nonce); + let payload: DomainMigrationPayload | null; + try { + payload = parseDomainMigrationPayload(await decryptDomainMigrationPayload(sealed, key), side.source); + } catch { + payload = null; + } + if (payload === null) { + throw new DomainMigrationImportError('invalid_payload'); + } + try { + const [{default: accountStorage}, {default: RuntimeConfig}] = await Promise.all([ + import('@app/features/auth/state/AccountStorage'), + import('@app/features/app/state/RuntimeConfig'), + ]); + const instance = RuntimeConfig.getSnapshot(); + await accountStorage.importAccounts(payload.accounts.map((account) => rewriteImportedAccount(account, instance))); + } catch (err) { + logger.warn('Failed to import migrated accounts:', err); + throw new DomainMigrationImportError('import_failed'); + } + const storage = getProtectedLocalStorage(); + for (const [storageKey, value] of Object.entries(payload.local_storage)) { + if (!isExportableLocalStorageKey(storageKey)) { + continue; + } + try { + storage?.setItem(storageKey, value); + } catch (err) { + logger.warn(`Failed to import localStorage key ${storageKey}:`, err); + } + } + await restoreCustomSounds(payload.custom_sounds); + await restoreThemeLibrary(payload.theme_library); + if (payload.notification_permission === 'granted') { + try { + storage?.setItem(DOMAIN_MIGRATION_NOTIFICATIONS_KEY, 'granted'); + } catch {} + } + await persistDesktopAppOrigin(); +} + +async function persistDesktopAppOrigin(): Promise { + try { + await window.electron?.domainMigration?.setAppOrigin(window.location.origin); + } catch (err) { + logger.warn('Failed to persist the desktop app origin:', err); + } +} + +function doneUrl(side: DomainMigrationSide, next: string, handoffId: string | null): string { + const handoff = handoffId === null ? '' : `h=${encodeURIComponent(handoffId)}&`; + return `${side.source}/migrate/done?${handoff}next=${encodeURIComponent(next)}`; +} + +function failedUrl(side: DomainMigrationSide, reason: DomainMigrationFailureReason, next: string): string { + return `${side.source}/migrate/failed?reason=${reason}&next=${encodeURIComponent(next)}`; +} + +async function beginOnTarget(side: DomainMigrationSide, params: URLSearchParams): Promise { + const next = sanitizeNextPath(params.get('next')); + if (readDomainMigrationDiscovery()?.enabled !== true) { + return navigate(failedUrl(side, 'disabled', next)); + } + const started = params.get('start') === '1'; + if (await hasStoredAccount()) { + await persistDesktopAppOrigin(); + return navigate(params.get('resume') === '1' || started ? `${side.target}${next}` : doneUrl(side, next, null)); + } + if (started) { + return navigate(`${side.source}/migrate/start?next=${encodeURIComponent(next)}`); + } + const nonce = randomBase64Url(NONCE_BYTES); + const pending: PendingHandoff = {nonce, next, at: Date.now()}; + getProtectedSessionStorage()?.setItem(DOMAIN_MIGRATION_PENDING_KEY, JSON.stringify(pending)); + return navigate(`${side.source}/migrate/export?n=${nonce}`); +} + +async function completeOnTarget(side: DomainMigrationSide): Promise { + const fragment = new URLSearchParams(window.location.hash.slice(1)); + window.history.replaceState(window.history.state, '', `${window.location.pathname}${window.location.search}`); + const pending = takePendingHandoff(); + if (pending === null || Date.now() - pending.at > DOMAIN_MIGRATION_PENDING_MAX_AGE_MS) { + return navigate(failedUrl(side, 'no_pending', DOMAIN_MIGRATION_DEFAULT_NEXT_PATH)); + } + const next = sanitizeNextPath(pending.next); + const handoffId = fragment.get('h'); + if (await hasStoredAccount()) { + await persistDesktopAppOrigin(); + return navigate(doneUrl(side, next, handoffId)); + } + try { + await importHandoff(side, fragment, pending.nonce); + } catch (err) { + const reason = err instanceof DomainMigrationImportError ? err.reason : 'import_failed'; + logger.warn('Domain migration import failed:', err); + return navigate(failedUrl(side, reason, next)); + } + return navigate(doneUrl(side, next, handoffId)); +} + +async function handleTarget(side: DomainMigrationSide): Promise { + switch (window.location.pathname) { + case '/migrate/begin': + return beginOnTarget(side, new URLSearchParams(window.location.search)); + case '/migrate/complete': + return completeOnTarget(side); + } + return false; +} + +export async function runDomainMigrationPreMount(): Promise { + if (typeof window === 'undefined') { + return false; + } + const side = resolveDomainMigrationSide(window.location.origin); + if (side === null) { + return false; + } + try { + return side.role === 'source' ? await handleSource(side) : await handleTarget(side); + } catch (err) { + logger.error('Domain migration pre-mount step failed:', err); + if (side.role === 'target' && window.location.pathname.startsWith('/migrate/')) { + return navigate(failedUrl(side, 'target_error', DOMAIN_MIGRATION_DEFAULT_NEXT_PATH)); + } + return false; + } +} diff --git a/fluxer_app/src/features/app/domain_migration/DomainMigrationRollout.ts b/fluxer_app/src/features/app/domain_migration/DomainMigrationRollout.ts new file mode 100644 index 000000000..9c3d94af2 --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMigrationRollout.ts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import ExperimentAssignments from '@app/features/experiment/state/ExperimentAssignments'; +import {Logger} from '@app/features/platform/utils/AppLogger'; +import {readDomainMigrationAssignment} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; + +const logger = new Logger('DomainMigrationRollout'); + +class DomainMigrationRolloutSelector { + get enabled(): boolean { + try { + return readDomainMigrationAssignment(ExperimentAssignments.response).enabled; + } catch (err) { + logger.warn('Failed to resolve domain migration assignment:', err); + return false; + } + } +} + +export const DomainMigrationRollout = new DomainMigrationRolloutSelector(); + +export default DomainMigrationRollout; diff --git a/fluxer_app/src/features/app/domain_migration/DomainMigrationTrigger.ts b/fluxer_app/src/features/app/domain_migration/DomainMigrationTrigger.ts new file mode 100644 index 000000000..c391835cb --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMigrationTrigger.ts @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {Routes} from '@app/app/Routes'; +import { + browserSupportsRelatedOrigins, + readDomainMigrationDiscovery, + readDomainMigrationEnvironment, +} from '@app/features/app/domain_migration/DomainMigrationBrowser'; +import { + DOMAIN_MIGRATION_NOTIFICATIONS_KEY, + type DomainMigrationGateInput, + type DomainMigrationSide, + markDomainMigrationFailed, + readDomainMigrationMarker, + resolveDomainMigrationSide, + shouldStartDomainMigration, + writeDomainMigrationIntent, +} from '@app/features/app/domain_migration/DomainMigrationCore'; +import DomainMigrationRollout from '@app/features/app/domain_migration/DomainMigrationRollout'; +import {getProtectedLocalStorage, getProtectedSessionStorage} from '@app/features/platform/state/ProtectedWebStorage'; +import {Logger} from '@app/features/platform/utils/AppLogger'; +import * as NagbarCommands from '@app/features/ui/commands/NagbarCommands'; +import MediaEngine from '@app/features/voice/engine/MediaEngineFacade'; +import {compareShallow, reaction} from 'mobx'; + +const logger = new Logger('DomainMigrationTrigger'); + +const ONE_SHOT_ROUTE_PREFIXES: ReadonlyArray = [ + Routes.RESET_PASSWORD, + Routes.VERIFY_EMAIL, + Routes.AUTHORIZE_IP, + Routes.EMAIL_REVERT, + Routes.OAUTH_AUTHORIZE, + Routes.SSO_CALLBACK, + Routes.PREMIUM_CALLBACK, + Routes.AGE_VERIFICATION_CALLBACK, + Routes.CONNECTION_CALLBACK, +]; + +let started = false; +let navigating = false; + +function isVoiceActive(): boolean { + return MediaEngine.connected || MediaEngine.connecting; +} + +function isOneShotRoute(pathname: string): boolean { + return ONE_SHOT_ROUTE_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)); +} + +function readGateInput(assignmentEnabled: boolean, relatedOriginsSupported: boolean): DomainMigrationGateInput { + return { + environment: readDomainMigrationEnvironment(), + assignmentEnabled, + discovery: readDomainMigrationDiscovery(), + marker: readDomainMigrationMarker(getProtectedLocalStorage()), + now: Date.now(), + relatedOriginsSupported, + voiceActive: isVoiceActive(), + oneShotRoute: isOneShotRoute(window.location.pathname), + }; +} + +async function evaluateSource(side: DomainMigrationSide, assignmentEnabled: boolean): Promise { + if (navigating || !shouldStartDomainMigration(readGateInput(assignmentEnabled, true))) { + return; + } + const relatedOriginsSupported = await browserSupportsRelatedOrigins(); + if ( + navigating || + !shouldStartDomainMigration(readGateInput(DomainMigrationRollout.enabled, relatedOriginsSupported)) + ) { + return; + } + navigating = true; + markDomainMigrationFailed(getProtectedLocalStorage(), Date.now()); + writeDomainMigrationIntent(getProtectedSessionStorage(), {at: Date.now()}); + const next = `${window.location.pathname}${window.location.search}${window.location.hash}`; + window.location.replace(`${side.target}/migrate/begin?next=${encodeURIComponent(next)}`); +} + +function offerNotificationReenable(): void { + const storage = getProtectedLocalStorage(); + try { + if (storage?.getItem(DOMAIN_MIGRATION_NOTIFICATIONS_KEY) !== 'granted') { + return; + } + storage.removeItem(DOMAIN_MIGRATION_NOTIFICATIONS_KEY); + } catch { + return; + } + if (typeof Notification !== 'undefined' && Notification.permission === 'default') { + NagbarCommands.resetNagbar('desktopNotificationDismissed'); + } +} + +export function startDomainMigrationTrigger(): void { + if (started || typeof window === 'undefined') { + return; + } + const side = resolveDomainMigrationSide(window.location.origin); + if (side === null) { + return; + } + started = true; + if (side.role === 'target') { + setTimeout(offerNotificationReenable, 0); + return; + } + reaction( + () => ({enabled: DomainMigrationRollout.enabled, voiceActive: isVoiceActive()}), + ({enabled}) => { + evaluateSource(side, enabled).catch((err) => { + logger.warn('Domain migration trigger failed:', err); + }); + }, + {fireImmediately: true, equals: compareShallow}, + ); +} diff --git a/fluxer_app/src/features/app/domain_migration/DomainMovedNotice.ts b/fluxer_app/src/features/app/domain_migration/DomainMovedNotice.ts new file mode 100644 index 000000000..738e9065e --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMovedNotice.ts @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + detectDomainMigrationInstallKind, + readDomainMigrationDiscovery, +} from '@app/features/app/domain_migration/DomainMigrationBrowser'; +import { + DOMAIN_MIGRATION_MARKER_KEY, + DOMAIN_MIGRATION_MOVED_DISMISSED_KEY, + DOMAIN_MIGRATION_SOURCE_TO_TARGET, + type DomainMigrationInstallKind, + type DomainMigrationMarker, + type DomainMigrationSide, + readDomainMigrationMarker, + resolveDomainMigrationSide, + shouldShowDomainMovedNotice, +} from '@app/features/app/domain_migration/DomainMigrationCore'; +import DomainMigrationRollout from '@app/features/app/domain_migration/DomainMigrationRollout'; +import {getProtectedLocalStorage} from '@app/features/platform/state/ProtectedWebStorage'; +import {makeAutoObservable} from 'mobx'; + +const FALLBACK_TARGET = DOMAIN_MIGRATION_SOURCE_TO_TARGET['https://web.fluxer.app']; + +function readDismissedAt(): number | null { + try { + const value = Number(getProtectedLocalStorage()?.getItem(DOMAIN_MIGRATION_MOVED_DISMISSED_KEY) ?? Number.NaN); + return Number.isFinite(value) ? value : null; + } catch { + return null; + } +} + +class DomainMovedNotice { + readonly side: DomainMigrationSide | null; + readonly installKind: DomainMigrationInstallKind; + marker: DomainMigrationMarker | null; + dismissedAt: number | null; + + constructor() { + const origin = typeof window === 'undefined' ? '' : window.location.origin; + this.side = resolveDomainMigrationSide(origin); + this.installKind = detectDomainMigrationInstallKind(); + this.marker = readDomainMigrationMarker(getProtectedLocalStorage()); + this.dismissedAt = readDismissedAt(); + makeAutoObservable(this, {side: false, installKind: false}, {autoBind: true}); + if (this.side?.role === 'source') { + window.addEventListener('storage', this.handleStorage); + } + } + + get target(): string { + return this.side?.target ?? FALLBACK_TARGET; + } + + get targetHost(): string { + return new URL(this.target).host; + } + + shouldShow(now: number): boolean { + return shouldShowDomainMovedNotice({ + side: this.side, + installKind: this.installKind, + discovery: readDomainMigrationDiscovery(), + assignmentEnabled: DomainMigrationRollout.enabled, + marker: this.marker, + dismissedAt: this.dismissedAt, + now, + }); + } + + dismiss(now: number): void { + this.dismissedAt = now; + try { + getProtectedLocalStorage()?.setItem(DOMAIN_MIGRATION_MOVED_DISMISSED_KEY, String(now)); + } catch {} + } + + resetDismissal(): void { + this.dismissedAt = null; + try { + getProtectedLocalStorage()?.removeItem(DOMAIN_MIGRATION_MOVED_DISMISSED_KEY); + } catch {} + } + + private handleStorage(event: StorageEvent): void { + if (event.key === DOMAIN_MIGRATION_MARKER_KEY || event.key === null) { + this.marker = readDomainMigrationMarker(getProtectedLocalStorage()); + } + if (event.key === DOMAIN_MIGRATION_MOVED_DISMISSED_KEY || event.key === null) { + this.dismissedAt = readDismissedAt(); + } + } +} + +export default new DomainMovedNotice(); diff --git a/fluxer_app/src/features/app/domain_migration/DomainMovedStepsModal.module.css b/fluxer_app/src/features/app/domain_migration/DomainMovedStepsModal.module.css new file mode 100644 index 000000000..71cb39271 --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMovedStepsModal.module.css @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later */ + +.content { + gap: 1rem; +} + +.steps { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin: 0; + padding-left: 1.25rem; + color: var(--text-primary); + list-style: decimal; +} + +.step { + padding-left: 0.25rem; + line-height: 1.4; +} diff --git a/fluxer_app/src/features/app/domain_migration/DomainMovedStepsModal.tsx b/fluxer_app/src/features/app/domain_migration/DomainMovedStepsModal.tsx new file mode 100644 index 000000000..b1189ee87 --- /dev/null +++ b/fluxer_app/src/features/app/domain_migration/DomainMovedStepsModal.tsx @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import * as Modal from '@app/features/app/components/dialogs/Modal'; +import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; +import styles from '@app/features/app/domain_migration/DomainMovedStepsModal.module.css'; +import {CLOSE_DESCRIPTOR, COPY_LINK_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; +import {Button} from '@app/features/ui/button/Button'; +import * as ModalCommands from '@app/features/ui/commands/ModalCommands'; +import {modal} from '@app/features/ui/commands/ModalCommands'; +import * as TextCopyCommands from '@app/features/ui/commands/TextCopyCommands'; +import type {MessageDescriptor} from '@lingui/core'; +import {msg} from '@lingui/core/macro'; +import {useLingui} from '@lingui/react/macro'; +import {useCallback} from 'react'; + +export type DomainMovedStepsPlatform = 'ios' | 'mac' | 'install' | 'generic'; + +const TITLE_DESCRIPTOR = msg({ + message: 'Get the new {productName} app', + comment: 'Title of the modal that explains how to install the app from its new domain. productName is the app name.', +}); +const INTRO_DESCRIPTOR = msg({ + message: '{productName} now lives at {host}. Install it from there, then sign in with this app.', + comment: + 'Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com.', +}); +const INSTALL_INTRO_DESCRIPTOR = msg({ + message: '{productName} now lives at {host}. Install it from there.', + comment: + 'Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com.', +}); +const OPEN_IN_SAFARI_STEP_DESCRIPTOR = msg({ + message: 'Open {host} in Safari.', + comment: + 'First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser.', +}); +const OPEN_IN_BROWSER_STEP_DESCRIPTOR = msg({ + message: 'Open {host} in your browser.', + comment: 'First install step on other devices. host is the new domain, for example fluxer.com.', +}); +const ADD_TO_HOME_SCREEN_STEP_DESCRIPTOR = msg({ + message: 'Tap Share, then Add to Home Screen.', + comment: + 'Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language.', +}); +const ADD_TO_DOCK_STEP_DESCRIPTOR = msg({ + message: 'Choose File, then Add to Dock.', + comment: + 'Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language.', +}); +const INSTALL_FROM_BROWSER_STEP_DESCRIPTOR = msg({ + message: 'Install it from your browser menu.', + comment: 'Second install step on other devices. Refers to the install option in the browser menu.', +}); +const SIGN_IN_STEP_DESCRIPTOR = msg({ + message: + 'Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows.', + comment: + 'Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name.', +}); + +const OPEN_SIGNED_IN_STEP_DESCRIPTOR = msg({ + message: 'Open the new app. You are already signed in.', + comment: 'Third install step in a desktop browser that already moved the account to the new domain.', +}); + +type DomainMovedSteps = readonly [MessageDescriptor, MessageDescriptor, MessageDescriptor]; + +const PLATFORM_STEPS: Record = { + ios: [OPEN_IN_SAFARI_STEP_DESCRIPTOR, ADD_TO_HOME_SCREEN_STEP_DESCRIPTOR, SIGN_IN_STEP_DESCRIPTOR], + mac: [OPEN_IN_SAFARI_STEP_DESCRIPTOR, ADD_TO_DOCK_STEP_DESCRIPTOR, SIGN_IN_STEP_DESCRIPTOR], + install: [OPEN_IN_BROWSER_STEP_DESCRIPTOR, INSTALL_FROM_BROWSER_STEP_DESCRIPTOR, OPEN_SIGNED_IN_STEP_DESCRIPTOR], + generic: [OPEN_IN_BROWSER_STEP_DESCRIPTOR, INSTALL_FROM_BROWSER_STEP_DESCRIPTOR, SIGN_IN_STEP_DESCRIPTOR], +}; + +interface DomainMovedStepsModalProps { + target: string; + platform: DomainMovedStepsPlatform; +} + +function DomainMovedStepsModal({target, platform}: DomainMovedStepsModalProps) { + const {i18n} = useLingui(); + const host = new URL(target).host; + const values = {host, productName: PRODUCT_NAME}; + const [first, second, third] = PLATFORM_STEPS[platform]; + const handleCopy = useCallback(() => { + void TextCopyCommands.copy(i18n, `${target}/`); + }, [i18n, target]); + return ( + + + + + + {i18n._(platform === 'install' ? INSTALL_INTRO_DESCRIPTOR : INTRO_DESCRIPTOR, values)} + +
    +
  1. + {i18n._(first, values)} +
  2. +
  3. + {i18n._(second, values)} +
  4. +
  5. + {i18n._(third, values)} +
  6. +
+
+
+ + + + +
+ ); +} + +export function showDomainMovedStepsModal(target: string, platform: DomainMovedStepsPlatform): void { + ModalCommands.push( + modal(() => ( + + )), + ); +} diff --git a/fluxer_app/src/features/app/state/Updater.ts b/fluxer_app/src/features/app/state/Updater.ts index b0b7575ac..8c529c63e 100644 --- a/fluxer_app/src/features/app/state/Updater.ts +++ b/fluxer_app/src/features/app/state/Updater.ts @@ -54,7 +54,12 @@ const MIN_CHECK_INTERVAL_MS = 60 * 1000; const MANUAL_DOWNLOAD_REFRESH_TIMEOUT_MS = 5 * 1000; const VERSION_ENDPOINT = '/version.json'; const CURRENT_BUILD_VERSION = Config.PUBLIC_BUILD_VERSION ?? null; -const ALLOWED_WEB_UPDATE_HOSTS = new Set(['web.fluxer.app', 'web.canary.fluxer.app']); +const ALLOWED_WEB_UPDATE_HOSTS = new Set([ + 'web.fluxer.app', + 'web.canary.fluxer.app', + 'fluxer.com', + 'canary.fluxer.com', +]); function normalizeUpdaterContext(context: NativeUpdaterEvent['context']): UpdaterContext { switch (context) { diff --git a/fluxer_app/src/features/auth/components/modals/PasskeyNameModal.tsx b/fluxer_app/src/features/auth/components/modals/PasskeyNameModal.tsx index 7aa44ae34..1a96aee11 100644 --- a/fluxer_app/src/features/auth/components/modals/PasskeyNameModal.tsx +++ b/fluxer_app/src/features/auth/components/modals/PasskeyNameModal.tsx @@ -1,6 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import * as Modal from '@app/features/app/components/dialogs/Modal'; +import { + PASSKEY_DOMAIN_UNSUPPORTED_DESCRIPTOR, + PasskeyDomainUnsupportedError, +} from '@app/features/auth/utils/WebAuthnUtils'; import {HttpError} from '@app/features/platform/types/EndpointError'; import {Button} from '@app/features/ui/button/Button'; import * as ModalCommands from '@app/features/ui/commands/ModalCommands'; @@ -43,6 +47,8 @@ export const PasskeyNameModal = observer(({onSubmit}: {onSubmit: (name: string) } catch (error) { if (error instanceof HttpError) { FormUtils.handleError(i18n, form, error, 'name'); + } else if (error instanceof PasskeyDomainUnsupportedError) { + form.setError('name', {type: 'server', message: i18n._(PASSKEY_DOMAIN_UNSUPPORTED_DESCRIPTOR)}); } else { form.setError('name', {type: 'server', message: FormUtils.extractErrorMessage(i18n, error)}); } diff --git a/fluxer_app/src/features/auth/components/modals/SudoVerificationModal.tsx b/fluxer_app/src/features/auth/components/modals/SudoVerificationModal.tsx index 18d8672a3..405b2f1d7 100644 --- a/fluxer_app/src/features/auth/components/modals/SudoVerificationModal.tsx +++ b/fluxer_app/src/features/auth/components/modals/SudoVerificationModal.tsx @@ -126,6 +126,10 @@ const SudoVerificationModal: React.FC = observer(() => { setWebAuthnError(i18n._(PASSKEYS_REQUIRE_A_SIGNED_MACOS_BUNDLE_WITH_A_DESCRIPTOR)); return; } + if (err instanceof WebAuthnUtils.PasskeyDomainUnsupportedError) { + setWebAuthnError(i18n._(WebAuthnUtils.PASSKEY_DOMAIN_UNSUPPORTED_DESCRIPTOR)); + return; + } setWebAuthnError(i18n._(COULDN_T_VERIFY_WITH_PASSKEY_PLEASE_TRY_AGAIN_DESCRIPTOR)); } }; diff --git a/fluxer_app/src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts b/fluxer_app/src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts index 3f256c258..312739531 100644 --- a/fluxer_app/src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts +++ b/fluxer_app/src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts @@ -26,6 +26,7 @@ import { selectAuthorizePhase, transitionAuthorizeSnapshot, } from '@app/features/auth/components/pages/oauth_authorize_page/state/authorizeMachine'; +import {getDefaultLandingPath} from '@app/features/navigation/utils/DefaultLandingUtils'; import type {BotPermissionOption} from '@app/features/permissions/utils/PermissionUtils'; import {http} from '@app/features/platform/transport/RestTransport'; import {failureMessage} from '@app/features/platform/utils/ResponseInspection'; @@ -425,7 +426,7 @@ export function useAuthorizeFlow(options: UseAuthorizeFlowOptions = {}): Authori window.location.href = url.toString(); return; } - window.location.href = '/'; + window.location.href = getDefaultLandingPath(); } catch (err) { logger.error('Failed to redirect on cancel', err); setSubmitting(null); diff --git a/fluxer_app/src/features/auth/flow/AuthLoginLayout.tsx b/fluxer_app/src/features/auth/flow/AuthLoginLayout.tsx index 9838e81cd..9e3049b1c 100644 --- a/fluxer_app/src/features/auth/flow/AuthLoginLayout.tsx +++ b/fluxer_app/src/features/auth/flow/AuthLoginLayout.tsx @@ -1,5 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; +import {detectDomainMigrationInstallKind} from '@app/features/app/domain_migration/DomainMigrationBrowser'; import RuntimeConfig from '@app/features/app/state/RuntimeConfig'; import * as AuthenticationCommands from '@app/features/auth/commands/AuthenticationCommands'; import {AccountSelector} from '@app/features/auth/components/accounts/AccountSelector'; @@ -17,6 +19,10 @@ import AuthLoginPasskeyActions, { AuthLoginDivider, } from '@app/features/auth/flow/auth_login_core/AuthLoginPasskeyActions'; import {isApprovalFlowMode, useDesktopHandoffFlow} from '@app/features/auth/flow/auth_login_core/useDesktopHandoffFlow'; +import { + SIGN_IN_WITH_OLD_APP_DESCRIPTOR, + showBrowserLoginHandoffModal, +} from '@app/features/auth/flow/BrowserLoginHandoffModal'; import DesktopHandoffAccountSelector from '@app/features/auth/flow/DesktopHandoffAccountSelector'; import {ConnectedHandoffApprovalFlow} from '@app/features/auth/flow/HandoffApprovalFlow'; import IpAuthorizationScreen from '@app/features/auth/flow/IpAuthorizationScreen'; @@ -28,6 +34,7 @@ import { type LoginSuccessPayload, startSsoLogin, } from '@app/features/auth/state/AuthFlow'; +import {shouldOfferOldAppSignIn} from '@app/features/auth/utils/OldAppSignIn'; import {NEED_ACCOUNT_DESCRIPTOR, SIGN_IN_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; import * as RouterUtils from '@app/features/navigation/utils/RouterUtils'; import {useLocation} from '@app/features/platform/components/router/RouterReact'; @@ -64,6 +71,11 @@ const FORGOT_PASSWORD_DESCRIPTOR = msg({ message: 'Forgot your password?', comment: 'Authentication link label that opens password recovery.', }); +const OLD_APP_SIGN_IN_HINT_DESCRIPTOR = msg({ + message: 'Approve this app from the {productName} app you already use. No password needed.', + comment: + 'Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name.', +}); const SIGN_IN_VIA_BROWSER_DESCRIPTOR = msg({ message: 'Sign in via browser', comment: 'Passkey sign-in action that opens the browser flow from the desktop app.', @@ -164,6 +176,28 @@ export const AuthLoginLayout = observer(function AuthLoginLayout({ }); const showBrowserPasskey = IS_DEV || isDesktop(); const passkeyControlsDisabled = isLoading || Boolean(form.isSubmitting) || isPasskeyLoading; + const offerOldAppSignIn = useMemo( + () => + !desktopHandoff && + shouldOfferOldAppSignIn({ + origin: window.location.origin, + installKind: detectDomainMigrationInstallKind(), + hasStoredAccounts, + }), + [desktopHandoff, hasStoredAccounts], + ); + const handleOldAppSignIn = useCallback(() => { + showBrowserLoginHandoffModal( + async (payload) => { + await handleLoginSuccess(payload); + if (redirectPath) { + RouterUtils.replaceWith(redirectPath); + } + }, + undefined, + 'old_app', + ); + }, [handleLoginSuccess, redirectPath]); const handleIpAuthorizationComplete = useCallback( async (payload: LoginSuccessPayload) => { await handleLoginSuccess(payload); @@ -327,6 +361,21 @@ export const AuthLoginLayout = observer(function AuthLoginLayout({ {switchError} ) : null} + {offerOldAppSignIn ? ( +
+ +
+ {i18n._(OLD_APP_SIGN_IN_HINT_DESCRIPTOR, {productName: PRODUCT_NAME})} +
+
+ ) : null} {ssoConfig?.enabled ? (
- + {isOldAppVariant ? null : ( + + )} ); @@ -160,6 +186,7 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillEmail}: BrowserLog export function showBrowserLoginHandoffModal( onSuccess: (payload: LoginSuccessPayload) => Promise, prefillEmail?: string, + variant: BrowserLoginHandoffVariant = 'browser', ): void { ModalCommands.push( modal(() => ( @@ -168,6 +195,7 @@ export function showBrowserLoginHandoffModal( await onSuccess(payload); }} prefillEmail={prefillEmail} + variant={variant} data-flx="auth.flow.browser-login-handoff-modal.show-browser-login-handoff-modal.browser-login-handoff-modal" /> )), diff --git a/fluxer_app/src/features/auth/flow/DesktopHandoffAccountSelector.tsx b/fluxer_app/src/features/auth/flow/DesktopHandoffAccountSelector.tsx index d7fb500b1..8b9e4c3a4 100644 --- a/fluxer_app/src/features/auth/flow/DesktopHandoffAccountSelector.tsx +++ b/fluxer_app/src/features/auth/flow/DesktopHandoffAccountSelector.tsx @@ -66,7 +66,7 @@ const DesktopHandoffAccountSelector = observer(function DesktopHandoffAccountSel Choose an account} - description={Select the account you want to sign in with on the desktop app.} + description={Select the account you want to sign in with on your new device.} disabled={isLoading} error={error} clickableRows diff --git a/fluxer_app/src/features/auth/flow/HandoffCodeDisplay.tsx b/fluxer_app/src/features/auth/flow/HandoffCodeDisplay.tsx index 235b47df7..f91d35811 100644 --- a/fluxer_app/src/features/auth/flow/HandoffCodeDisplay.tsx +++ b/fluxer_app/src/features/auth/flow/HandoffCodeDisplay.tsx @@ -6,7 +6,7 @@ import {Button} from '@app/features/ui/button/Button'; import * as TextCopyCommands from '@app/features/ui/commands/TextCopyCommands'; import {Trans, useLingui} from '@lingui/react/macro'; import {CheckCircleIcon, ClipboardIcon} from '@phosphor-icons/react'; -import {useCallback, useEffect, useRef, useState} from 'react'; +import {type ReactNode, useCallback, useEffect, useRef, useState} from 'react'; interface HandoffCodeDisplayProps { code: string | null; @@ -14,6 +14,7 @@ interface HandoffCodeDisplayProps { isGenerating: boolean; error: string | null; onRetry?: () => void; + description?: ReactNode; } function useCountdown(expiresAt: string | null): number | null { @@ -48,7 +49,14 @@ function useCountdown(expiresAt: string | null): number | null { return remaining; } -export function HandoffCodeDisplay({code, expiresAt, isGenerating, error, onRetry}: HandoffCodeDisplayProps) { +export function HandoffCodeDisplay({ + code, + expiresAt, + isGenerating, + error, + onRetry, + description, +}: HandoffCodeDisplayProps) { const {i18n} = useLingui(); const [copied, setCopied] = useState(false); const remaining = useCountdown(expiresAt); @@ -121,7 +129,7 @@ export function HandoffCodeDisplay({code, expiresAt, isGenerating, error, onRetr Your code is ready

- Enter this code in your browser to complete sign-in. + {description ?? Enter this code in your browser to complete sign-in.}

diff --git a/fluxer_app/src/features/auth/hooks/useLoginFlow.ts b/fluxer_app/src/features/auth/hooks/useLoginFlow.ts index 538cf761d..6f5155b0b 100644 --- a/fluxer_app/src/features/auth/hooks/useLoginFlow.ts +++ b/fluxer_app/src/features/auth/hooks/useLoginFlow.ts @@ -19,7 +19,9 @@ import { import * as WebAuthnUtils from '@app/features/auth/utils/WebAuthnUtils'; import * as RouterUtils from '@app/features/navigation/utils/RouterUtils'; import {Logger} from '@app/features/platform/utils/AppLogger'; +import * as ToastCommands from '@app/features/ui/commands/ToastCommands'; import {isDesktop} from '@app/features/ui/utils/NativeUtils'; +import {useLingui} from '@lingui/react/macro'; import {useCallback, useMemo, useRef, useState} from 'react'; const logger = Logger.create('useLoginFlow'); @@ -87,6 +89,7 @@ export function useLoginFormController({ onRequireMfa, onRequireIpAuthorization, }: LoginFormControllerOptions) { + const {i18n} = useLingui(); const [isPasskeyLoading, setIsPasskeyLoading] = useState(false); const {form, isLoading, fieldErrors, error} = useAuthForm({ initialValues: {email: '', password: ''}, @@ -129,6 +132,10 @@ export function useLoginFormController({ return; } logger.error('Passkey login failed', err); + if (err instanceof WebAuthnUtils.PasskeyDomainUnsupportedError) { + ToastCommands.error(i18n._(WebAuthnUtils.PASSKEY_DOMAIN_UNSUPPORTED_DESCRIPTOR)); + return; + } const userCancelled = err instanceof DOMException && (err.name === 'NotAllowedError' || err.name === 'AbortError'); if (isDesktop() && !userCancelled) { @@ -137,7 +144,7 @@ export function useLoginFormController({ } finally { setIsPasskeyLoading(false); } - }, [inviteCode, onLoginSuccess, redirectPath, handleDesktopPasskeyHandoff]); + }, [inviteCode, onLoginSuccess, redirectPath, handleDesktopPasskeyHandoff, i18n]); return { form, isLoading, @@ -161,6 +168,7 @@ interface MfaControllerOptions { } export function useMfaController({ticket, methods, inviteCode, onLoginSuccess}: MfaControllerOptions) { + const {i18n} = useLingui(); const [isWebAuthnLoading, setIsWebAuthnLoading] = useState(false); const {form, isLoading, fieldErrors} = useAuthForm({ initialValues: {code: ''}, @@ -193,10 +201,13 @@ export function useMfaController({ticket, methods, inviteCode, onLoginSuccess}: await onLoginSuccess?.(response); } catch (error) { logger.error('WebAuthn MFA failed', error); + if (error instanceof WebAuthnUtils.PasskeyDomainUnsupportedError) { + ToastCommands.error(i18n._(WebAuthnUtils.PASSKEY_DOMAIN_UNSUPPORTED_DESCRIPTOR)); + } } finally { setIsWebAuthnLoading(false); } - }, [inviteCode, onLoginSuccess, ticket]); + }, [inviteCode, onLoginSuccess, ticket, i18n]); const supports = useMemo( () => ({totp: methods.totp, webauthn: methods.webauthn, backupCodes: methods.backupCodes}), [methods.totp, methods.webauthn, methods.backupCodes], diff --git a/fluxer_app/src/features/auth/state/AccountStorage.ts b/fluxer_app/src/features/auth/state/AccountStorage.ts index f2d104f73..b0d3b803c 100644 --- a/fluxer_app/src/features/auth/state/AccountStorage.ts +++ b/fluxer_app/src/features/auth/state/AccountStorage.ts @@ -449,6 +449,13 @@ class AccountStorage { } } + async importAccounts(records: ReadonlyArray): Promise { + await this.ensureDb(); + for (const record of records) { + await this.putRecord(this.sanitizeRecord(record)); + } + } + async deleteAccount(userId: string): Promise { await this.ensureDb(); if (!userId) { diff --git a/fluxer_app/src/features/auth/utils/OldAppSignIn.test.ts b/fluxer_app/src/features/auth/utils/OldAppSignIn.test.ts new file mode 100644 index 000000000..5e0e754b9 --- /dev/null +++ b/fluxer_app/src/features/auth/utils/OldAppSignIn.test.ts @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {shouldOfferOldAppSignIn} from '@app/features/auth/utils/OldAppSignIn'; +import {describe, expect, it} from 'vitest'; + +describe('shouldOfferOldAppSignIn', () => { + it('offers the old app sign-in in an installed WebKit app on an official target origin', () => { + expect( + shouldOfferOldAppSignIn({origin: 'https://fluxer.com', installKind: 'webkit', hasStoredAccounts: false}), + ).toBe(true); + expect( + shouldOfferOldAppSignIn({origin: 'https://canary.fluxer.com', installKind: 'webkit', hasStoredAccounts: false}), + ).toBe(true); + }); + + it('offers the old app sign-in in an installed Android Chromium app on an official target origin', () => { + expect( + shouldOfferOldAppSignIn({ + origin: 'https://fluxer.com', + installKind: 'chromium-android', + hasStoredAccounts: false, + }), + ).toBe(true); + }); + + it('does not offer it once an account is stored', () => { + expect( + shouldOfferOldAppSignIn({origin: 'https://fluxer.com', installKind: 'webkit', hasStoredAccounts: true}), + ).toBe(false); + }); + + it('does not offer it outside the installed WebKit and Android Chromium apps', () => { + for (const installKind of ['none', 'chromium-desktop', 'firefox', 'other'] as const) { + expect(shouldOfferOldAppSignIn({origin: 'https://fluxer.com', installKind, hasStoredAccounts: false})).toBe( + false, + ); + } + }); + + it('does not offer it on source or self-hosted origins', () => { + for (const origin of ['https://web.fluxer.app', 'https://web.canary.fluxer.app', 'https://chat.example.com']) { + expect(shouldOfferOldAppSignIn({origin, installKind: 'webkit', hasStoredAccounts: false})).toBe(false); + } + }); +}); diff --git a/fluxer_app/src/features/auth/utils/OldAppSignIn.ts b/fluxer_app/src/features/auth/utils/OldAppSignIn.ts new file mode 100644 index 000000000..d27a444b6 --- /dev/null +++ b/fluxer_app/src/features/auth/utils/OldAppSignIn.ts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + type DomainMigrationInstallKind, + resolveDomainMigrationSide, +} from '@app/features/app/domain_migration/DomainMigrationCore'; + +const OLD_APP_SIGN_IN_INSTALL_KINDS: ReadonlySet = new Set([ + 'webkit', + 'chromium-android', +]); + +export interface OldAppSignInInput { + origin: string; + installKind: DomainMigrationInstallKind; + hasStoredAccounts: boolean; +} + +export function shouldOfferOldAppSignIn({origin, installKind, hasStoredAccounts}: OldAppSignInInput): boolean { + if (hasStoredAccounts || !OLD_APP_SIGN_IN_INSTALL_KINDS.has(installKind)) { + return false; + } + return resolveDomainMigrationSide(origin)?.role === 'target'; +} diff --git a/fluxer_app/src/features/auth/utils/WebAuthnUtils.ts b/fluxer_app/src/features/auth/utils/WebAuthnUtils.ts index 8f9b6b7b9..eb9e4aef0 100644 --- a/fluxer_app/src/features/auth/utils/WebAuthnUtils.ts +++ b/fluxer_app/src/features/auth/utils/WebAuthnUtils.ts @@ -4,6 +4,7 @@ import {promptForSecurityKeyPin} from '@app/features/auth/components/modals/Pass import {parsePasskeyPinFailure} from '@app/features/auth/utils/PasskeyPinErrors'; import {Platform} from '@app/features/platform/types/Platform'; import {getElectronAPI} from '@app/features/ui/utils/NativeUtils'; +import {msg} from '@lingui/core/macro'; import { type AuthenticationResponseJSON, browserSupportsWebAuthn, @@ -14,6 +15,47 @@ import { startRegistration, } from '@simplewebauthn/browser'; +export const PASSKEY_DOMAIN_UNSUPPORTED_DESCRIPTOR = msg({ + message: + 'Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code.', + comment: + 'Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain.', +}); +const RP_MISMATCH_MESSAGE_PATTERN = /relying party|\brp ?id\b|\bdomain\b|\borigin\b/i; + +export class PasskeyDomainUnsupportedError extends Error { + constructor() { + super('Passkeys are not supported on this domain in this browser'); + this.name = 'PasskeyDomainUnsupportedError'; + } +} + +function isRelatedOriginFailure(error: unknown, rpId: string | undefined): boolean { + if (!rpId || !(error instanceof Error)) { + return false; + } + const hostname = window.location.hostname.toLowerCase(); + const normalizedRpId = rpId.toLowerCase(); + if (hostname === normalizedRpId || hostname.endsWith(`.${normalizedRpId}`)) { + return false; + } + if (error.name === 'SecurityError') { + return true; + } + return error.name === 'NotAllowedError' && RP_MISMATCH_MESSAGE_PATTERN.test(error.message); +} + +async function runBrowserCeremony(rpId: string | undefined, run: () => Promise): Promise { + try { + return await run(); + } catch (error) { + if (isRelatedOriginFailure(error, rpId)) { + throw new PasskeyDomainUnsupportedError(); + } + throw error; + } +} + async function runNativeCeremonyWithPinSupport(run: (requestContext?: {pin?: string}) => Promise): Promise { try { return await run(); @@ -54,7 +96,7 @@ export async function performRegistration( return runNativeCeremonyWithPinSupport((requestContext) => passkeyRegister(options, requestContext)); } } - return await startRegistration({optionsJSON: options}); + return await runBrowserCeremony(options.rp.id, () => startRegistration({optionsJSON: options})); } export async function performAuthentication( @@ -69,5 +111,5 @@ export async function performAuthentication( return runNativeCeremonyWithPinSupport((requestContext) => passkeyAuthenticate(options, requestContext)); } } - return await startAuthentication({optionsJSON: options}); + return await runBrowserCeremony(options.rpId, () => startAuthentication({optionsJSON: options})); } diff --git a/fluxer_app/src/features/devtools/components/NagbarControls.ts b/fluxer_app/src/features/devtools/components/NagbarControls.ts index 9dc45bf8e..fafe4e50f 100644 --- a/fluxer_app/src/features/devtools/components/NagbarControls.ts +++ b/fluxer_app/src/features/devtools/components/NagbarControls.ts @@ -73,6 +73,11 @@ const INVITES_DISABLED_NAGBAR_DESCRIPTOR = msg({ message: 'Invites disabled nagbar', comment: 'Developer control label for the invites-disabled banner.', }); +const DOMAIN_MOVED_NAGBAR_DESCRIPTOR = msg({ + message: 'Domain moved nagbar', + comment: + 'Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain.', +}); const GUILD_MFA_REQUIREMENT_NAGBAR_DESCRIPTOR = msg({ message: 'Community MFA requirement nagbar', comment: 'Developer control label for the community MFA requirement banner.', @@ -409,4 +414,16 @@ export const getNagbarControls = (): Array => [ forceShowDisabled: (state) => state.forceGuildMfaRequirement, forceHideDisabled: (state) => state.forceHideGuildMfaRequirement, }, + { + key: 'forceDomainMoved', + label: DOMAIN_MOVED_NAGBAR_DESCRIPTOR, + forceKey: 'forceDomainMoved', + forceHideKey: 'forceHideDomainMoved', + resetKeys: ['forceDomainMoved'], + status: (state) => + state.forceDomainMoved ? FORCE_ENABLED : state.forceHideDomainMoved ? FORCE_DISABLED : USING_ACTUAL_STATE, + useActualDisabled: (state) => !state.forceDomainMoved && !state.forceHideDomainMoved, + forceShowDisabled: (state) => state.forceDomainMoved, + forceHideDisabled: (state) => state.forceHideDomainMoved, + }, ]; diff --git a/fluxer_app/src/features/gateway/events/GatewayReady.ts b/fluxer_app/src/features/gateway/events/GatewayReady.ts index 8b4b2f679..d39526933 100644 --- a/fluxer_app/src/features/gateway/events/GatewayReady.ts +++ b/fluxer_app/src/features/gateway/events/GatewayReady.ts @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +import {startDomainMigrationTrigger} from '@app/features/app/domain_migration/DomainMigrationTrigger'; import Initialization from '@app/features/app/state/Initialization'; import AccountManager from '@app/features/auth/state/AccountManager'; import accountStorage from '@app/features/auth/state/AccountStorage'; @@ -184,4 +185,5 @@ function handleReadyInternal(data: ReadyPayload, context: GatewayHandlerContext) Initialization.setReady(); context.setReady(); Messages.handleGatewayReady(); + startDomainMigrationTrigger(); } diff --git a/fluxer_app/src/features/gift/utils/GiftCodeUtils.ts b/fluxer_app/src/features/gift/utils/GiftCodeUtils.ts index f11a4443e..ae11f5ba3 100644 --- a/fluxer_app/src/features/gift/utils/GiftCodeUtils.ts +++ b/fluxer_app/src/features/gift/utils/GiftCodeUtils.ts @@ -8,6 +8,8 @@ const OFFICIAL_GIFT_URL_BASES = Object.freeze([ 'https://canary.fluxer.app/gift', 'https://web.fluxer.app/gift', 'https://web.canary.fluxer.app/gift', + 'https://fluxer.com/gift', + 'https://canary.fluxer.com/gift', 'https://fluxer.gift', 'https://fluxer.gift/gift', ]); diff --git a/fluxer_app/src/features/i18n/locales/ar/messages.po b/fluxer_app/src/features/i18n/locales/ar/messages.po index abcf56e93..ba948f1e1 100644 --- a/fluxer_app/src/features/i18n/locales/ar/messages.po +++ b/fluxer_app/src/features/i18n/locales/ar/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, zero {# رسالة محظورة} one {رسالة محظ msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, zero {لم يُعثر على أي مجتمع} one {تم العثور على مجتمع واحد} two {تم العثور على مجتمعين} few {تم العثور على # مجتمعات} many {تم العثور على # مجتمعًا} other {تم العثور على # مجتمع}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, zero {# مجتمع غير متاح مؤقتًا بسبب عطل في مكثف التدفق.} one {مجتمع واحد غير متاح مؤقتًا بسبب عطل في مكثف التدفق.} two {مجتمعان غير متاحين مؤقتًا بسبب عطل في مكثف التدفق.} few {# مجتمعات غير متاحة مؤقتًا بسبب عطل في مكثف التدفق.} many {# مجتمعًا غير متاح مؤقتًا بسبب عطل في مكثف التدفق.} other {# مجتمع غير متاح مؤقتًا بسبب عطل في مكثف التدفق.}}" @@ -1692,6 +1692,26 @@ msgstr "اكتشف {productName} هجومًا محتملاً في هذا الم msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "اكتشف {productName} هجومًا محتملاً، لذا لا يمكن للمستخدمين الجدد الانضمام الآن." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "تم نقل {productName} إلى {host}. أضفه إلى شاشتك الرئيسية أو Dock، ثم سجّل الدخول باستخدام هذا التطبيق." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "تم نقل {productName} إلى {host}. قم بتثبيته من متصفحك، ثم سجّل الدخول باستخدام هذا التطبيق." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "تم نقل {productName} إلى {host}. قم بتثبيت التطبيق الجديد وستكون مسجلاً الدخول بالفعل." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "تم نقل {productName} إلى {host}. افتحه في متصفحك لتثبيت التطبيق الجديد." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "يحتاج {productName} إلى الوصول إلى الميكروفون msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "يحتاج {productName} إلى إذن تسجيل الشاشة. اسمح بتسجيل الشاشة في إعدادات خصوصية نظام التشغيل الخاص بك وأعد تشغيل التطبيق." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} موجود الآن على {host}. قم بتثبيته من هناك، ثم سجّل الدخول باستخدام هذا التطبيق." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "تم نقل {productName} الآن إلى {host}. قم بتثبيته من هناك." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "استطلاع" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "يلزم توفير redirect_uri عندما لا يكون نطاق bot هو النطاق الوحيد." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "إضافة بوت إلى مجتمع بالأذونات المطلوبة" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "إضافة تعليق (اختياري)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "أضف مفتاح YouTube Data API لإثراء روابط YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "إضافة حساب" @@ -4379,6 +4409,11 @@ msgstr "الموافقة مطلوبة" msgid "Approve" msgstr "موافقة" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "وافق على هذا التطبيق من تطبيق {productName} الذي تستخدمه بالفعل. لا حاجة لكلمة مرور." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "فشل التفويض" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "فشل التفويض. حاول مرة أخرى." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "أذونات البوت" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "لا يمكن أن تكون أذونات البوت سالبة." @@ -6329,10 +6364,10 @@ msgstr "لا يمكن تحميل الملفات هنا" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "لا يمكن تحميل الملفات هنا" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "اختيار الخلفية" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "اختر guild_id أو channel_id، وليس كليهما." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "اختر \"ملف\"، ثم \"إضافة إلى Dock\"." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "كتلة برمجية" msgid "Code blocks" msgstr "كتل التعليمات البرمجية" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "انتهت صلاحية الرمز" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "الأوامر" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "التعليقات معطّلة لأن الوضع البطيء مفعّل في قناة واحدة أو أكثر من القنوات المحددة. سيحظر الوضع البطيء التعليق مباشرةً بعد إعادة التوجيه." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "التعليقات معطّلة لأن الوضع البطيء مفعّل في {forwardChannelDisplayName}. سيحظر الوضع البطيء التعليق مباشرةً بعد إعادة التوجيه." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "التعليقات غير متاحة عندما يكون الوضع البطيء مفعّلًا." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "إشعارات رسائل المجتمع" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "شريط تذكير بمتطلبات المصادقة متعددة العوامل للمجتمع" @@ -9182,7 +9222,7 @@ msgstr "متابعة الإبلاغ عن المجتمع" msgid "Continue to report user" msgstr "متابعة الإبلاغ عن المستخدم" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "المتابعة باستخدام مفتاح المرور" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "ينسّق الاقتحامات الجماعية أو الهجمات المنظمة أو المضايقات ضد أشخاص أو مجتمعات." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "تم النسخ" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "نسخ رابط القناة" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "نسخ الرمز" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "الاستخدامات الحالية:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "مُغلق حاليًا" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "معروض حاليًا" @@ -12555,6 +12595,11 @@ msgstr "لا يحظر المستخدم. يمكنك إعادة فتح المحا msgid "Domain" msgstr "النطاق" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "تم نقل النطاق" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "الهولندية" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "مثال: YubiKey، أو iPhone، أو حاسوب العمل" @@ -14056,10 +14101,14 @@ msgstr "أدخل رمز PIN لمفتاح الأمان للمتابعة." msgid "Enter the SMS code" msgstr "أدخل رمز الرسالة النصية القصيرة" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "أدخل هذا الرمز في متصفحك لإكمال تسجيل الدخول." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "أدخل هذا الرمز في تطبيق {PRODUCT_NAME} القديم لديك لإكمال تسجيل الدخول." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "اضغط Enter <0><1>للحفظ" @@ -14441,7 +14490,7 @@ msgstr "تنتهي صلاحيته في {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "تنتهي صلاحيته بين {earliest} و{latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "تنتهي الصلاحية خلال {remaining} ث" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "تعذّر بدء تسجيل الدخول الموحّد" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "تعذّر تبديل الحساب" @@ -15413,17 +15462,17 @@ msgstr "لنفسي" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "لهذا التحقق، سترسل لنا رسالة نصية بدلاً من أن تستقبلها منا." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "حفاظًا على أمانك، أكّد هويتك للمتابعة." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "معطل قسريًا" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "مفعل قسريًا" @@ -15540,7 +15589,7 @@ msgstr "نسيت كلمة المرور" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "هل نسيت كلمة المرور؟" @@ -15571,7 +15620,7 @@ msgstr "إعادة توجيه الوسائط" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "إعادة توجيه الرسالة" @@ -15965,11 +16014,11 @@ msgstr "أنشئ رابطًا قابلاً للمشاركة أو انسخ CSS ا msgid "Generate invite link" msgstr "إنشاء رابط دعوة" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "إنشاء رمز جديد" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "جارٍ إنشاء الرمز…" @@ -16045,6 +16094,11 @@ msgstr "احصل على تطبيق {productName} لسطح المكتب لاست msgid "Get the desktop app" msgstr "احصل على تطبيق سطح المكتب" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "احصل على تطبيق {productName} الجديد" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "إدراج رمز تعبيري" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "ثبّت {productName} للحصول على أداة اختيار المصدر الكاملة وعناصر تحكم أكثر سلاسة لمشاركة الشاشة." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "ثبّته من قائمة متصفحك." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "تثبيت التطبيق الجديد" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "طلب تفويض غير صالح" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "أذونات البوت غير صالحة." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "الرمز غير صالح أو انتهت صلاحيته. حاول مرة أخرى." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "قيمة redirect_uri غير صالحة" @@ -19332,6 +19396,11 @@ msgstr "رابط" msgid "Link (mature)" msgstr "رابط (محتوى للبالغين)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "ربط جهاز جديد" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "إعدادات عرض الرسائل متاحة على سطح المكت msgid "Message editing" msgstr "تعديل الرسالة" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "تمت إعادة توجيه الرسالة" @@ -20746,7 +20815,7 @@ msgstr "أزرار إدخال الرسائل" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "الرسالة طويلة جدًا" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "متفرقات" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id مفقود" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "يجب أن يتكون الاسم من حرفين على الأقل" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "تسمية مفتاح المرور" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "نموذج تسمية مفتاح المرور" @@ -22381,7 +22450,7 @@ msgstr "لا توجد تغييرات لحفظها" msgid "No channels available" msgstr "لا توجد قنوات متاحة" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "لم يتم العثور على قنوات" @@ -22824,7 +22893,7 @@ msgstr "لم يتم العثور على مستخدمين" msgid "No verification is required." msgstr "لا يلزم التحقق." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "لا توجد طرق تحقق متاحة" @@ -23417,6 +23486,16 @@ msgstr "فتح {contextName}" msgid "Open {displayName}" msgstr "فتح {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "افتح {host} في سفاري." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "افتح {host} في متصفحك." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "افتح {macosSystemSettingsName} ← {macosPrivacyAndSecuritySettingsN msgid "Open {productName}" msgstr "فتح {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "افتح {productName} في متصفحك" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "فتح خيارات اللافتة" msgid "Open bookmarks" msgstr "فتح الإشارات المرجعية" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "فتح المتصفح" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "فتح الرمز التعبيري في المتصفح" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "فتح منتقي الرموز التعبيرية" @@ -23809,6 +23893,16 @@ msgstr "افتح الرابط أدناه في متصفحك، ثم انسخ اس msgid "Open the menu and choose an option." msgstr "افتح القائمة واختر خيارًا." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "افتح التطبيق الجديد واختر \"تسجيل الدخول باستخدام تطبيق {productName} القديم\". ثم اختر \"ربط جهاز جديد هنا\" وأدخل الرمز الذي يظهر." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "افتح التطبيق الجديد. أنت مسجل الدخول بالفعل." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "فتح المحادثة الصوتية" msgid "Open voice & video settings" msgstr "فتح إعدادات الصوت والفيديو" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "افتح متصفحك، سجّل الدخول، ثم أدخل الرمز أدناه لربط حسابك." @@ -23868,6 +23962,10 @@ msgstr "افتح متصفحك، سجّل الدخول، ثم أدخل الرمز msgid "Open your messages" msgstr "افتح رسائلك" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "افتح تطبيق {PRODUCT_NAME} القديم واختر \"ربط جهاز جديد\"، ثم أدخل الرمز أدناه." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "افتح تطبيق الرسائل على هاتفك وأنشئ رسالة نصية جديدة." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "مفتاح المرور" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "اسم مفتاح المرور" @@ -28138,7 +28236,7 @@ msgstr "التشبع" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "معاينة التمرير في الفيديو" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "تحديد النص" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "اختر الحساب الذي تريد تسجيل الدخول به في تطبيق سطح المكتب." +msgid "Select the account you want to sign in with on your new device." +msgstr "اختر الحساب الذي تريد تسجيل الدخول به على جهازك الجديد." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "إرسال" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "إرسال ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "الجلسة" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "انتهت جلسة {identifier}. سجّل الدخول مرة أخرى." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "تتوقف اختصارات لوحة المفاتيح مؤقتًا أثناء التسجيل." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "قصّر الرسالة وحاول مرة أخرى." @@ -30217,6 +30315,11 @@ msgstr "عرض 3 كحد أقصى (شارة +5)" msgid "Show max 5 (+5 badge)" msgstr "عرض 5 كحد أقصى (شارة +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "أرني كيف" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "سجّل الدخول لاستلام هديتك" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "تسجيل الدخول عبر المتصفح" @@ -30496,6 +30599,11 @@ msgstr "تسجيل الدخول عبر المتصفح" msgid "Sign in with a passkey" msgstr "تسجيل الدخول باستخدام مفتاح مرور" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "تسجيل الدخول باستخدام تطبيق {productName} القديم الخاص بك" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "تفاصيل تسجيل الدخول" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "خطوات تسجيل الدخول" @@ -31030,7 +31138,7 @@ msgstr "لا يمكن إضافة بعض المستخدمين" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "التحدث في هذه القناة الصوتية. بدون هذا ا msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "التحدث في القنوات الصوتية. بدون هذا الإذن، يبقى الأعضاء مكتومي الصوت حتى يلغي كتمهم شخص لديه إذن \"كتم صوت الأعضاء\"." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "اضغط على مشاركة، ثم إضافة إلى الشاشة الرئيسية." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "تم تأجيل التحقق من الهاتف. تبقّت خطوة واحدة للبريد الإلكتروني." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "عنوان redirect_uri المقدّم غير مسجّل لهذا التطبيق." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "يجب أن يستخدم عنوان redirect_uri المقدّم بروتوكول HTTP أو HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "هذه الرسوم المتحركة طويلة جدًا أو كبيرة جدًا بحيث لا يمكن قصها. جرب ملفًا أقصر أو أصغر." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "هذا التطبيق" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "انتهت صلاحية هذا المرفق" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "هذا البوت ليس عامًا ولا يمكن للمستخدمين الآخرين إضافته." @@ -33757,7 +33870,7 @@ msgstr "عدد الأشخاص في هذه المجموعة كبير جدًا" msgid "This group is full. Remove someone before adding more people." msgstr "هذه المجموعة ممتلئة. أزل شخصًا قبل إضافة المزيد من الأشخاص." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "انتهت صلاحية رمز التسليم هذا. أنشئ رمزًا جديدًا للمتابعة." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "غير معروف" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "تطبيق غير معروف، أو جلسة منتهية الصلاحية، أو طلب تفويض غير صالح." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "الاستخدامات:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "يستخدم حالة الحساب الفعلية" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "يستخدم حالة مخزون الهدايا الفعلية" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "يستخدم حالة Premium الفعلية" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "يستخدم الحالة الفعلية" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "يستخدم حالة التحقق الفعلية" @@ -37947,17 +38060,17 @@ msgstr "انتظر هنا" msgid "Waiting for connection..." msgstr "في انتظار الاتصال..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "جارٍ انتظار مفتاح المرور…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "في انتظار انتهاء الوضع البطيء في {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "في انتظار انتهاء الوضع البطيء في قناة واحدة أو أكثر من القنوات المحددة." @@ -38156,7 +38269,7 @@ msgstr "لقد غيّرنا حجم هذا الملصق وضغطناه إلى 320 msgid "We sent a code to {email}." msgstr "أرسلنا رمزًا إلى {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "سنقوم بملء {prefillEmail} مسبقًا بمجرد فتح تسجيل الدخول في المتصفح." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "معاينات المواقع" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "أهلاً بعودتك" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "انتهت صلاحية اشتراكك في {premiumProductName}. لقد فقدت جميع مزايا {premiumProductName}. أعد تفعيل اشتراكك لاستعادة الوصول." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "انتهت صلاحية جلسة {productName} الخاصة بك. سجّل الدخول مرة أخرى لمتابعة تفويض هذا التطبيق." @@ -39734,7 +39847,7 @@ msgstr "حسابك جديد جدًا ولا يمكنه إرسال رسائل ف msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "طلب حسابك بانتظار الموافقة. يمكنك تسجيل الدخول بعد أن يوافق عليه أحد المسؤولين." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "يتطلب حسابك مصادقة متعددة العوامل، ولكن لم يتم إعداد أي طرق مدعومة على هذا الجهاز. سجّل الدخول على جهاز باستخدام تطبيق المصادقة أو مفتاح الأمان الخاص بك، أو اتصل بالدعم." @@ -39757,6 +39870,11 @@ msgstr "تعذّر إكمال الإجراء الذي طلبته لأن أنظم msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "طلبك قيد المراجعة. لا يزال بإمكانك تحديث تفاصيل الإدراج أو سحب الطلب." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "متصفحك لا يدعم مفاتيح المرور على هذا النطاق. قم بتحديث متصفحك، أو سجّل الدخول باستخدام كلمة مرورك ورمز المصادقة الثنائية." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "بطاقتك مؤهلة. سيتم توجيهك إلى صفحة الدف msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "تفيد شركة الاتصالات لديك بأن هذا الرقم خارج الخدمة. تحقق منه وحاول مرة أخرى، أو اتصل بالدعم إذا كان صحيحًا." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "رمزك" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "رمزك جاهز" diff --git a/fluxer_app/src/features/i18n/locales/auto-i18n-reviewed-unchanged.json b/fluxer_app/src/features/i18n/locales/auto-i18n-reviewed-unchanged.json index 931ea20c9..1d904f172 100644 --- a/fluxer_app/src/features/i18n/locales/auto-i18n-reviewed-unchanged.json +++ b/fluxer_app/src/features/i18n/locales/auto-i18n-reviewed-unchanged.json @@ -1521,6 +1521,9 @@ { "msgid": "Approval required" }, + { + "msgid": "Approve this app from the {productName} app you already use. No password needed." + }, { "msgid": "Are you sure you want to kick <0>{targetUserTag} from the community? They will be able to rejoin with a new invite." }, @@ -1716,6 +1719,9 @@ { "msgid": "Choices" }, + { + "msgid": "Choose File, then Add to Dock." + }, { "msgid": "Choose how new accounts are created on this instance." }, @@ -1878,6 +1884,9 @@ { "msgid": "Do you want to join {communityName}?" }, + { + "msgid": "Domain moved nagbar" + }, { "msgid": "Drag members between voice channels they can access, and disconnect them from voice." }, @@ -1932,6 +1941,9 @@ { "msgid": "Enter the PIN for your security key to continue." }, + { + "msgid": "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." + }, { "msgid": "Entire screen" }, @@ -1968,6 +1980,9 @@ { "msgid": "Fully quit and restart {productName} so macOS applies this permission." }, + { + "msgid": "Get the new {productName} app" + }, { "msgid": "Give everyone on this instance the highest premium limits." }, @@ -2025,6 +2040,12 @@ { "msgid": "Input level" }, + { + "msgid": "Install it from your browser menu." + }, + { + "msgid": "Install the new app" + }, { "msgid": "Invite new people to the community with a link to this channel." }, @@ -2085,6 +2106,9 @@ { "msgid": "Limited the channel to {count, plural, one {# user} other {# users}}." }, + { + "msgid": "Link a new device" + }, { "msgid": "Loading instance configuration" }, @@ -2214,6 +2238,21 @@ { "msgid": "Open popup" }, + { + "msgid": "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." + }, + { + "msgid": "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." + }, + { + "msgid": "Open {host} in Safari." + }, + { + "msgid": "Open {host} in your browser." + }, + { + "msgid": "Open {productName} in your browser" + }, { "msgid": "Opens the Members page. Press Space or Enter to open." }, @@ -2580,6 +2619,9 @@ { "msgid": "See this channel. Denying it for {everyoneMention} makes the channel private." }, + { + "msgid": "Select the account you want to sign in with on your new device." + }, { "msgid": "Self-host setup" }, @@ -2760,6 +2802,9 @@ { "msgid": "Show fewer filters" }, + { + "msgid": "Show me how" + }, { "msgid": "Show per-device participant volume sliders in voice menus" }, @@ -2772,6 +2817,9 @@ { "msgid": "Showed members with this role in their own section in the member list." }, + { + "msgid": "Sign in with your old {productName} app" + }, { "msgid": "Sign-in details" }, @@ -2901,6 +2949,9 @@ { "msgid": "Talk in calls and test your input." }, + { + "msgid": "Tap Share, then Add to Home Screen." + }, { "msgid": "Text is rendered with grayscale antialiasing instead of ClearType while transparency is enabled, so it will look softer." }, @@ -3822,9 +3873,27 @@ { "msgid": "{productName} cannot start a safe isolated route for this app's audio." }, + { + "msgid": "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." + }, + { + "msgid": "{productName} has moved to {host}. Install it from your browser, then sign in with this app." + }, + { + "msgid": "{productName} has moved to {host}. Install the new app and you will already be signed in." + }, + { + "msgid": "{productName} has moved to {host}. Open it in your browser to install the new app." + }, { "msgid": "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." }, + { + "msgid": "{productName} now lives at {host}. Install it from there, then sign in with this app." + }, + { + "msgid": "{productName} now lives at {host}. Install it from there." + }, { "msgid": "{productName} wordmark" }, @@ -6821,6 +6890,9 @@ { "msgid": "Open {displayName}" }, + { + "msgid": "Open {host} in Safari." + }, { "msgid": "Open {landmarkName}" }, diff --git a/fluxer_app/src/features/i18n/locales/bg/messages.po b/fluxer_app/src/features/i18n/locales/bg/messages.po index f602b9704..8cb87a7e7 100644 --- a/fluxer_app/src/features/i18n/locales/bg/messages.po +++ b/fluxer_app/src/features/i18n/locales/bg/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# блокирано съобщение} other {# msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Намерена е # общност} other {Намерени са # общности}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# общност е временно недостъпна поради неизправност на флукс кондензатора.} other {# общности са временно недостъпни поради неизправност на флукс кондензатора.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} засече потенциална атака в тази msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} откри потенциална атака, така че новите потребители не могат да се присъединят в момента." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} се премести на адрес {host}. Добавете го към началния си екран или Dock, след което влезте с това приложение." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} се премести на {host}. Инсталирайте го от браузъра си, след което влезте с това приложение." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} се премести на адрес {host}. Инсталирайте новото приложение и ще бъдете автоматично вписани." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} се премести на {host}. Отворете го в браузъра си, за да инсталирате новото приложение." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} се нуждае от достъп до микрофон msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} се нуждае от достъп до записа на екрана. Разреши записа на екрана в настройките за поверителност на операционната система и рестартирай приложението." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} вече се намира на адрес {host}. Инсталирайте го оттам, след което влезте с това приложение." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} вече се намира на адрес {host}. Инсталирайте го оттам." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "анкета" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Изисква се redirect_uri, когато обхватът на бота не е единственият обхват." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Добавяне на бот към общност с поисканите разрешения" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Добавяне на коментар (по избор)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Добави ключ за YouTube Data API, за да обогатиш линковете към YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Добавяне на акаунт" @@ -4379,6 +4409,11 @@ msgstr "Изисква се одобрение" msgid "Approve" msgstr "Одобри" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Одобрете това приложение от {productName}, което вече използвате. Не е необходима парола." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Неуспешна оторизация" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Неуспешна оторизация. Опитай отново." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Разрешения за бота" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Разрешенията за бот не могат да бъдат отрицателни." @@ -6329,10 +6364,10 @@ msgstr "Тук не можеш да качваш файлове" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Тук не можеш да качваш файлове" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Избери фон" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Избери guild_id или channel_id, не и двете." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Изберете Файл, след това Добавяне в Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "кодов блок" msgid "Code blocks" msgstr "Кодови блокове" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Кодът е изтекъл" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Команди" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Коментарите са изключени, защото в един или повече от избраните канали е включен бавен режим. Коментарът ще бъде блокиран от бавния режим веднага след препращането." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Коментарите са изключени, защото в {forwardChannelDisplayName} е включен бавен режим. Коментарът ще бъде блокиран от бавния режим веднага след препращането." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Коментарите не са достъпни, докато е включен бавен режим." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Известия за съобщения в общности" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Лента за изискване за MFA на общността" @@ -9182,7 +9222,7 @@ msgstr "Продължи с докладването на общността" msgid "Continue to report user" msgstr "Продължи с докладването на потребителя" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Продължи с ключ за достъп" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Координира атаки, групови нападки или тормоз срещу хора или общности." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Копирано" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Копирай линка към канала" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Копирай кода" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Текущи използвания:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "В момента е отхвърлено" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "В момента се показва" @@ -12555,6 +12595,11 @@ msgstr "Не блокира. Можеш да го отвориш отново п msgid "Domain" msgstr "Домейн" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Домейнът е преместен" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Нидерландски" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "напр. YubiKey, iPhone, служебен компютър" @@ -14056,10 +14101,14 @@ msgstr "Въведи ПИН кода на защитния си ключ, за msgid "Enter the SMS code" msgstr "Въведи SMS кода" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Въведи този код в браузъра си, за да завършиш влизането." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Въведете този код в старото си приложение {PRODUCT_NAME}, за да завършите влизането." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter, за да <0><1>запазиш" @@ -14441,7 +14490,7 @@ msgstr "Изтича на {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Изтича между {earliest} и {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Изтича след {remaining} сек" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Неуспешно стартиране на SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Неуспешно превключване на акаунт" @@ -15413,17 +15462,17 @@ msgstr "За мен" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "За тази проверка ти ни изпращаш текстово съобщение, вместо да получаваш такова от нас." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "За твоята сигурност потвърди самоличността си, за да продължиш." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Принудително деактивирано" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Принудително активирано" @@ -15540,7 +15589,7 @@ msgstr "Забравена парола" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Забравена парола?" @@ -15571,7 +15620,7 @@ msgstr "Препращане на медия" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Препращане на съобщение" @@ -15965,11 +16014,11 @@ msgstr "Генерирай линк за споделяне или копира msgid "Generate invite link" msgstr "Генериране на линк за покана" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Генериране на нов код" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Генериране на код…" @@ -16045,6 +16094,11 @@ msgstr "Изтегли настолното приложение {productName} msgid "Get the desktop app" msgstr "Изтегли настолното приложение" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Вземете новото приложение {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Вмъкване на емоджи" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Инсталирай {productName} за пълния избор на източник и по-плавни контроли за споделяне на екрана." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Инсталирай го от менюто на браузъра си." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Инсталирай новото приложение" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Невалидна заявка за оторизация" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Невалидни разрешения за бота." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Невалиден или изтекъл код. Опитай отново." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Невалиден redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Линк" msgid "Link (mature)" msgstr "Линк (за възрастни)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Свържи ново устройство" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Настройките за показване на съобщения msgid "Message editing" msgstr "Редактиране на съобщения" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Съобщението е препратено" @@ -20746,7 +20815,7 @@ msgstr "Бутони за въвеждане на съобщения" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Съобщението е твърде дълго" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Разни" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Липсващ client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Името трябва да е поне 2 знака" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Наименуване на ключа за достъп" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Формуляр за наименуване на ключ за достъп" @@ -22381,7 +22450,7 @@ msgstr "Няма промени за запазване" msgid "No channels available" msgstr "Няма налични канали" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Няма намерени канали" @@ -22824,7 +22893,7 @@ msgstr "Няма намерени потребители" msgid "No verification is required." msgstr "Не се изисква потвърждение." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Няма налични методи за потвърждение" @@ -23417,6 +23486,16 @@ msgstr "Отваряне на {contextName}" msgid "Open {displayName}" msgstr "Отваряне на {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Отвори {host} в Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Отвори {host} в браузъра си." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Отвори {macosSystemSettingsName} → {macosPrivacyAndSecuritySetti msgid "Open {productName}" msgstr "Отваряне на {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Отвори {productName} в браузъра си" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Отваряне на опциите за банера" msgid "Open bookmarks" msgstr "Отваряне на отметките" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Отваряне на браузъра" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Отваряне на емоджи в браузър" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Отваряне на палитрата с емоджита" @@ -23809,6 +23893,16 @@ msgstr "Отвори линка по-долу в браузъра си, след msgid "Open the menu and choose an option." msgstr "Отвори менюто и избери опция." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Отворете новото приложение и изберете Вход със старото си приложение {productName}. След това изберете Свързване на ново устройство тук и въведете показания код." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Отвори новото приложение. Вече си влязъл." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Отваряне на гласов чат" msgid "Open voice & video settings" msgstr "Отваряне на настройките за глас и видео" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Отвори браузъра си, влез в акаунта си, след което въведи кода по-долу, за да свържеш акаунта си." @@ -23868,6 +23962,10 @@ msgstr "Отвори браузъра си, влез в акаунта си, с msgid "Open your messages" msgstr "Отвори съобщенията си" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Отворете старото си приложение {PRODUCT_NAME} и изберете „Свързване на ново устройство“, след което въведете кода по-долу." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Отвори приложението за съобщения на телефона си и създай ново текстово съобщение." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Ключ за достъп" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Име на ключ за достъп" @@ -28138,7 +28236,7 @@ msgstr "Наситеност" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Преглед при превъртане" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Избор на текст" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Избери акаунта, с който искаш да влезеш в настолното приложение." +msgid "Select the account you want to sign in with on your new device." +msgstr "Изберете акаунта, с който искате да влезете на новото си устройство." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Изпращане" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Изпращане ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Сесия" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Сесията за {identifier} е изтекла. Влез отново." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Клавишните комбинации са на пауза по време на записване." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Съкрати съобщението и опитай отново." @@ -30217,6 +30315,11 @@ msgstr "Показване на максимум 3 (значка +5)" msgid "Show max 5 (+5 badge)" msgstr "Показване на максимум 5 (значка +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Покажи ми как" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Влез, за да вземеш подаръка си" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Вход през браузър" @@ -30496,6 +30599,11 @@ msgstr "Вход през браузър" msgid "Sign in with a passkey" msgstr "Вход с ключ за достъп" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Влезте със старото си приложение {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Данни за вход" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Процес на вход" @@ -31030,7 +31138,7 @@ msgstr "Някои потребители не могат да бъдат доб #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Говорене в този гласов канал. Без това р msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Говорене в гласови канали. Без това разрешение членовете остават с изключен микрофон, докато някой с разрешението „Изключване на микрофона на членове“ не им го включи." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Докоснете „Споделяне“, след което „Добавяне към начален екран“." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Телефонната проверка е оставена настрана. Остава една стъпка с имейл." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Предоставеният redirect_uri не е регистриран за това приложение." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Предоставеният redirect_uri трябва да използва HTTP или HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Анимацията е твърде дълга или твърде голяма, за да бъде изрязана. Опитайте с по-кратък или по-малък файл." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Това приложение" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Този прикачен файл е изтекъл" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Този бот не е публичен и не може да бъде добавен от други потребители." @@ -33757,7 +33870,7 @@ msgstr "Тази група има твърде много хора" msgid "This group is full. Remove someone before adding more people." msgstr "Тази група е пълна. Премахни някого, преди да добавиш още хора." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Този код за прехвърляне е изтекъл. Генерирай нов, за да продължиш." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Неизвестен" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Неизвестно приложение, изтекла сесия или невалидна заявка за оторизация." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Използвания:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Използва се действителното състояние на акаунта" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Използва се действителното състояние на инвентара с подаръци" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Използва се действителното премиум състояние" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Използва се действителното състояние" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Използва се действителното състояние на потвърждение" @@ -37947,17 +38060,17 @@ msgstr "Изчакай тук" msgid "Waiting for connection..." msgstr "Изчакване на връзка..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Изчакване на ключ за достъп…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Изчаква се бавният режим в {forwardChannelDisplayName} да изтече." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Изчаква се да изтече бавният режим в един или повече избрани канала." @@ -38156,7 +38269,7 @@ msgstr "Преоразмерихме и компресирахме този ст msgid "We sent a code to {email}." msgstr "Изпратихме код на {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Ще попълним предварително {prefillEmail}, след като се отвори влизането в браузъра." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Визуализации на уебсайтове" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Добре дошли отново" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Абонаментът ти за {premiumProductName} изтече. Загуби всички предимства на {premiumProductName}. Активирай отново абонамента си, за да възстановиш достъпа." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Сесията ти в {productName} изтече. Влез отново, за да продължиш с оторизацията на това приложение." @@ -39734,7 +39847,7 @@ msgstr "Акаунтът ти е твърде нов, за да изпращаш msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Заявката ти за акаунт чака одобрение. Можеш да влезеш, след като администратор я одобри." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Акаунтът ти изисква многофакторно удостоверяване, но на това устройство не са настроени поддържани методи. Влез от устройство с приложението за удостоверяване или защитния си ключ, или се свържи с поддръжката." @@ -39757,6 +39870,11 @@ msgstr "Действието ти не можа да бъде извършено msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Заявлението ти е в процес на преглед. Все още можеш да актуализираш данните за обявата си или да оттеглиш заявлението." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Вашият браузър не поддържа ключове за достъп за този домейн. Актуализирайте браузъра си или влезте с паролата и двуфакторния си код." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Картата ти отговаря на условията. Прена msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Мобилният ти оператор съобщава, че този номер не е активен. Провери го и опитай отново или се свържи с поддръжката, ако е правилен." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Твоят код" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Кодът ти е готов" diff --git a/fluxer_app/src/features/i18n/locales/cs/messages.po b/fluxer_app/src/features/i18n/locales/cs/messages.po index 540394c83..cb472e626 100644 --- a/fluxer_app/src/features/i18n/locales/cs/messages.po +++ b/fluxer_app/src/features/i18n/locales/cs/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# zablokovaná zpráva} few {# zablokované zprávy msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Nalezena # komunita} few {Nalezeny # komunity} many {Nalezeno # komunity} other {Nalezeno # komunit}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# komunita je dočasně nedostupná kvůli poruše fluxkondenzátoru.} few {# komunity jsou dočasně nedostupné kvůli poruše fluxkondenzátoru.} many {# komunity je dočasně nedostupných kvůli poruše fluxkondenzátoru.} other {# komunit je dočasně nedostupných kvůli poruše fluxkondenzátoru.}}" @@ -1692,6 +1692,26 @@ msgstr "Aplikace {productName} zjistila možný nájezd na tuto komunitu. Pozvá msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "Aplikace {productName} zjistila možný nájezd, takže se teď noví uživatelé nemohou připojit." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} se přesunul na {host}. Přidejte si ho na úvodní obrazovku nebo do Docku a poté se přihlaste pomocí této aplikace." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} se přesunul na {host}. Nainstalujte si ho z prohlížeče a poté se přihlaste pomocí této aplikace." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} se přesunul na {host}. Nainstalujte novou aplikaci a budete již přihlášeni." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} se přesunul na {host}. Otevřete jej v prohlížeči a nainstalujte novou aplikaci." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "Aplikace {productName} potřebuje přístup k mikrofonu, aby mohla zobra msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} potřebuje přístup k nahrávání obrazovky. Povolte nahrávání obrazovky v nastavení ochrany osobních údajů vašeho operačního systému a restartujte aplikaci." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} se nyní nachází na adrese {host}. Nainstalujte si ho odtud a poté se přihlaste pomocí této aplikace." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} se teď nachází na adrese {host}. Nainstalujte si ho odtud." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "anketa" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Parametr redirect_uri je povinný, pokud „bot“ není jediným rozsahem oprávnění." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Přidat bota do komunity s požadovanými oprávněními" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Přidat komentář (volitelné)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Přidejte klíč YouTube Data API pro doplnění informací k odkazům na YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Přidat účet" @@ -4379,6 +4409,11 @@ msgstr "Vyžaduje schválení" msgid "Approve" msgstr "Schválit" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Schvalte tuto aplikaci z aplikace {productName}, kterou již používáte. Heslo není potřeba." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autorizace se nezdařila" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorizace se nezdařila. Zkuste to znovu." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Oprávnění bota" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Oprávnění bota nemohou být záporná." @@ -6329,10 +6364,10 @@ msgstr "Sem nelze nahrávat soubory" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Sem nelze nahrávat soubory" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Vybrat pozadí" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Zvolte buď guild_id, nebo channel_id, ne obojí." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Vyberte Soubor a poté Přidat do Docku." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "blok kódu" msgid "Code blocks" msgstr "Bloky kódu" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Platnost kódu vypršela" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Příkazy" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Komentáře jsou zakázány, protože jeden nebo více vybraných kanálů má zapnutý pomalý režim. Komentář by byl pomalým režimem zablokován hned po přeposlání." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Komentáře jsou zakázány, protože v kanálu {forwardChannelDisplayName} je zapnutý pomalý režim. Komentář by byl pomalým režimem zablokován hned po přeposlání." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Komentáře nejsou dostupné, když je zapnutý pomalý režim." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Oznámení zpráv v komunitě" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Lišta s požadavkem komunity na vícefaktorové ověřování" @@ -9182,7 +9222,7 @@ msgstr "Pokračovat v nahlášení komunity" msgid "Continue to report user" msgstr "Pokračovat v nahlášení uživatele" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Pokračovat pomocí přístupového klíče" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Koordinuje nájezdy, hromadné útoky nebo obtěžování lidí či komunit." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Zkopírováno" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kopírovat odkaz na kanál" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kopírovat kód" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Aktuální počet použití:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Momentálně skryto" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Aktuálně se zobrazuje" @@ -12555,6 +12595,11 @@ msgstr "Neblokuje. Můžete znovu otevřít později." msgid "Domain" msgstr "Doména" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Přesun domény" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Nizozemština" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "např. YubiKey, iPhone, pracovní počítač" @@ -14056,10 +14101,14 @@ msgstr "Zadejte PIN pro váš bezpečnostní klíč a pokračujte." msgid "Enter the SMS code" msgstr "Zadejte kód SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Zadejte tento kód do prohlížeče a dokončete přihlášení." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Zadejte tento kód do své staré aplikace {PRODUCT_NAME} pro dokončení přihlášení." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter pro <0><1>uložení" @@ -14441,7 +14490,7 @@ msgstr "Vyprší {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Platnost vyprší mezi {earliest} a {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Vyprší za {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Nepodařilo se spustit jednotné přihlášení" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Přepnutí účtu se nezdařilo" @@ -15413,17 +15462,17 @@ msgstr "Pro mě" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Při tomto ověření posíláte SMS vy nám, místo abyste ji od nás dostali." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Pro vaši bezpečnost prosím potvrďte, že jste to vy, abyste mohli pokračovat." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Vynuceně vypnuto" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Vynuceně zapnuto" @@ -15540,7 +15589,7 @@ msgstr "Zapomenuté heslo" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Zapomněli jste heslo?" @@ -15571,7 +15620,7 @@ msgstr "Přeposlat médium" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Přeposlat zprávu" @@ -15965,11 +16014,11 @@ msgstr "Vygenerujte odkaz ke sdílení nebo zkopírujte svůj kombinovaný vlast msgid "Generate invite link" msgstr "Vygenerovat odkaz na pozvánku" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Vygenerovat nový kód" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generování kódu…" @@ -16045,6 +16094,11 @@ msgstr "Pořiďte si desktopovou aplikaci {productName} pro funkci Stisknutím m msgid "Get the desktop app" msgstr "Stáhněte si desktopovou aplikaci" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Získejte novou aplikaci {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Vložit emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Nainstalujte si aplikaci {productName} a získejte kompletní výběr zdrojů a plynulejší ovládání sdílení obrazovky." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Nainstalujte si ho z nabídky prohlížeče." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Nainstalovat novou aplikaci" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Neplatný požadavek na autorizaci" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Neplatná oprávnění bota." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Kód je neplatný nebo jeho platnost vypršela. Zkuste to znovu." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Neplatný parametr redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Odkaz" msgid "Link (mature)" msgstr "Odkaz (pro dospělé)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Propojit nové zařízení" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Nastavení zobrazení zpráv je k dispozici pouze na počítači." msgid "Message editing" msgstr "Úprava zpráv" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Zpráva přeposlána" @@ -20746,7 +20815,7 @@ msgstr "Tlačítka pro zadávání zpráv" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Zpráva je příliš dlouhá" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Různé" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Chybí client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Název musí mít alespoň 2 znaky" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Pojmenovat přístupový klíč" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formulář pro pojmenování přístupového klíče" @@ -22381,7 +22450,7 @@ msgstr "Žádné změny k uložení" msgid "No channels available" msgstr "Žádné kanály nejsou k dispozici" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Nebyly nalezeny žádné kanály" @@ -22824,7 +22893,7 @@ msgstr "Žádní uživatelé nenalezeni" msgid "No verification is required." msgstr "Ověření není vyžadováno." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Nejsou k dispozici žádné metody ověření" @@ -23417,6 +23486,16 @@ msgstr "Otevřít {contextName}" msgid "Open {displayName}" msgstr "Otevřít {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Otevřete {host} v Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Otevřete {host} ve svém prohlížeči." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Otevřete {macosSystemSettingsName} → {macosPrivacyAndSecuritySettings msgid "Open {productName}" msgstr "Otevřít {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Otevřít {productName} v prohlížeči" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Otevřít možnosti banneru" msgid "Open bookmarks" msgstr "Otevřít záložky" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Otevřít prohlížeč" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Otevřít emoji v prohlížeči" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Otevřít výběr emoji" @@ -23809,6 +23893,16 @@ msgstr "Otevřete odkaz níže ve svém prohlížeči, zkopírujte celou odpově msgid "Open the menu and choose an option." msgstr "Otevřete nabídku a vyberte možnost." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Otevřete novou aplikaci a zvolte Přihlásit se pomocí své staré aplikace {productName}. Poté zvolte Propojit nové zařízení zde a zadejte zobrazený kód." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Otevřete novou aplikaci. Jste již přihlášeni." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Otevřít hlasový hovor" msgid "Open voice & video settings" msgstr "Otevřít nastavení zvuku a videa" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Otevřete prohlížeč, přihlaste se a poté zadejte níže uvedený kód pro propojení účtu." @@ -23868,6 +23962,10 @@ msgstr "Otevřete prohlížeč, přihlaste se a poté zadejte níže uvedený k msgid "Open your messages" msgstr "Otevřete si zprávy" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Otevřete svou starou aplikaci {PRODUCT_NAME} a vyberte „Propojit nové zařízení“, poté zadejte níže uvedený kód." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Otevřete v telefonu aplikaci pro zprávy a vytvořte novou SMS." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Přístupový klíč" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Název přístupového klíče" @@ -28138,7 +28236,7 @@ msgstr "Sytost" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Náhled při posouvání v záznamu" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Vybrat text" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Vyberte účet, se kterým se chcete přihlásit do desktopové aplikace." +msgid "Select the account you want to sign in with on your new device." +msgstr "Vyberte účet, ke kterému se chcete přihlásit na novém zařízení." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Odeslat" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Odeslat ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Relace" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Relace pro {identifier} vypršela. Přihlaste se znovu." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Během zaznamenávání jsou klávesové zkratky pozastavené." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Zkraťte zprávu a zkuste to znovu." @@ -30217,6 +30315,11 @@ msgstr "Zobrazit nejvýše 3 (odznak +5)" msgid "Show max 5 (+5 badge)" msgstr "Zobrazit nejvýše 5 (odznak +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Ukázat mi jak" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Přihlaste se a převezměte si dárek" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Přihlásit se přes prohlížeč" @@ -30496,6 +30599,11 @@ msgstr "Přihlásit se přes prohlížeč" msgid "Sign in with a passkey" msgstr "Přihlásit se pomocí přístupového klíče" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Přihlaste se pomocí své staré aplikace {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Přihlašovací údaje" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Průběh přihlášení" @@ -31030,7 +31138,7 @@ msgstr "Některé uživatele nelze přidat" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Mluvit v tomto hlasovém kanálu. Bez tohoto oprávnění zůstanou čle msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Mluvit v hlasových kanálech. Bez tohoto oprávnění zůstanou členové ztlumení, dokud jim mikrofon nezapne někdo s oprávněním „Ztlumovat mikrofony členům“." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Klepněte na Sdílet a poté na Přidat na plochu." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Ověření telefonu je odložené. Zbývá jeden krok s e-mailem." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Zadané redirect_uri není pro tuto aplikaci zaregistrováno." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Zadané redirect_uri musí používat HTTP nebo HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Tato animace je příliš dlouhá nebo příliš velká na oříznutí. Zkuste kratší nebo menší soubor." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Tato aplikace" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Tato příloha vypršela" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Tento bot není veřejný a ostatní uživatelé ho nemohou přidat." @@ -33757,7 +33870,7 @@ msgstr "Tato skupina má příliš mnoho lidí" msgid "This group is full. Remove someone before adding more people." msgstr "Tato skupina je plná. Než přidáte další lidi, někoho odeberte." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Tento kód pro předání vypršel. Vygenerujte si nový, abyste mohli pokračovat." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Neznámý" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Neznámá aplikace, vypršená relace nebo neplatný požadavek na autorizaci." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Použití:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Používá skutečný stav účtu" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Používá se skutečný stav inventáře dárků" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Používá skutečný stav prémiového účtu" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Používá skutečný stav" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Používá skutečný stav ověření" @@ -37947,17 +38060,17 @@ msgstr "Počkejte zde" msgid "Waiting for connection..." msgstr "Čekání na připojení..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Čekání na přístupový klíč…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Čekání na vypršení pomalého režimu v kanálu {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Čekání na vypršení pomalého režimu v jednom nebo více vybraných kanálech." @@ -38156,7 +38269,7 @@ msgstr "Změnili jsme velikost této samolepky na 320x320 pixelů a zkomprimoval msgid "We sent a code to {email}." msgstr "Kód jsme odeslali na {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Předvyplníme {prefillEmail}, jakmile se otevře přihlášení v prohlížeči." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Náhledy webových stránek" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Vítejte zpět" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Vaše předplatné {premiumProductName} vypršelo. Ztratili jste všechny výhody {premiumProductName}. Znovu aktivujte své předplatné, abyste získali přístup." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Vaše relace {productName} vypršela. Přihlaste se znovu a pokračujte v autorizaci této aplikace." @@ -39734,7 +39847,7 @@ msgstr "Váš účet je příliš nový na odesílání zpráv v této komunitě msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Žádost o váš účet čeká na schválení. Přihlásit se můžete, až ji administrátor schválí." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Váš účet vyžaduje vícefaktorové ověřování, ale na tomto zařízení nejsou nastavené žádné podporované metody. Přihlaste se na zařízení s ověřovací aplikací nebo bezpečnostním klíčem, případně kontaktujte podporu." @@ -39757,6 +39870,11 @@ msgstr "Vaši akci se nepodařilo dokončit, protože byla označena našimi bez msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Vaše žádost čeká na posouzení. Stále můžete upravovat údaje v záznamu nebo žádost stáhnout." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Váš prohlížeč nepodporuje přístupové klíče pro tuto doménu. Aktualizujte svůj prohlížeč nebo se přihlaste pomocí hesla a dvoufaktorového kódu." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Vaše karta je způsobilá. Nyní vás přesměrujeme na platební strá msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Váš operátor uvádí, že toto číslo není v provozu. Zkontrolujte ho a zkuste to znovu. Pokud je správné, kontaktujte podporu." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Váš kód" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Váš kód je připraven" diff --git a/fluxer_app/src/features/i18n/locales/da/messages.po b/fluxer_app/src/features/i18n/locales/da/messages.po index dad4dc8c6..8763226e4 100644 --- a/fluxer_app/src/features/i18n/locales/da/messages.po +++ b/fluxer_app/src/features/i18n/locales/da/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# blokeret besked} other {# blokerede beskeder}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# fællesskab fundet} other {# fællesskaber fundet}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# fællesskab er midlertidigt utilgængeligt på grund af en fejl i fluxkondensatoren.} other {# fællesskaber er midlertidigt utilgængelige på grund af en fejl i fluxkondensatoren.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} har registreret et potentielt raid i dette fællesskab. In msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} har registreret et potentielt raid, så nye brugere kan ikke tilmelde sig lige nu." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} er flyttet til {host}. Tilføj det til din hjemmeskærm eller Dock, og log derefter ind med denne app." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} er flyttet til {host}. Installer det fra din browser, og log derefter ind med denne app." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} er flyttet til {host}. Installer den nye app, og du vil allerede være logget ind." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} er flyttet til {host}. Åbn det i din browser for at installere den nye app." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} skal have adgang til mikrofonen for at vise dine enheder." msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} skal have adgang til skærmoptagelse. Tillad skærmoptagelse i operativsystemets privatlivsindstillinger, og genstart appen." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} findes nu på {host}. Installer det derfra, og log derefter ind med denne app." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} findes nu på {host}. Installer det derfra." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "en afstemning" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri er påkrævet, når \"bot\" ikke er det eneste rettighedsområde." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Tilføj en bot til et fællesskab med de ønskede tilladelser" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Tilføj en kommentar (valgfrit)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Tilføj en YouTube Data API-nøgle for at berige YouTube-links." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Tilføj konto" @@ -4379,6 +4409,11 @@ msgstr "Godkendelse påkrævet" msgid "Approve" msgstr "Godkend" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Godkend denne app fra din eksisterende {productName}-app. Ingen adgangskode nødvendig." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Godkendelse mislykkedes" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorisation mislykkedes. Prøv igen." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Bot-tilladelser" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Bot-tilladelser kan ikke være negative." @@ -6329,10 +6364,10 @@ msgstr "Kan ikke uploade filer her" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Kan ikke uploade filer her" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Vælg baggrund" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Vælg enten guild_id eller channel_id, ikke begge." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Vælg Arkiv, derefter Tilføj til Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "kodeblok" msgid "Code blocks" msgstr "Kodeblokke" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Koden er udløbet" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Kommandoer" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Kommentarer er deaktiveret, fordi langsom tilstand er slået til i en eller flere valgte kanaler. Kommentaren ville blive blokeret af langsom tilstand lige efter videresendelsen." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Kommentarer er deaktiveret, fordi langsom tilstand er slået til i {forwardChannelDisplayName}. Kommentaren ville blive blokeret af langsom tilstand lige efter videresendelsen." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Kommentarer er ikke tilgængelige, når langsom tilstand er slået til." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Notifikationer for fællesskabsbeskeder" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Banner for fællesskabets MFA-krav" @@ -9182,7 +9222,7 @@ msgstr "Fortsæt med at anmelde fællesskab" msgid "Continue to report user" msgstr "Fortsæt med at anmelde bruger" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Fortsæt med adgangsnøgle" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Koordinerer raids, organiserede angreb eller chikane mod personer eller fællesskaber." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Kopieret" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kopiér kanallink" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kopiér kode" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Antal anvendelser:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Skjult i øjeblikket" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Vises i øjeblikket" @@ -12555,6 +12595,11 @@ msgstr "Blokerer ikke. Du kan genåbne senere." msgid "Domain" msgstr "Domæne" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domæne flyttet nagbar" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Nederlandsk" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "f.eks. YubiKey, iPhone, arbejdscomputer" @@ -14056,10 +14101,14 @@ msgstr "Indtast pinkoden til din sikkerhedsnøgle for at fortsætte." msgid "Enter the SMS code" msgstr "Indtast sms-koden" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Indtast denne kode i din browser for at logge ind." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Indtast denne kode i din gamle {PRODUCT_NAME}-app for at fuldføre login." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Tryk på Enter for at <0><1>gemme" @@ -14441,7 +14490,7 @@ msgstr "Udløber {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Udløber mellem {earliest} og {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Udløber om {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Kunne ikke starte SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Kunne ikke skifte konto" @@ -15413,17 +15462,17 @@ msgstr "Til mig" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Til dette tjek sender du os en sms i stedet for at modtage en fra os." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Af hensyn til din sikkerhed skal du bekræfte, at det er dig, for at fortsætte." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Fremtving deaktivering" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Fremtving aktivering" @@ -15540,7 +15589,7 @@ msgstr "Glemt adgangskode" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Glemt din adgangskode?" @@ -15571,7 +15620,7 @@ msgstr "Videresend medie" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Videresend besked" @@ -15965,11 +16014,11 @@ msgstr "Generer et link, der kan deles, eller kopiér din samlede brugerdefinere msgid "Generate invite link" msgstr "Generér invitationslink" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generer ny kode" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Genererer kode…" @@ -16045,6 +16094,11 @@ msgstr "Hent computerappen til {productName} for at bruge Tryk for at tale i hel msgid "Get the desktop app" msgstr "Hent computerappen" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Hent den nye {productName} app" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Indsæt emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Installer {productName} for at få den fulde kildevælger og mere flydende skærmdelingskontroller." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Installer det fra din browser menu." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Installer den nye app" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Ugyldig godkendelsesanmodning" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Ugyldige bot-tilladelser." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ugyldig eller udløbet kode. Prøv igen." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Ugyldig redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (indhold for voksne)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Link en ny enhed" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Indstillinger for visning af beskeder er kun tilgængelige på computere msgid "Message editing" msgstr "Redigering af beskeder" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Besked videresendt" @@ -20746,7 +20815,7 @@ msgstr "Knapper i skrivefeltet" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Beskeden er for lang" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Diverse" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Manglende client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Navnet skal være på mindst 2 tegn" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Navngiv adgangsnøgle" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formular til navngivning af adgangsnøgle" @@ -22381,7 +22450,7 @@ msgstr "Ingen ændringer at gemme" msgid "No channels available" msgstr "Ingen kanaler tilgængelige" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Ingen kanaler fundet" @@ -22824,7 +22893,7 @@ msgstr "Ingen brugere fundet" msgid "No verification is required." msgstr "Ingen bekræftelse er påkrævet." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Ingen bekræftelsesmetoder tilgængelige" @@ -23417,6 +23486,16 @@ msgstr "Åbn {contextName}" msgid "Open {displayName}" msgstr "Åbn {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Åbn {host} i Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Åbn {host} i din browser." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Åbn {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Åbn {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Åbn {productName} i din browser" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Åbn bannerindstillinger" msgid "Open bookmarks" msgstr "Åbn gemte beskeder" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Åbn browser" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Åbn emoji i browser" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Åbn emoji-vælger" @@ -23809,6 +23893,16 @@ msgstr "Åbn linket herunder i din browser, kopier derefter hele JSON-svaret, og msgid "Open the menu and choose an option." msgstr "Åbn menuen, og vælg en mulighed." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Åbn den nye app, og vælg \"Log ind med din gamle {productName}-app\". Vælg derefter \"Link en ny enhed her\" og indtast den kode, den viser." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Åbn den nye app. Du er allerede logget ind." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Åbn talechat" msgid "Open voice & video settings" msgstr "Åbn indstillinger for lyd og video" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Åbn din browser, log ind, og indtast derefter koden herunder for at forbinde din konto." @@ -23868,6 +23962,10 @@ msgstr "Åbn din browser, log ind, og indtast derefter koden herunder for at for msgid "Open your messages" msgstr "Åbn dine beskeder" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Åbn din gamle {PRODUCT_NAME}-app, og vælg \"Link en ny enhed\", og indtast derefter koden nedenfor." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Åbn din telefons beskedapp, og opret en ny sms." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Adgangsnøgle" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Navn på adgangsnøgle" @@ -28138,7 +28236,7 @@ msgstr "Mætning" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Forhåndsvisning ved spoling" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Markér tekst" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Vælg den konto, du vil logge ind med i computerappen." +msgid "Select the account you want to sign in with on your new device." +msgstr "Vælg den konto, du vil logge ind med på din nye enhed." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Send" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Send ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Session" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sessionen er udløbet for {identifier}. Log ind igen." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Genveje sættes på pause under optagelse." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Forkort beskeden, og prøv igen." @@ -30217,6 +30315,11 @@ msgstr "Vis maks. 3 (+5 badge)" msgid "Show max 5 (+5 badge)" msgstr "Vis maks. 5 (+5 badge)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Vis mig hvordan" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Log ind for at modtage din gave" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Log ind via browser" @@ -30496,6 +30599,11 @@ msgstr "Log ind via browser" msgid "Sign in with a passkey" msgstr "Log ind med en adgangsnøgle" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Log ind med din gamle {productName}-app" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Loginoplysninger" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Loginforløb" @@ -31030,7 +31138,7 @@ msgstr "Nogle brugere kan ikke tilføjes" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Tal i denne talekanal. Uden denne tilladelse forbliver medlemmers mikrof msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Tal i talekanaler. Uden denne tilladelse forbliver medlemmers mikrofon slået fra, indtil nogen med tilladelsen \"Slå medlemmers mikrofon fra\" slår den til." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Tryk på Del, og derefter Tilføj til hjemmeskærm." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Telefonbekræftelsen er udsat. Der mangler ét trin med e-mail." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Den angivne redirect_uri er ikke registreret for denne applikation." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Den angivne redirect_uri skal bruge HTTP eller HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Denne animation er for lang eller for stor til at beskære. Prøv en kortere eller mindre fil." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Denne app" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Denne vedhæftede fil er udløbet" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Denne bot er ikke offentlig og kan ikke tilføjes af andre brugere." @@ -33757,7 +33870,7 @@ msgstr "Denne gruppe har for mange personer" msgid "This group is full. Remove someone before adding more people." msgstr "Denne gruppe er fuld. Fjern en person, før du tilføjer flere." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Denne overdragelseskode er udløbet. Generer en ny for at fortsætte." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Ukendt" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Ukendt app, udløbet session eller ugyldig godkendelsesanmodning." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Anvendelser:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Bruger faktisk kontostatus" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Bruger gaveoversigtens faktiske status" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Bruger faktisk premium-status" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Bruger faktisk tilstand" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Bruger faktisk bekræftelsesstatus" @@ -37947,17 +38060,17 @@ msgstr "Vent her" msgid "Waiting for connection..." msgstr "Venter på forbindelse ..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Venter på adgangsnøgle …" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Venter på, at ventetiden i langsom tilstand udløber i {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Venter på, at ventetiden i langsom tilstand udløber i en eller flere valgte kanaler." @@ -38156,7 +38269,7 @@ msgstr "Vi har ændret størrelsen på og komprimeret dette klistermærke til 32 msgid "We sent a code to {email}." msgstr "Vi har sendt en kode til {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Vi udfylder automatisk {prefillEmail}, når browser-login åbnes." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Forhåndsvisninger af links" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Velkommen tilbage" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Dit {premiumProductName}-abonnement er udløbet. Du har mistet alle {premiumProductName}-fordele. Genaktiver dit abonnement for at få adgang igen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Din {productName}-session er udløbet. Log ind igen for at fortsætte med at godkende denne app." @@ -39734,7 +39847,7 @@ msgstr "Din konto er for ny til at sende beskeder i dette fællesskab." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Din kontoanmodning afventer godkendelse. Du kan logge ind, når en administrator har godkendt den." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Din konto kræver flerfaktorgodkendelse, men der er ingen understøttede metoder konfigureret på denne enhed. Log ind på en enhed med din godkendelsesapp eller sikkerhedsnøgle, eller kontakt support." @@ -39757,6 +39870,11 @@ msgstr "Din handling kunne ikke gennemføres, da den blev markeret af vores sikk msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Din ansøgning afventer gennemgang. Du kan stadig opdatere oplysningerne om dit fællesskab eller trække ansøgningen tilbage." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Din browser understøtter ikke sikkerhedsnøgler på dette domæne. Opdater din browser, eller log ind med din adgangskode og to-faktor-kode." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Dit kort opfylder kravene. Du bliver nu omdirigeret til betalingssiden." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Din teleudbyder siger, at nummeret ikke er i brug. Tjek det, og prøv igen, eller kontakt support, hvis det er korrekt." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Din kode" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Din kode er klar" diff --git a/fluxer_app/src/features/i18n/locales/de/messages.po b/fluxer_app/src/features/i18n/locales/de/messages.po index 435094155..6969909e2 100644 --- a/fluxer_app/src/features/i18n/locales/de/messages.po +++ b/fluxer_app/src/features/i18n/locales/de/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# blockierte Nachricht} other {# blockierte Nachric msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# Community gefunden} other {# Communitys gefunden}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# Community ist aufgrund einer Fehlfunktion des Fluxkompensators vorübergehend nicht verfügbar.} other {# Communitys sind aufgrund einer Fehlfunktion des Fluxkompensators vorübergehend nicht verfügbar.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} hat einen potenziellen Raid in dieser Community erkannt. E msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} hat einen potenziellen Raid erkannt. Neue Nutzer können deshalb derzeit nicht beitreten." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} ist zu {host} umgezogen. Füge es zu deinem Home Screen oder Dock hinzu und melde dich dann mit dieser App an." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} ist zu {host} umgezogen. Installiere es in deinem Browser und melde dich dann mit dieser App an." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} ist zu {host} umgezogen. Installiere die neue App und du bist bereits angemeldet." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} ist zu {host} umgezogen. Öffne es in deinem Browser, um die neue App zu installieren." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} benötigt Mikrofonzugriff, um deine Geräte aufzulisten." msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} benötigt Zugriff auf die Bildschirmaufnahme. Erlaube die Bildschirmaufnahme in den Datenschutzeinstellungen deines Betriebssystems und starte die App neu." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} ist jetzt unter {host} erreichbar. Installiere es von dort und melde dich dann mit dieser App an." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} ist jetzt unter {host} erreichbar. Installiere es von dort." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "eine Umfrage" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Eine redirect_uri ist erforderlich, wenn der Bot-Scope nicht der einzige Scope ist." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Einen Bot mit den angeforderten Berechtigungen zu einer Community hinzufügen" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Kommentar hinzufügen (optional)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Füge einen YouTube Data API-Schlüssel hinzu, um YouTube-Links anzureichern." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Account hinzufügen" @@ -4379,6 +4409,11 @@ msgstr "Genehmigung erforderlich" msgid "Approve" msgstr "Genehmigen" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Bestätige diese App aus der {productName}-App, die du bereits verwendest. Kein Passwort erforderlich." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autorisierung fehlgeschlagen" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorisierung fehlgeschlagen. Bitte versuche es noch einmal." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Bot-Berechtigungen" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Bot-Berechtigungen können nicht negativ sein." @@ -6329,10 +6364,10 @@ msgstr "Dateien können hier nicht hochgeladen werden" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Dateien können hier nicht hochgeladen werden" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Hintergrund auswählen" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Wähle entweder „guild_id“ oder „channel_id“, nicht beides." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Wähle \"Ablage\" und dann \"Zum Dock hinzufügen\"." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "Codeblock" msgid "Code blocks" msgstr "Codeblöcke" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Code abgelaufen" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Befehle" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Kommentare sind deaktiviert, da in einem oder mehreren ausgewählten Kanälen der Slow-Modus aktiviert ist. Der Kommentar würde direkt nach dem Weiterleiten durch den Slow-Modus blockiert werden." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Kommentare sind deaktiviert, da der Slow-Modus in {forwardChannelDisplayName} aktiviert ist. Der Kommentar würde direkt nach dem Weiterleiten durch den Slow-Modus blockiert werden." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Kommentare sind nicht verfügbar, solange der Slow-Modus aktiviert ist." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Benachrichtigungen für Community-Nachrichten" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Hinweisleiste zur MFA-Pflicht in der Community" @@ -9182,7 +9222,7 @@ msgstr "Weiter zur Community-Meldung" msgid "Continue to report user" msgstr "Weiter zur Nutzermeldung" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Mit Passkey fortfahren" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Koordiniert Raids, Brigading oder Belästigung gegen Personen oder Communitys." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Kopiert" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kanallink kopieren" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Code kopieren" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Aktuelle Nutzungen:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Derzeit ausgeblendet" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Wird gerade angezeigt" @@ -12555,6 +12595,11 @@ msgstr "Blockiert nicht. Du kannst es später wieder öffnen." msgid "Domain" msgstr "Domain" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domain umgezogen-Hinweis" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Niederländisch" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "z. B. YubiKey, iPhone, Arbeitscomputer" @@ -14056,10 +14101,14 @@ msgstr "Gib die PIN für deinen Sicherheitsschlüssel ein, um fortzufahren." msgid "Enter the SMS code" msgstr "SMS-Code eingeben" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Gib diesen Code in deinem Browser ein, um die Anmeldung abzuschließen." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Gib diesen Code in deine alte {PRODUCT_NAME}-App ein, um die Anmeldung abzuschließen." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Eingabetaste zum <0><1>Speichern" @@ -14441,7 +14490,7 @@ msgstr "Läuft ab am {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Läuft zwischen {earliest} und {latest} ab" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Läuft in {remaining}s ab" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "SSO konnte nicht gestartet werden" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Accountwechsel fehlgeschlagen" @@ -15413,17 +15462,17 @@ msgstr "Für mich" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Sende uns für diese Überprüfung eine SMS, anstatt eine von uns zu erhalten." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Bestätige bitte zu deiner Sicherheit, dass du es bist, um fortzufahren." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Erzwungen deaktiviert" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Erzwungen aktiviert" @@ -15540,7 +15589,7 @@ msgstr "Passwort vergessen" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Passwort vergessen?" @@ -15571,7 +15620,7 @@ msgstr "Medien weiterleiten" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Nachricht weiterleiten" @@ -15965,11 +16014,11 @@ msgstr "Generiere einen teilbaren Link oder kopiere dein kombiniertes benutzerde msgid "Generate invite link" msgstr "Einladungslink erstellen" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Neuen Code generieren" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Code wird generiert …" @@ -16045,6 +16094,11 @@ msgstr "Hol dir die {productName}-Desktop-App für systemweites Push-to-Talk und msgid "Get the desktop app" msgstr "Desktop-App herunterladen" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Hol dir die neue {productName} App" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Emoji einfügen" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Installiere {productName} für die vollständige Quellenauswahl und eine reibungslosere Bildschirmfreigabe." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Installiere es über das Menü deines Browsers." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Neue App installieren" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Ungültige Autorisierungsanfrage" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Ungültige Bot-Berechtigungen." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ungültiger oder abgelaufener Code. Bitte versuche es noch einmal." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Ungültiger Parameter redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (nicht jugendfrei)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Neues Gerät verknüpfen" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Nachrichtenanzeige-Einstellungen sind nur auf dem Desktop verfügbar." msgid "Message editing" msgstr "Nachrichten bearbeiten" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Nachricht weitergeleitet" @@ -20746,7 +20815,7 @@ msgstr "Schaltflächen für die Nachrichteneingabe" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Nachricht ist zu lang" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Verschiedenes" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id fehlt" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Name muss mindestens 2 Zeichen lang sein" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Passkey benennen" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formular zum Benennen eines Passkeys" @@ -22381,7 +22450,7 @@ msgstr "Keine Änderungen zum Speichern" msgid "No channels available" msgstr "Keine Kanäle verfügbar" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Keine Kanäle gefunden" @@ -22824,7 +22893,7 @@ msgstr "Keine Benutzer gefunden" msgid "No verification is required." msgstr "Keine Verifizierung erforderlich." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Keine Verifizierungsmethoden verfügbar" @@ -23417,6 +23486,16 @@ msgstr "{contextName} öffnen" msgid "Open {displayName}" msgstr "{displayName} öffnen" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Öffne {host} in Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Öffne {host} in deinem Browser." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Öffne „{macosSystemSettingsName}“ → „{macosPrivacyAndSecuritySe msgid "Open {productName}" msgstr "{productName} öffnen" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Öffne {productName} in deinem Browser" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Banner-Optionen öffnen" msgid "Open bookmarks" msgstr "Lesezeichen öffnen" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Browser öffnen" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Emoji im Browser öffnen" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Emoji-Auswahl öffnen" @@ -23809,6 +23893,16 @@ msgstr "Öffne den Link unten in deinem Browser, kopiere dann die gesamte JSON-A msgid "Open the menu and choose an option." msgstr "Öffne das Menü und wähle eine Option aus." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Öffne die neue App und wähle \"Mit deiner alten {productName}-App anmelden\". Wähle dann hier \"Neues Gerät verknüpfen\" und gib den angezeigten Code ein." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Öffne die neue App. Du bist bereits angemeldet." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Sprachchat öffnen" msgid "Open voice & video settings" msgstr "Einstellungen für Audio & Video öffnen" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Öffne deinen Browser, melde dich an und gib dann den folgenden Code ein, um deinen Account zu verknüpfen." @@ -23868,6 +23962,10 @@ msgstr "Öffne deinen Browser, melde dich an und gib dann den folgenden Code ein msgid "Open your messages" msgstr "Öffne deine Nachrichten" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Öffne deine alte {PRODUCT_NAME} App und wähle \"Neues Gerät verknüpfen\", gib dann den folgenden Code ein." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Öffne die Nachrichten-App deines Smartphones und erstelle eine neue SMS." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Passkey" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Passkey-Name" @@ -28138,7 +28236,7 @@ msgstr "Sättigung" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Vorschau beim Spulen" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Text auswählen" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Wähle den Account aus, mit dem du dich in der Desktop-App anmelden möchtest." +msgid "Select the account you want to sign in with on your new device." +msgstr "Wähle das Konto aus, mit dem du dich auf deinem neuen Gerät anmelden möchtest." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Senden" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Senden ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sitzung" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sitzung für {identifier} abgelaufen. Melde dich erneut an." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Während der Aufnahme sind Tastenkombinationen pausiert." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Nachricht kürzen und erneut versuchen." @@ -30217,6 +30315,11 @@ msgstr "Max. 3 anzeigen (+5 Abzeichen)" msgid "Show max 5 (+5 badge)" msgstr "Max. 5 anzeigen (+5 Abzeichen)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Zeig es mir" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Melde dich an, um dein Geschenk einzulösen" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Im Browser anmelden" @@ -30496,6 +30599,11 @@ msgstr "Im Browser anmelden" msgid "Sign in with a passkey" msgstr "Mit einem Passkey anmelden" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Mit deiner alten {productName}-App anmelden" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Anmeldedaten" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Anmeldevorgang" @@ -31030,7 +31138,7 @@ msgstr "Einige Nutzer können nicht hinzugefügt werden" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "In diesem Sprachkanal sprechen. Ohne diese Berechtigung bleiben Mitglied msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "In Sprachkanälen sprechen. Ohne diese Berechtigung bleiben Mitglieder stummgeschaltet, bis jemand mit der Berechtigung „Mitglieder stummschalten“ ihre Stummschaltung aufhebt." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Tippe auf Teilen und dann auf „Zum Home-Bildschirm hinzufügen“." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Die Telefonprüfung wurde zurückgestellt. Ein E-Mail-Schritt ist noch übrig." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Die angegebene redirect_uri ist für diese Anwendung nicht registriert." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Die angegebene redirect_uri muss HTTP oder HTTPS verwenden." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Diese Animation ist zu lang oder zu groß zum Zuschneiden. Versuche es mit einer kürzeren oder kleineren Datei." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Diese Anwendung" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Dieser Anhang ist abgelaufen" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Dieser Bot ist nicht öffentlich und kann nicht von anderen Nutzern hinzugefügt werden." @@ -33757,7 +33870,7 @@ msgstr "Diese Gruppe hat zu viele Mitglieder" msgid "This group is full. Remove someone before adding more people." msgstr "Diese Gruppe ist voll. Entferne jemanden, bevor du weitere Personen hinzufügst." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Dieser Übergabecode ist abgelaufen. Erstelle einen neuen, um fortzufahren." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Unbekannt" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Unbekannte Anwendung, abgelaufene Sitzung oder ungültige Autorisierungsanfrage." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Nutzungen:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Verwendet den tatsächlichen Accountstatus" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Verwendet den tatsächlichen Status des Geschenkinventars" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Verwendet den tatsächlichen Premium-Status" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Verwendet den tatsächlichen Status" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Verwendet den tatsächlichen Verifizierungsstatus" @@ -37947,17 +38060,17 @@ msgstr "Hier warten" msgid "Waiting for connection..." msgstr "Warten auf Verbindung..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Warte auf Passkey …" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Warten, bis die Wartezeit des Slow-Modus in {forwardChannelDisplayName} abgelaufen ist." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Warten, bis die Wartezeit des Slow-Modus in einem oder mehreren ausgewählten Kanälen abgelaufen ist." @@ -38156,7 +38269,7 @@ msgstr "Wir haben diesen Sticker auf 320x320 Pixel verkleinert und komprimiert, msgid "We sent a code to {email}." msgstr "Wir haben einen Code an {email} gesendet." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Wir tragen \"{prefillEmail}\" automatisch ein, sobald sich die Browser-Anmeldung öffnet." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Webseiten-Vorschauen" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Willkommen zurück" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Dein {premiumProductName}-Abo ist abgelaufen. Du hast alle {premiumProductName}-Vorteile verloren. Reaktiviere dein Abo, um den Zugriff wiederherzustellen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Deine {productName}-Sitzung ist abgelaufen. Melde dich erneut an, um die Autorisierung dieser Anwendung fortzusetzen." @@ -39734,7 +39847,7 @@ msgstr "Dein Account ist zu neu, um Nachrichten in dieser Community zu senden." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Deine Accountanfrage wartet auf Genehmigung. Du kannst dich anmelden, nachdem ein Admin sie genehmigt hat." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Für deinen Account ist die Multi-Faktor-Authentifizierung erforderlich, aber auf diesem Gerät sind keine unterstützten Methoden eingerichtet. Melde dich auf einem Gerät mit deiner Authenticator-App oder deinem Sicherheitsschlüssel an oder wende dich an den Support." @@ -39757,6 +39870,11 @@ msgstr "Deine Aktion konnte nicht abgeschlossen werden, da sie von unseren Siche msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Dein Antrag wartet auf Prüfung. Du kannst die Details deines Eintrags weiterhin aktualisieren oder den Antrag zurückziehen." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Dein Browser unterstützt keine Passkeys für diese Domain. Aktualisiere deinen Browser oder melde dich mit deinem Passwort und deinem Zwei-Faktor-Code an." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Deine Karte ist gültig. Du wirst jetzt zur Zahlungsseite weitergeleitet msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Dein Mobilfunkanbieter gibt an, dass diese Nummer nicht vergeben ist. Überprüfe sie und versuche es erneut, oder kontaktiere den Support, falls sie korrekt ist." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Dein Code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Dein Code ist bereit" diff --git a/fluxer_app/src/features/i18n/locales/el/messages.po b/fluxer_app/src/features/i18n/locales/el/messages.po index 5ee4dd521..033189908 100644 --- a/fluxer_app/src/features/i18n/locales/el/messages.po +++ b/fluxer_app/src/features/i18n/locales/el/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# αποκλεισμένο μήνυμα} other {# msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# κοινότητα βρέθηκε} other {# κοινότητες βρέθηκαν}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# κοινότητα είναι προσωρινά μη διαθέσιμη λόγω δυσλειτουργίας του πυκνωτή ροής.} other {# κοινότητες είναι προσωρινά μη διαθέσιμες λόγω δυσλειτουργίας του πυκνωτή ροής.}}" @@ -1692,6 +1692,26 @@ msgstr "Το {productName} εντόπισε μια πιθανή επιδρομή msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "Το {productName} εντόπισε μια πιθανή επιδρομή, οπότε οι νέοι χρήστες δεν μπορούν να συμμετάσχουν αυτήν τη στιγμή." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "Το {productName} μεταφέρθηκε στο {host}. Προσθέστε το στην Αρχική οθόνη ή στο Dock σας και, στη συνέχεια, συνδεθείτε με αυτήν την εφαρμογή." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "Το {productName} μεταφέρθηκε στο {host}. Εγκαταστήστε το από το πρόγραμμα περιήγησής σας και, στη συνέχεια, συνδεθείτε με αυτήν την εφαρμογή." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "Το {productName} μεταφέρθηκε στο {host}. Εγκαταστήστε τη νέα εφαρμογή και θα είστε ήδη συνδεδεμένοι." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "Το {productName} μεταφέρθηκε στο {host}. Ανοίξτε το στο πρόγραμμα περιήγησής σας για να εγκαταστήσετε τη νέα εφαρμογή." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "Το {productName} χρειάζεται πρόσβαση στο μικρ msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "Το {productName} χρειάζεται πρόσβαση στην εγγραφή οθόνης. Επιτρέψτε την εγγραφή οθόνης στις ρυθμίσεις απορρήτου του λειτουργικού σας συστήματος και επανεκκινήστε την εφαρμογή." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "Το {productName} βρίσκεται πλέον στο {host}. Εγκαταστήστε το από εκεί και, στη συνέχεια, συνδεθείτε με αυτήν την εφαρμογή." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "Το {productName} βρίσκεται πλέον στο {host}. Εγκαταστήστε το από εκεί." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "μια δημοσκόπηση" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Απαιτείται ένα redirect_uri όταν το πεδίο εφαρμογής bot δεν είναι το μόνο πεδίο εφαρμογής." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Προσθήκη bot σε μια κοινότητα με τα ζητούμενα δικαιώματα" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Προσθήκη σχολίου (προαιρετικό)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Προσθέστε ένα κλειδί API YouTube Data για να εμπλουτίσετε τους συνδέσμους του YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Προσθήκη λογαριασμού" @@ -4379,6 +4409,11 @@ msgstr "Απαιτείται έγκριση" msgid "Approve" msgstr "Έγκριση" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Επιβεβαίωσε αυτήν την εφαρμογή από την εφαρμογή {productName} που χρησιμοποιείς ήδη. Δεν χρειάζεται κωδικός πρόσβασης." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Η εξουσιοδότηση απέτυχε" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Η εξουσιοδότηση απέτυχε. Δοκιμάστε ξανά." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Δικαιώματα bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Τα δικαιώματα bot δεν μπορούν να είναι αρνητικά." @@ -6329,10 +6364,10 @@ msgstr "Δεν μπορείτε να ανεβάσετε αρχεία εδώ" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Δεν μπορείτε να ανεβάσετε αρχεία εδώ" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Επιλέξτε φόντο" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Επιλέξτε είτε guild_id είτε channel_id, όχι και τα δύο." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Επιλέξτε Αρχείο, έπειτα Προσθήκη στο Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "μπλοκ κώδικα" msgid "Code blocks" msgstr "Μπλοκ κώδικα" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Ο κωδικός έληξε" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Εντολές" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Τα σχόλια είναι απενεργοποιημένα επειδή ένα ή περισσότερα επιλεγμένα κανάλια έχουν ενεργοποιημένη την αργή λειτουργία. Το σχόλιο θα αποκλειόταν από την αργή λειτουργία αμέσως μετά την προώθηση." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Τα σχόλια είναι απενεργοποιημένα επειδή η αργή λειτουργία είναι ενεργοποιημένη στο {forwardChannelDisplayName}. Το σχόλιο θα αποκλειόταν από την αργή λειτουργία αμέσως μετά την προώθηση." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Τα σχόλια δεν είναι διαθέσιμα όσο είναι ενεργή η αργή λειτουργία." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Ειδοποιήσεις μηνυμάτων κοινότητας" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Γραμμή ειδοποιήσεων απαίτησης MFA κοινότητας" @@ -9182,7 +9222,7 @@ msgstr "Συνέχεια αναφοράς κοινότητας" msgid "Continue to report user" msgstr "Συνέχεια αναφοράς χρήστη" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Συνέχεια με κλειδί πρόσβασης" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Συντονίζει επιδρομές, οργανωμένες επιθέσεις ή παρενόχληση εναντίον ατόμων ή κοινοτήτων." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Αντιγράφηκε" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Αντιγραφή συνδέσμου καναλιού" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Αντιγραφή κωδικού" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Τρέχουσες χρήσεις:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Έχει απορριφθεί προς το παρόν" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Εμφανίζεται τώρα" @@ -12555,6 +12595,11 @@ msgstr "Δεν μπλοκάρει. Μπορείτε να το ξανανοίξε msgid "Domain" msgstr "Τομέας" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Η διεύθυνση άλλαξε" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Ολλανδικά" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "π.χ. YubiKey, iPhone, υπολογιστής εργασίας" @@ -14056,10 +14101,14 @@ msgstr "Εισαγάγετε τον κωδικό PIN για το κλειδί α msgid "Enter the SMS code" msgstr "Εισαγάγετε τον κωδικό SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Πληκτρολογήστε αυτόν τον κωδικό στο πρόγραμμα περιήγησής σας για να ολοκληρώσετε τη σύνδεση." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Εισαγάγετε αυτόν τον κωδικό στην παλιά σας εφαρμογή {PRODUCT_NAME} για να ολοκληρώσετε τη σύνδεση." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Πατήστε Enter για <0><1>αποθήκευση" @@ -14441,7 +14490,7 @@ msgstr "Λήγει στις {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Λήγει μεταξύ {earliest} και {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Λήγει σε {remaining} δευτ." @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Αποτυχία έναρξης SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Αποτυχία αλλαγής λογαριασμού" @@ -15413,17 +15462,17 @@ msgstr "Για μένα" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Για αυτόν τον έλεγχο, μας στέλνετε ένα μήνυμα αντί να λάβετε ένα από εμάς." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Για την ασφάλειά σας, επιβεβαιώστε ότι είστε εσείς για να συνεχίσετε." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Αναγκαστική απενεργοποίηση" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Αναγκαστική ενεργοποίηση" @@ -15540,7 +15589,7 @@ msgstr "Ξεχάσατε τον κωδικό πρόσβασης" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Ξεχάσατε τον κωδικό σας;" @@ -15571,7 +15620,7 @@ msgstr "Προώθηση πολυμέσων" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Προώθηση μηνύματος" @@ -15965,11 +16014,11 @@ msgstr "Δημιουργήστε έναν σύνδεσμο κοινής χρήσ msgid "Generate invite link" msgstr "Δημιουργία συνδέσμου πρόσκλησης" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Δημιουργία νέου κωδικού" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Δημιουργία κωδικού…" @@ -16045,6 +16094,11 @@ msgstr "Αποκτήστε την εφαρμογή υπολογιστή {product msgid "Get the desktop app" msgstr "Κατεβάστε την εφαρμογή υπολογιστή" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Λήψη της νέας εφαρμογής {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Εισαγωγή emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Εγκαταστήστε το {productName} για τον πλήρη επιλογέα πηγών και πιο ομαλά χειριστήρια κοινής χρήσης οθόνης." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Εγκατέστησέ το από το μενού του προγράμματος περιήγησής σου." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Εγκατάσταση της νέας εφαρμογής" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Μη έγκυρο αίτημα εξουσιοδότησης" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Μη έγκυρα δικαιώματα bot." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Μη έγκυρος ή ληγμένος κωδικός. Δοκιμάστε ξανά." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Μη έγκυρο redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Σύνδεσμος" msgid "Link (mature)" msgstr "Σύνδεσμος (ενηλίκων)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Σύνδεσε μια νέα συσκευή" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Οι ρυθμίσεις εμφάνισης μηνυμάτων είναι msgid "Message editing" msgstr "Επεξεργασία μηνύματος" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Το μήνυμα προωθήθηκε" @@ -20746,7 +20815,7 @@ msgstr "Κουμπιά εισαγωγής μηνυμάτων" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Το μήνυμα είναι πολύ μεγάλο" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Διάφορα" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Λείπει το client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Το όνομα πρέπει να έχει τουλάχιστον 2 χαρακτήρες" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Ονομασία κλειδιού πρόσβασης" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Φόρμα ονομασίας κλειδιού πρόσβασης" @@ -22381,7 +22450,7 @@ msgstr "Δεν υπάρχουν αλλαγές για αποθήκευση" msgid "No channels available" msgstr "Δεν υπάρχουν διαθέσιμα κανάλια" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Δεν βρέθηκαν κανάλια" @@ -22824,7 +22893,7 @@ msgstr "Δεν βρέθηκαν χρήστες" msgid "No verification is required." msgstr "Δεν απαιτείται επαλήθευση." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Δεν υπάρχουν διαθέσιμες μέθοδοι επαλήθευσης" @@ -23417,6 +23486,16 @@ msgstr "Άνοιγμα {contextName}" msgid "Open {displayName}" msgstr "Άνοιγμα {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Άνοιξε το {host} στο Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Άνοιξε το {host} στο πρόγραμμα περιήγησής σου." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Ανοίξτε τις {macosSystemSettingsName} → {macosPrivacyAndSecu msgid "Open {productName}" msgstr "Άνοιγμα {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Άνοιγμα του {productName} στο πρόγραμμα περιήγησής σας" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Άνοιγμα επιλογών banner" msgid "Open bookmarks" msgstr "Άνοιγμα σελιδοδεικτών" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Άνοιγμα προγράμματος περιήγησης" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Άνοιγμα emoji στο πρόγραμμα περιήγησης" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Άνοιγμα επιλογέα emoji" @@ -23809,6 +23893,16 @@ msgstr "Ανοίξτε τον παρακάτω σύνδεσμο στο πρόγ msgid "Open the menu and choose an option." msgstr "Ανοίξτε το μενού και κάντε μια επιλογή." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Ανοίξτε τη νέα εφαρμογή και επιλέξτε \"Σύνδεση με την παλιά σας εφαρμογή {productName}\". Στη συνέχεια, επιλέξτε \"Σύνδεση νέας συσκευής εδώ\" και εισαγάγετε τον κωδικό που εμφανίζεται." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Άνοιξε τη νέα εφαρμογή. Είσαι ήδη συνδεδεμένος/η." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Άνοιγμα φωνητικής συνομιλίας" msgid "Open voice & video settings" msgstr "Άνοιγμα ρυθμίσεων φωνής και βίντεο" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Ανοίξτε το πρόγραμμα περιήγησής σας, συνδεθείτε και, στη συνέχεια, εισαγάγετε τον παρακάτω κωδικό για να συνδέσετε τον λογαριασμό σας." @@ -23868,6 +23962,10 @@ msgstr "Ανοίξτε το πρόγραμμα περιήγησής σας, συ msgid "Open your messages" msgstr "Ανοίξτε τα μηνύματά σας" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Άνοιξε την παλιά σου εφαρμογή {PRODUCT_NAME} και επίλεξε \"Σύνδεση νέας συσκευής\", έπειτα εισήγαγε τον παρακάτω κωδικό." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Ανοίξτε την εφαρμογή μηνυμάτων του τηλεφώνου σας και δημιουργήστε ένα νέο μήνυμα κειμένου." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Κλειδί πρόσβασης" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Όνομα κλειδιού πρόσβασης" @@ -28138,7 +28236,7 @@ msgstr "Κορεσμός" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Προεπισκόπηση μετακίνησης" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Επιλογή κειμένου" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Επιλέξτε τον λογαριασμό με τον οποίο θέλετε να συνδεθείτε στην εφαρμογή για υπολογιστές." +msgid "Select the account you want to sign in with on your new device." +msgstr "Επιλέξτε τον λογαριασμό με τον οποίο θέλετε να συνδεθείτε στη νέα σας συσκευή." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Αποστολή" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Αποστολή ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Συνεδρία" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Η συνεδρία για το {identifier} έληξε. Συνδεθείτε ξανά." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Οι συντομεύσεις απενεργοποιούνται προσωρινά κατά την καταγραφή." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Συντομεύστε το μήνυμα και δοκιμάστε ξανά." @@ -30217,6 +30315,11 @@ msgstr "Εμφάνιση έως 3 (σήμα +5)" msgid "Show max 5 (+5 badge)" msgstr "Εμφάνιση έως 5 (σήμα +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Δείξε μου πώς" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Συνδεθείτε για να παραλάβετε το δώρο σα #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Σύνδεση μέσω προγράμματος περιήγησης" @@ -30496,6 +30599,11 @@ msgstr "Σύνδεση μέσω προγράμματος περιήγησης" msgid "Sign in with a passkey" msgstr "Σύνδεση με κλειδί πρόσβασης" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Σύνδεση με την παλιά σας εφαρμογή {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Στοιχεία σύνδεσης" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Ροή σύνδεσης" @@ -31030,7 +31138,7 @@ msgstr "Ορισμένοι χρήστες δεν μπορούν να προστ #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Ομιλία σε αυτό το φωνητικό κανάλι. Χωρί msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Ομιλία στα φωνητικά κανάλια. Χωρίς αυτό το δικαίωμα, τα μέλη παραμένουν σε σίγαση μέχρι κάποιος με το δικαίωμα «Σίγαση μελών» να τους άρει τη σίγαση." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Πατήστε Κοινή χρήση, έπειτα Προσθήκη στην Αρχική οθόνη." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Αφήσαμε στην άκρη τον τηλεφωνικό έλεγχο. Απομένει ένα βήμα με το email." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Το redirect_uri που δόθηκε δεν είναι καταχωρισμένο για αυτήν την εφαρμογή." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Το redirect_uri που δόθηκε πρέπει να χρησιμοποιεί HTTP ή HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Αυτό το κινούμενο σχέδιο είναι πολύ μεγάλο ή έχει πάρα πολλά καρέ για να γίνει περικοπή. Δοκιμάστε ένα μικρότερο αρχείο." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Αυτή η εφαρμογή" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Αυτό το συνημμένο έχει λήξει" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Αυτό το bot δεν είναι δημόσιο και δεν μπορεί να προστεθεί από άλλους χρήστες." @@ -33757,7 +33870,7 @@ msgstr "Αυτή η ομάδα έχει πάρα πολλά άτομα" msgid "This group is full. Remove someone before adding more people." msgstr "Αυτή η ομάδα είναι πλήρης. Αφαιρέστε κάποιον πριν προσθέσετε κι άλλα άτομα." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Αυτός ο κωδικός παράδοσης έχει λήξει. Δημιουργήστε έναν νέο για να συνεχίσετε." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Άγνωστο" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Άγνωστη εφαρμογή, ληγμένη συνεδρία ή μη έγκυρο αίτημα εξουσιοδότησης." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Χρήσεις:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Χρήση της πραγματικής κατάστασης λογαριασμού" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Χρήση της πραγματικής κατάστασης αποθέματος δώρων" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Χρήση της πραγματικής κατάστασης premium" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Χρήση της πραγματικής κατάστασης" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Χρήση της πραγματικής κατάστασης επαλήθευσης" @@ -37947,17 +38060,17 @@ msgstr "Περιμένετε εδώ" msgid "Waiting for connection..." msgstr "Αναμονή σύνδεσης..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Αναμονή για κλειδί πρόσβασης…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Αναμονή λήξης της αργής λειτουργίας στο κανάλι {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Αναμονή λήξης της αργής λειτουργίας σε ένα ή περισσότερα επιλεγμένα κανάλια." @@ -38156,7 +38269,7 @@ msgstr "Αλλάξαμε το μέγεθος και συμπιέσαμε αυτ msgid "We sent a code to {email}." msgstr "Στείλαμε έναν κωδικό στο {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Θα συμπληρώσουμε αυτόματα το {prefillEmail} μόλις ανοίξει η σύνδεση στο πρόγραμμα περιήγησης." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Προεπισκοπήσεις ιστοτόπων" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Καλώς ήρθες ξανά" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Η συνδρομή σας στο {premiumProductName} έληξε. Έχετε χάσει όλα τα προνόμια του {premiumProductName}. Ενεργοποιήστε ξανά τη συνδρομή σας για να αποκτήσετε ξανά πρόσβαση." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Η περίοδος λειτουργίας σας στο {productName} έληξε. Συνδεθείτε ξανά για να συνεχίσετε την εξουσιοδότηση αυτής της εφαρμογής." @@ -39734,7 +39847,7 @@ msgstr "Ο λογαριασμός σας είναι πολύ καινούριο msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Το αίτημα λογαριασμού σας εκκρεμεί για έγκριση. Μπορείτε να συνδεθείτε αφού το εγκρίνει ένας διαχειριστής." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Ο λογαριασμός σας απαιτεί έλεγχο ταυτότητας πολλών παραγόντων, αλλά δεν έχουν ρυθμιστεί υποστηριζόμενες μέθοδοι σε αυτήν τη συσκευή. Συνδεθείτε σε μια συσκευή με την εφαρμογή ελέγχου ταυτότητας ή το κλειδί ασφαλείας σας ή επικοινωνήστε με την υποστήριξη." @@ -39757,6 +39870,11 @@ msgstr "Η ενέργειά σας δεν ολοκληρώθηκε επειδή msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Η αίτησή σας εκκρεμεί για έλεγχο. Μπορείτε ακόμα να ενημερώσετε τα στοιχεία της καταχώρισής σας ή να αποσύρετε την αίτηση." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Ο περιηγητής σας δεν υποστηρίζει κλειδιά πρόσβασης σε αυτόν τον τομέα. Ενημερώστε τον περιηγητή σας ή συνδεθείτε με τον κωδικό πρόσβασης και τον διπλό κωδικό επαλήθευσης." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Η κάρτα σας πληροί τις προϋποθέσεις. Σα msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Ο πάροχός σας αναφέρει ότι αυτός ο αριθμός δεν είναι σε χρήση. Ελέγξτε τον και δοκιμάστε ξανά, ή επικοινωνήστε με την υποστήριξη αν είναι σωστός." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Ο κωδικός σας" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Ο κωδικός σας είναι έτοιμος" diff --git a/fluxer_app/src/features/i18n/locales/en-GB/messages.po b/fluxer_app/src/features/i18n/locales/en-GB/messages.po index 72696b165..26cf7adfc 100644 --- a/fluxer_app/src/features/i18n/locales/en-GB/messages.po +++ b/fluxer_app/src/features/i18n/locales/en-GB/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# blocked message} other {# blocked messages}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community found} other {# communities found}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} detected a potential raid in this community. Invites are p msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} detected a potential raid, so new users can't join right now." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} has moved to {host}. Install it from your browser, then sign in with this app." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} has moved to {host}. Install the new app and you will already be signed in." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} has moved to {host}. Open it in your browser to install the new app." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} needs microphone access to list your devices." msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} now lives at {host}. Install it from there, then sign in with this app." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} now lives at {host}. Install it from there." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "a poll" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "A redirect_uri is required when the bot scope is not the only scope." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Add a bot to a community with requested permissions" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Add a comment (optional)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Add a YouTube Data API key to enrich YouTube links." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Add account" @@ -4379,6 +4409,11 @@ msgstr "Approval required" msgid "Approve" msgstr "Approve" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Approve this app from the {productName} app you already use. No password needed." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Authorisation failed" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Authorisation failed. Try again." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Bot permissions" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Bot permissions cannot be negative." @@ -6329,10 +6364,10 @@ msgstr "Can't upload files here" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Can't upload files here" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Choose background" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Choose either guild_id or channel_id, not both." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Choose File, then Add to Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "code block" msgid "Code blocks" msgstr "Code blocks" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Code expired" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Commands" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Comments are unavailable while slowmode is on." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Community message notifications" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Community MFA requirement nagbar" @@ -8754,7 +8794,6 @@ msgstr "Composer buttons" msgid "Composer focus" msgstr "Composer focus" -# auto-i18n: reviewed unchanged #. Progress label on the save button while a cropped animated image is compressed. Keep it concise. #: src/features/messaging/components/modals/ImageCropModal.tsx:67 msgid "Compressing" @@ -9183,7 +9222,7 @@ msgstr "Continue to report community" msgid "Continue to report user" msgstr "Continue to report user" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Continue with passkey" @@ -9267,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coordinates raids, brigading, or harassment against people or communities." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Copied" @@ -9369,7 +9408,7 @@ msgid "Copy channel link" msgstr "Copy channel link" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Copy code" @@ -10560,12 +10599,12 @@ msgid "Current uses:" msgstr "Current uses:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Currently dismissed" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Currently showing" @@ -12556,6 +12595,11 @@ msgstr "Doesn't block. You can reopen later." msgid "Domain" msgstr "Domain" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domain moved nagbar" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12913,7 +12957,7 @@ msgid "Dutch" msgstr "Dutch" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "e.g., YubiKey, iPhone, work computer" @@ -14057,10 +14101,14 @@ msgstr "Enter the PIN for your security key to continue." msgid "Enter the SMS code" msgstr "Enter the SMS code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Enter this code in your browser to complete sign-in." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter to <0><1>save" @@ -14442,7 +14490,7 @@ msgstr "Expires {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Expires between {earliest} and {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Expires in {remaining}s" @@ -14836,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Failed to start SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Failed to switch account" @@ -15414,17 +15462,17 @@ msgstr "For me" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "For this check, you send us a text message instead of receiving one from us." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "For your security, please confirm it's you to continue." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Force disabled" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Force enabled" @@ -15541,7 +15589,7 @@ msgstr "Forgot password" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Forgot your password?" @@ -15572,7 +15620,7 @@ msgstr "Forward media" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Forward message" @@ -15966,11 +16014,11 @@ msgstr "Generate a shareable link or copy your combined custom CSS." msgid "Generate invite link" msgstr "Generate invite link" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generate new code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generating code…" @@ -16046,6 +16094,11 @@ msgstr "Get the {productName} desktop app for system-wide push-to-talk and a few msgid "Get the desktop app" msgstr "Get the desktop app" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Get the new {productName} app" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18038,6 +18091,16 @@ msgstr "Insert emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Install {productName} for the full source picker and smoother screen-sharing controls." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Install it from your browser menu." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Install the new app" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18124,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Invalid authorisation request" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Invalid bot permissions." @@ -18165,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Invalid or expired code. Try again." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Invalid redirect_uri" @@ -19333,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (mature)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Link a new device" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20698,7 +20766,7 @@ msgstr "Message display settings are only available on desktop." msgid "Message editing" msgstr "Message editing" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Message forwarded" @@ -20747,7 +20815,7 @@ msgstr "Message input buttons" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Message is too long" @@ -21046,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Miscellaneous" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Missing client_id" @@ -21863,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Name must be at least 2 characters" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Name passkey" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Name passkey form" @@ -22382,7 +22450,7 @@ msgstr "No changes to save" msgid "No channels available" msgstr "No channels available" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "No channels found" @@ -22825,7 +22893,7 @@ msgstr "No users found" msgid "No verification is required." msgstr "No verification is required." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "No verification methods available" @@ -23418,6 +23486,16 @@ msgstr "Open {contextName}" msgid "Open {displayName}" msgstr "Open {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Open {host} in Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Open {host} in your browser." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23438,6 +23516,11 @@ msgstr "Open {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Open {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Open {productName} in your browser" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23511,7 +23594,7 @@ msgstr "Open banner options" msgid "Open bookmarks" msgstr "Open bookmarks" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Open browser" @@ -23615,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Open emoji in browser" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Open emoji picker" @@ -23810,6 +23893,16 @@ msgstr "Open the link below in your browser, then copy the entire JSON response msgid "Open the menu and choose an option." msgstr "Open the menu and choose an option." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Open the new app. You're already signed in." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23860,7 +23953,7 @@ msgstr "Open voice" msgid "Open voice & video settings" msgstr "Open voice & video settings" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Open your browser, sign in, then enter the code below to link your account." @@ -23869,6 +23962,11 @@ msgstr "Open your browser, sign in, then enter the code below to link your accou msgid "Open your messages" msgstr "Open your messages" +# auto-i18n: reviewed unchanged +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Open your phone's messaging app and create a new text message." @@ -24223,7 +24321,7 @@ msgid "Passkey" msgstr "Passkey" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Passkey name" @@ -28139,7 +28237,7 @@ msgstr "Saturation" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28546,7 +28644,7 @@ msgstr "Scrub preview" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29075,8 +29173,8 @@ msgid "Select text" msgstr "Select text" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Select the account you want to sign in with on the desktop app." +msgid "Select the account you want to sign in with on your new device." +msgstr "Select the account you want to sign in with on your new device." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29183,7 +29281,7 @@ msgid "Send" msgstr "Send" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Send ({selectedCount}/{selectionLimit})" @@ -29488,7 +29586,7 @@ msgid "Session" msgstr "Session" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Session expired for {identifier}. Sign in again." @@ -29971,7 +30069,7 @@ msgid "Shortcuts are paused while recording." msgstr "Shortcuts are paused while recording." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Shorten the message and try again." @@ -30218,6 +30316,11 @@ msgstr "Show max 3 (+5 badge)" msgid "Show max 5 (+5 badge)" msgstr "Show max 5 (+5 badge)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Show me how" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30489,7 +30592,7 @@ msgstr "Sign in to claim your gift" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Sign in via browser" @@ -30497,6 +30600,12 @@ msgstr "Sign in via browser" msgid "Sign in with a passkey" msgstr "Sign in with a passkey" +# auto-i18n: reviewed unchanged +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Sign in with your old {productName} app" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30563,7 +30672,7 @@ msgid "Sign-in details" msgstr "Sign-in details" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Sign-in flow" @@ -31031,7 +31140,7 @@ msgstr "Some users can't be added" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32689,6 +32798,11 @@ msgstr "Talk in this voice channel. Without this permission, members stay muted msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Tap Share, then Add to Home Screen." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33208,12 +33322,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "The phone check is set aside. One email step is left." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "The provided redirect_uri is not registered for this application." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "The provided redirect_uri must use HTTP or HTTPS." @@ -33446,7 +33560,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "This animation is too long or too large to crop. Try a shorter or smaller file." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "This application" @@ -33456,7 +33570,7 @@ msgid "This attachment has expired" msgstr "This attachment has expired" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "This bot is not public and cannot be added by other users." @@ -33758,7 +33872,7 @@ msgstr "This group has too many people" msgid "This group is full. Remove someone before adding more people." msgstr "This group is full. Remove someone before adding more people." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "This handoff code has expired. Generate a new one to continue." @@ -35481,7 +35595,7 @@ msgid "Unknown" msgstr "Unknown" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Unknown application, expired session, or invalid authorisation request." @@ -36863,27 +36977,27 @@ msgid "Uses:" msgstr "Uses:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Using actual account state" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Using actual gift inventory state" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Using actual premium state" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Using actual state" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Using actual verification state" @@ -37948,17 +38062,17 @@ msgstr "Wait here" msgid "Waiting for connection..." msgstr "Waiting for connection..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Waiting for passkey…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Waiting for slowmode in {forwardChannelDisplayName} to expire." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Waiting for slowmode in one or more selected channels to expire." @@ -38157,7 +38271,7 @@ msgstr "We resized and compressed this sticker to 320x320 pixels, but it is stil msgid "We sent a code to {email}." msgstr "We sent a code to {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "We will prefill {prefillEmail} once browser sign-in opens." @@ -38357,7 +38471,7 @@ msgid "Website previews" msgstr "Website previews" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Welcome back" @@ -39700,7 +39814,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Your {premiumProductName} subscription has expired. You have lost all {premiumProductName} perks. Reactivate your subscription to regain access." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Your {productName} session expired. Sign in again to continue authorising this application." @@ -39735,7 +39849,7 @@ msgstr "Your account is too new to send messages in this community." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Your account request is pending approval. You can sign in after an admin approves it." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." @@ -39758,6 +39872,11 @@ msgstr "Your action could not be completed because it was flagged by our safety msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Your application is pending review. You can still update your listing details or withdraw the application." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Your browser doesn't support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39776,11 +39895,11 @@ msgstr "Your card is eligible. Redirecting you to the payment page now." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Your code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Your code is ready" diff --git a/fluxer_app/src/features/i18n/locales/en-US/messages.po b/fluxer_app/src/features/i18n/locales/en-US/messages.po index c8e7472c9..dec7ad965 100644 --- a/fluxer_app/src/features/i18n/locales/en-US/messages.po +++ b/fluxer_app/src/features/i18n/locales/en-US/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# blocked message} other {# blocked messages}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community found} other {# communities found}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} detected a potential raid in this community. Invites are p msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} detected a potential raid, so new users can't join right now." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} has moved to {host}. Install it from your browser, then sign in with this app." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} has moved to {host}. Install the new app and you will already be signed in." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} has moved to {host}. Open it in your browser to install the new app." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} needs microphone access to list your devices." msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} now lives at {host}. Install it from there, then sign in with this app." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} now lives at {host}. Install it from there." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "a poll" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "A redirect_uri is required when the bot scope is not the only scope." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Add a bot to a community with requested permissions" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Add a comment (optional)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Add a YouTube Data API key to enrich YouTube links." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Add account" @@ -4379,6 +4409,11 @@ msgstr "Approval required" msgid "Approve" msgstr "Approve" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Approve this app from the {productName} app you already use. No password needed." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Authorization failed" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Authorization failed. Try again." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Bot permissions" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Bot permissions cannot be negative." @@ -6329,10 +6364,10 @@ msgstr "Can't upload files here" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Can't upload files here" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Choose background" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Choose either guild_id or channel_id, not both." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Choose File, then Add to Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "code block" msgid "Code blocks" msgstr "Code blocks" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Code expired" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Commands" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Comments are unavailable while slowmode is on." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Community message notifications" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Community MFA requirement nagbar" @@ -9182,7 +9222,7 @@ msgstr "Continue to report community" msgid "Continue to report user" msgstr "Continue to report user" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Continue with passkey" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coordinates raids, brigading, or harassment against people or communities." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Copied" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Copy channel link" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Copy code" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Current uses:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Currently dismissed" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Currently showing" @@ -12555,6 +12595,11 @@ msgstr "Doesn't block. You can reopen later." msgid "Domain" msgstr "Domain" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domain moved nagbar" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Dutch" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "e.g., YubiKey, iPhone, work computer" @@ -14056,10 +14101,14 @@ msgstr "Enter the PIN for your security key to continue." msgid "Enter the SMS code" msgstr "Enter the SMS code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Enter this code in your browser to complete sign-in." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter to <0><1>save" @@ -14441,7 +14490,7 @@ msgstr "Expires {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Expires between {earliest} and {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Expires in {remaining}s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Failed to start SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Failed to switch account" @@ -15413,17 +15462,17 @@ msgstr "For me" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "For this check, you send us a text message instead of receiving one from us." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "For your security, please confirm it's you to continue." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Force disabled" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Force enabled" @@ -15540,7 +15589,7 @@ msgstr "Forgot password" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Forgot your password?" @@ -15571,7 +15620,7 @@ msgstr "Forward media" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Forward message" @@ -15965,11 +16014,11 @@ msgstr "Generate a shareable link or copy your combined custom CSS." msgid "Generate invite link" msgstr "Generate invite link" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generate new code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generating code…" @@ -16045,6 +16094,11 @@ msgstr "Get the {productName} desktop app for system-wide push-to-talk and a few msgid "Get the desktop app" msgstr "Get the desktop app" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Get the new {productName} app" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Insert emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Install {productName} for the full source picker and smoother screen-sharing controls." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Install it from your browser menu." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Install the new app" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Invalid authorization request" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Invalid bot permissions." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Invalid or expired code. Try again." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Invalid redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (mature)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Link a new device" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Message display settings are only available on desktop." msgid "Message editing" msgstr "Message editing" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Message forwarded" @@ -20746,7 +20815,7 @@ msgstr "Message input buttons" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Message is too long" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Miscellaneous" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Missing client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Name must be at least 2 characters" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Name passkey" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Name passkey form" @@ -22381,7 +22450,7 @@ msgstr "No changes to save" msgid "No channels available" msgstr "No channels available" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "No channels found" @@ -22824,7 +22893,7 @@ msgstr "No users found" msgid "No verification is required." msgstr "No verification is required." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "No verification methods available" @@ -23417,6 +23486,16 @@ msgstr "Open {contextName}" msgid "Open {displayName}" msgstr "Open {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Open {host} in Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Open {host} in your browser." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Open {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Open {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Open {productName} in your browser" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Open banner options" msgid "Open bookmarks" msgstr "Open bookmarks" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Open browser" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Open emoji in browser" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Open emoji picker" @@ -23809,6 +23893,16 @@ msgstr "Open the link below in your browser, then copy the entire JSON response msgid "Open the menu and choose an option." msgstr "Open the menu and choose an option." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Open the new app. You are already signed in." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Open voice" msgid "Open voice & video settings" msgstr "Open voice & video settings" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Open your browser, sign in, then enter the code below to link your account." @@ -23868,6 +23962,10 @@ msgstr "Open your browser, sign in, then enter the code below to link your accou msgid "Open your messages" msgstr "Open your messages" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Open your phone's messaging app and create a new text message." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Passkey" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Passkey name" @@ -28138,7 +28236,7 @@ msgstr "Saturation" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Scrub preview" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Select text" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Select the account you want to sign in with on the desktop app." +msgid "Select the account you want to sign in with on your new device." +msgstr "Select the account you want to sign in with on your new device." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Send" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Send ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Session" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Session expired for {identifier}. Sign in again." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Shortcuts are paused while recording." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Shorten the message and try again." @@ -30217,6 +30315,11 @@ msgstr "Show max 3 (+5 badge)" msgid "Show max 5 (+5 badge)" msgstr "Show max 5 (+5 badge)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Show me how" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Sign in to claim your gift" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Sign in via browser" @@ -30496,6 +30599,11 @@ msgstr "Sign in via browser" msgid "Sign in with a passkey" msgstr "Sign in with a passkey" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Sign in with your old {productName} app" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Sign-in details" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Sign-in flow" @@ -31030,7 +31138,7 @@ msgstr "Some users can't be added" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Talk in this voice channel. Without this permission, members stay muted msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Tap Share, then Add to Home Screen." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "The phone check is set aside. One email step is left." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "The provided redirect_uri is not registered for this application." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "The provided redirect_uri must use HTTP or HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "This animation is too long or too large to crop. Try a shorter or smaller file." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "This application" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "This attachment has expired" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "This bot is not public and cannot be added by other users." @@ -33757,7 +33870,7 @@ msgstr "This group has too many people" msgid "This group is full. Remove someone before adding more people." msgstr "This group is full. Remove someone before adding more people." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "This handoff code has expired. Generate a new one to continue." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Unknown" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Unknown application, expired session, or invalid authorization request." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Uses:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Using actual account state" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Using actual gift inventory state" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Using actual premium state" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Using actual state" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Using actual verification state" @@ -37947,17 +38060,17 @@ msgstr "Wait here" msgid "Waiting for connection..." msgstr "Waiting for connection..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Waiting for passkey…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Waiting for slowmode in {forwardChannelDisplayName} to expire." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Waiting for slowmode in one or more selected channels to expire." @@ -38156,7 +38269,7 @@ msgstr "We resized and compressed this sticker to 320x320 pixels, but it is stil msgid "We sent a code to {email}." msgstr "We sent a code to {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "We will prefill {prefillEmail} once browser sign-in opens." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Website previews" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Welcome back" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Your {premiumProductName} subscription has expired. You have lost all {premiumProductName} perks. Reactivate your subscription to regain access." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Your {productName} session expired. Sign in again to continue authorizing this application." @@ -39734,7 +39847,7 @@ msgstr "Your account is too new to send messages in this community." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Your account request is pending approval. You can sign in after an admin approves it." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." @@ -39757,6 +39870,11 @@ msgstr "Your action could not be completed because it was flagged by our safety msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Your application is pending review. You can still update your listing details or withdraw the application." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Your card is eligible. Redirecting you to the payment page now." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Your code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Your code is ready" diff --git a/fluxer_app/src/features/i18n/locales/es-419/messages.po b/fluxer_app/src/features/i18n/locales/es-419/messages.po index 9a5de6e23..ee8153b06 100644 --- a/fluxer_app/src/features/i18n/locales/es-419/messages.po +++ b/fluxer_app/src/features/i18n/locales/es-419/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# mensaje bloqueado} other {# mensajes bloqueados}} msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# comunidad encontrada} other {# comunidades encontradas}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# comunidad no está disponible temporalmente debido a una falla en el condensador de flujo.} other {# comunidades no están disponibles temporalmente debido a una falla en el condensador de flujo.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} detectó un posible ataque en esta comunidad. Las invitaci msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} detectó un posible ataque, por lo que los usuarios nuevos no pueden unirse en este momento." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} se mudó a {host}. Agrégalo a tu pantalla de inicio o Dock, luego inicia sesión con esta app." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} se mudó a {host}. Instálalo desde tu navegador y luego inicia sesión con esta app." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} se mudó a {host}. Instala la nueva app y ya habrás iniciado sesión." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} se mudó a {host}. Ábrelo en tu navegador para instalar la nueva app." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} necesita acceso al micrófono para mostrar tus dispositivo msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} necesita acceso a la grabación de pantalla. Permite la grabación de pantalla en la configuración de privacidad de tu sistema operativo y reinicia la aplicación." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} ahora está en {host}. Instálalo desde allí y luego inicia sesión con esta app." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} ahora vive en {host}. Instálalo desde allí." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "una encuesta" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Se requiere redirect_uri cuando \"bot\" no es el único ámbito." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Agregar un bot a una comunidad con los permisos solicitados" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Agregar un comentario (opcional)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Agrega una clave de YouTube Data API para enriquecer los enlaces de YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Agregar cuenta" @@ -4379,6 +4409,11 @@ msgstr "Se requiere aprobación" msgid "Approve" msgstr "Aprobar" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Aprueba esta app desde la app {productName} que ya usas. No necesitas contraseña." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Falló la autorización" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Falló la autorización. Intenta de nuevo." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Permisos del bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Los permisos del bot no pueden ser negativos." @@ -6329,10 +6364,10 @@ msgstr "No se pueden subir archivos aquí" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "No se pueden subir archivos aquí" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Elige un fondo" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Elige guild_id o channel_id, no ambos." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Selecciona Archivo, luego Agregar al Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "bloque de código" msgid "Code blocks" msgstr "Bloques de código" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "El código expiró" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Comandos" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Los comentarios están desactivados porque uno o más canales seleccionados tienen el modo lento activado. El comentario sería bloqueado por el modo lento justo después del reenvío." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Los comentarios están deshabilitados porque el modo lento está activado en {forwardChannelDisplayName}. El comentario se bloquearía por el modo lento justo después del reenvío." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Los comentarios no están disponibles mientras el modo lento esté activado." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Notificaciones de mensajes de la comunidad" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Barra de notificación de requisito de MFA de la comunidad" @@ -9182,7 +9222,7 @@ msgstr "Continuar para reportar a la comunidad" msgid "Continue to report user" msgstr "Continuar para reportar al usuario" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Continuar con llave de acceso" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coordina ataques, acoso o intimidación contra personas o comunidades." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Copiado" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Copiar enlace del canal" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Copiar código" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Usos actuales:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Actualmente descartado" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Mostrando actualmente" @@ -12555,6 +12595,11 @@ msgstr "No bloquea. Puedes reabrirlo más tarde." msgid "Domain" msgstr "Dominio" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "La dominio se movió" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Neerlandés" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "ej., YubiKey, iPhone, computadora del trabajo" @@ -14056,10 +14101,14 @@ msgstr "Ingresa el PIN de tu llave de seguridad para continuar." msgid "Enter the SMS code" msgstr "Ingresa el código SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Ingresa este código en tu navegador para completar el inicio de sesión." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Ingresa este código en tu antigua app de {PRODUCT_NAME} para completar el inicio de sesión." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Intro para <0><1>guardar" @@ -14441,7 +14490,7 @@ msgstr "Vence el {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Vence entre {earliest} y {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Expira en {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "No se pudo iniciar la autenticación con SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "No se pudo cambiar de cuenta" @@ -15413,17 +15462,17 @@ msgstr "Para mí" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Para esta verificación, nos envías un mensaje de texto en lugar de recibir uno nuestro." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Para tu seguridad, confirma que eres tú para continuar." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Forzar desactivación" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Forzar activación" @@ -15540,7 +15589,7 @@ msgstr "¿Olvidaste tu contraseña?" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "¿Olvidaste tu contraseña?" @@ -15571,7 +15620,7 @@ msgstr "Reenviar archivo multimedia" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Reenviar mensaje" @@ -15965,11 +16014,11 @@ msgstr "Genera un enlace para compartir o copia tu CSS personalizado combinado." msgid "Generate invite link" msgstr "Generar enlace de invitación" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generar código nuevo" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generando código…" @@ -16045,6 +16094,11 @@ msgstr "Obtén la aplicación de escritorio de {productName} para usar la funci msgid "Get the desktop app" msgstr "Obtén la aplicación de escritorio" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Descarga la nueva app {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Insertar emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Instala {productName} para tener el selector de fuentes completo y controles de pantalla compartida más fluidos." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Instálalo desde el menú de tu navegador." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Instalar la nueva app" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Solicitud de autorización no válida" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Permisos de bot no válidos." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Código no válido o caducado. Intenta de nuevo." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Valor de redirect_uri no válido" @@ -19332,6 +19396,11 @@ msgstr "Enlace" msgid "Link (mature)" msgstr "Enlace (contenido para adultos)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Vincular un dispositivo nuevo" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "La configuración de visualización de mensajes solo está disponible en msgid "Message editing" msgstr "Edición de mensajes" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Mensaje reenviado" @@ -20746,7 +20815,7 @@ msgstr "Botones de entrada de mensajes" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "El mensaje es demasiado largo" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Varios" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Falta client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "El nombre debe tener al menos 2 caracteres" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Nombrar llave de acceso" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formulario para nombrar la llave de acceso" @@ -22381,7 +22450,7 @@ msgstr "No hay cambios para guardar" msgid "No channels available" msgstr "No hay canales disponibles" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "No se encontraron canales" @@ -22824,7 +22893,7 @@ msgstr "No se encontraron usuarios" msgid "No verification is required." msgstr "No se requiere verificación." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "No hay métodos de verificación disponibles" @@ -23417,6 +23486,16 @@ msgstr "Abrir {contextName}" msgid "Open {displayName}" msgstr "Abrir {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Abre {host} en Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Abre {host} en tu navegador." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Abre {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Abrir {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Abrir {productName} en tu navegador" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Abrir opciones del banner" msgid "Open bookmarks" msgstr "Abrir mensajes guardados" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Abrir navegador" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Abrir emoji en el navegador" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Abrir selector de emojis" @@ -23809,6 +23893,16 @@ msgstr "Abre el siguiente enlace en tu navegador, luego copia la respuesta JSON msgid "Open the menu and choose an option." msgstr "Abre el menú y elige una opción." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Abre la nueva app y elige Iniciar sesión con tu antigua app de {productName}. Luego, elige Vincular un dispositivo nuevo aquí e introduce el código que se muestre." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Abre la nueva app. Ya iniciaste sesión." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Abrir chat de voz" msgid "Open voice & video settings" msgstr "Abrir configuración de audio y video" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Abre tu navegador, inicia sesión y luego ingresa el código a continuación para vincular tu cuenta." @@ -23868,6 +23962,10 @@ msgstr "Abre tu navegador, inicia sesión y luego ingresa el código a continuac msgid "Open your messages" msgstr "Abre tus mensajes" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Abre tu app de {PRODUCT_NAME} anterior y selecciona \"Vincular un dispositivo nuevo\", luego ingresa el código a continuación." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Abre la app de mensajes de tu teléfono y crea un mensaje de texto nuevo." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Llave de acceso" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Nombre de la llave de acceso" @@ -28138,7 +28236,7 @@ msgstr "Saturación" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Vista previa al recorrer el video" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Seleccionar texto" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Selecciona la cuenta con la que quieres iniciar sesión en la aplicación de escritorio." +msgid "Select the account you want to sign in with on your new device." +msgstr "Selecciona la cuenta con la que quieres iniciar sesión en tu nuevo dispositivo." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Enviar" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Enviar ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sesión" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "La sesión de {identifier} caducó. Inicia sesión de nuevo." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Los atajos se pausan durante la grabación." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Acorta el mensaje e inténtalo de nuevo." @@ -30217,6 +30315,11 @@ msgstr "Mostrar un máximo de 3 (insignia +5)" msgid "Show max 5 (+5 badge)" msgstr "Mostrar un máximo de 5 (insignia +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Muéstrame cómo" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Inicia sesión para reclamar tu regalo" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Iniciar sesión con el navegador" @@ -30496,6 +30599,11 @@ msgstr "Iniciar sesión con el navegador" msgid "Sign in with a passkey" msgstr "Iniciar sesión con una llave de acceso" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Inicia sesión con tu antigua app {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Detalles de inicio de sesión" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Flujo de inicio de sesión" @@ -31030,7 +31138,7 @@ msgstr "No se pueden agregar algunos usuarios" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Hablar en este canal de voz. Sin este permiso, los miembros permanecerá msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Hablar en canales de voz. Sin este permiso, los miembros permanecerán silenciados hasta que alguien con el permiso \"Silenciar miembros\" les active el micrófono." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Toca Compartir y luego Agregar a pantalla de inicio." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "La verificación del teléfono ha quedado pendiente. Falta un paso por correo electrónico." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "La redirect_uri proporcionada no está registrada para esta aplicación." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "La redirect_uri proporcionada debe usar HTTP o HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Esta animación es demasiado larga o grande para recortarla. Intenta con un archivo más corto o pequeño." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Esta aplicación" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Este archivo adjunto ha caducado" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Este bot no es público y no puede ser añadido por otros usuarios." @@ -33757,7 +33870,7 @@ msgstr "Este grupo tiene demasiadas personas" msgid "This group is full. Remove someone before adding more people." msgstr "Este grupo está lleno. Elimina a alguien antes de agregar a más personas." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Este código de transferencia ha expirado. Genera uno nuevo para continuar." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Desconocido" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplicación desconocida, sesión caducada o solicitud de autorización no válida." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Usos:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Usando el estado real de la cuenta" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Usando el estado real del inventario de regalos" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Usando el estado premium real" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Usando el estado real" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Usando el estado de verificación real" @@ -37947,17 +38060,17 @@ msgstr "Espera aquí" msgid "Waiting for connection..." msgstr "Esperando conexión..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Esperando la llave de acceso…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Esperando que el modo lento en {forwardChannelDisplayName} expire." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Esperando que el modo lento en uno o más canales seleccionados expire." @@ -38156,7 +38269,7 @@ msgstr "Redimensionamos y comprimimos este sticker a 320x320 píxeles, pero sigu msgid "We sent a code to {email}." msgstr "Enviamos un código a {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Vamos a rellenar automáticamente {prefillEmail} cuando se abra el inicio de sesión en el navegador." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Vistas previas de sitios web" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Te damos la bienvenida de nuevo" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Tu suscripción a {premiumProductName} ha caducado. Has perdido todos los beneficios de {premiumProductName}. Reactiva tu suscripción para recuperar el acceso." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Tu sesión de {productName} caducó. Inicia sesión de nuevo para seguir autorizando esta aplicación." @@ -39734,7 +39847,7 @@ msgstr "Tu cuenta es demasiado nueva para enviar mensajes en esta comunidad." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Tu solicitud de cuenta está pendiente de aprobación. Podrás iniciar sesión cuando un administrador la apruebe." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Tu cuenta requiere autenticación multifactor, pero no hay métodos compatibles configurados en este dispositivo. Inicia sesión en un dispositivo con tu aplicación de autenticación o llave de seguridad, o contacta a soporte." @@ -39757,6 +39870,11 @@ msgstr "No pudimos completar tu acción porque fue marcada por nuestros sistemas msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Tu solicitud está pendiente de revisión. Aún puedes actualizar los datos de tu comunidad o retirar la solicitud." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Tu navegador no admite claves de acceso en este dominio. Actualiza tu navegador o inicia sesión con tu contraseña y código de dos factores." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Tu tarjeta cumple los requisitos. Te estamos redirigiendo a la página d msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Tu operador dice que este número no está en servicio. Verifícalo e inténtalo de nuevo, o contacta a soporte si es correcto." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Tu código" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Tu código está listo" diff --git a/fluxer_app/src/features/i18n/locales/es-ES/messages.po b/fluxer_app/src/features/i18n/locales/es-ES/messages.po index ae6761b11..e65bcbeba 100644 --- a/fluxer_app/src/features/i18n/locales/es-ES/messages.po +++ b/fluxer_app/src/features/i18n/locales/es-ES/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# mensaje bloqueado} other {# mensajes bloqueados}} msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# comunidad encontrada} other {# comunidades encontradas}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# comunidad no está disponible temporalmente debido a un fallo en el condensador de flujo.} other {# comunidades no están disponibles temporalmente debido a un fallo en el condensador de flujo.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} ha detectado un posible ataque en esta comunidad. Las invi msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} ha detectado un posible ataque, por lo que los nuevos usuarios no pueden unirse ahora mismo." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} se ha mudado a {host}. Añádelo a tu pantalla de inicio o Dock, y luego inicia sesión con esta app." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} se ha mudado a {host}. Instálalo desde tu navegador y luego inicia sesión con esta aplicación." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} se ha mudado a {host}. Instala la nueva app y ya habrás iniciado sesión." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} se ha mudado a {host}. Ábrelo en tu navegador para instalar la nueva app." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} necesita acceso al micrófono para mostrar tus dispositivo msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} necesita acceso a la grabación de pantalla. Permite la grabación de pantalla en los ajustes de privacidad de tu sistema operativo y reinicia la aplicación." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} ahora está en {host}. Instálalo desde allí e inicia sesión con esta aplicación." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} ahora está en {host}. Instálalo desde allí." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "una encuesta" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Se requiere redirect_uri cuando \"bot\" no es el único ámbito." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Añadir un bot a una comunidad con los permisos solicitados" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Añadir un comentario (opcional)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Añade una clave de YouTube Data API para enriquecer los enlaces de YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Añadir cuenta" @@ -4379,6 +4409,11 @@ msgstr "Se requiere aprobación" msgid "Approve" msgstr "Aprobar" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Aprueba esta app desde la app {productName} que ya usas. No necesitas contraseña." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Error de autorización" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Error de autorización. Inténtalo de nuevo." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Permisos del bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Los permisos del bot no pueden ser negativos." @@ -6329,10 +6364,10 @@ msgstr "No se pueden subir archivos aquí" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "No se pueden subir archivos aquí" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Elige un fondo" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Elige guild_id o channel_id, no ambos." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Selecciona Archivo y luego Añadir a Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "bloque de código" msgid "Code blocks" msgstr "Bloques de código" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "El código ha caducado" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Comandos" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Los comentarios están desactivados porque uno o varios de los canales seleccionados tienen el modo lento activado. El comentario se bloquearía por el modo lento justo después del reenvío." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Los comentarios están deshabilitados porque el modo lento está activado en {forwardChannelDisplayName}. El comentario se bloquearía por el modo lento justo después del reenvío." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Los comentarios no están disponibles mientras el modo lento esté activado." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Notificaciones de mensajes de la comunidad" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Barra de notificación de requisito de MFA de la comunidad" @@ -9182,7 +9222,7 @@ msgstr "Continuar para denunciar a la comunidad" msgid "Continue to report user" msgstr "Continuar para denunciar al usuario" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Continuar con clave de acceso" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coordina ataques, acoso organizado o acoso contra personas o comunidades." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Copiado" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Copiar enlace del canal" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Copiar código" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Usos actuales:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Actualmente descartado" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Mostrando actualmente" @@ -12555,6 +12595,11 @@ msgstr "No bloquea. Puedes volver a abrirlo más tarde." msgid "Domain" msgstr "Dominio" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "La dominio se ha movido" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Neerlandés" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "p. ej., YubiKey, iPhone, ordenador del trabajo" @@ -14056,10 +14101,14 @@ msgstr "Introduce el PIN de tu llave de seguridad para continuar." msgid "Enter the SMS code" msgstr "Introduce el código SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Introduce este código en tu navegador para completar el inicio de sesión." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Introduce este código en tu antigua aplicación {PRODUCT_NAME} para completar el inicio de sesión." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Intro para <0><1>guardar" @@ -14441,7 +14490,7 @@ msgstr "Caduca el {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Caduca entre {earliest} y {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Caduca en {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "No se pudo iniciar la autenticación con SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "No se pudo cambiar de cuenta" @@ -15413,17 +15462,17 @@ msgstr "Para mí" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Para esta verificación, nos envías un mensaje de texto en lugar de recibir uno nuestro." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Para tu seguridad, confirma que eres tú para continuar." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Forzar desactivación" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Forzar activación" @@ -15540,7 +15589,7 @@ msgstr "¿Has olvidado la contraseña?" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "¿Has olvidado tu contraseña?" @@ -15571,7 +15620,7 @@ msgstr "Reenviar contenido multimedia" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Reenviar mensaje" @@ -15965,11 +16014,11 @@ msgstr "Genera un enlace para compartir o copia tu CSS personalizado combinado." msgid "Generate invite link" msgstr "Generar enlace de invitación" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generar nuevo código" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generando código…" @@ -16045,6 +16094,11 @@ msgstr "Consigue la aplicación de escritorio de {productName} para usar la func msgid "Get the desktop app" msgstr "Consigue la aplicación de escritorio" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Descarga la nueva app {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Insertar emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Instala {productName} para tener el selector de fuentes completo y controles de pantalla compartida más fluidos." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Instálalo desde el menú de tu navegador." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Instalar la nueva app" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Solicitud de autorización no válida" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Permisos de bot no válidos." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Código no válido o caducado. Inténtalo de nuevo." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Valor de redirect_uri no válido" @@ -19332,6 +19396,11 @@ msgstr "Enlace" msgid "Link (mature)" msgstr "Enlace (contenido para adultos)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Vincular un dispositivo nuevo" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Los ajustes de visualización de mensajes solo están disponibles en la msgid "Message editing" msgstr "Edición de mensajes" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Mensaje reenviado" @@ -20746,7 +20815,7 @@ msgstr "Botones de entrada de mensajes" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "El mensaje es demasiado largo" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Varios" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Falta client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "El nombre debe tener al menos 2 caracteres" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Nombrar clave de acceso" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formulario para nombrar la clave de acceso" @@ -22381,7 +22450,7 @@ msgstr "No hay cambios que guardar" msgid "No channels available" msgstr "No hay canales disponibles" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "No se encontraron canales" @@ -22824,7 +22893,7 @@ msgstr "No se encontraron usuarios" msgid "No verification is required." msgstr "No se requiere verificación." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "No hay métodos de verificación disponibles" @@ -23417,6 +23486,16 @@ msgstr "Abrir {contextName}" msgid "Open {displayName}" msgstr "Abrir {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Abre {host} en Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Abre {host} en tu navegador." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Abre {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Abrir {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Abrir {productName} en tu navegador" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Abrir opciones del banner" msgid "Open bookmarks" msgstr "Abrir mensajes guardados" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Abrir navegador" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Abrir emoji en el navegador" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Abrir selector de emojis" @@ -23809,6 +23893,16 @@ msgstr "Abre el siguiente enlace en tu navegador, luego copia la respuesta JSON msgid "Open the menu and choose an option." msgstr "Abre el menú y elige una opción." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Abre la nueva aplicación y selecciona Iniciar sesión con tu antigua aplicación de {productName}. Luego, selecciona Vincular un dispositivo nuevo aquí e introduce el código que aparezca." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Abre la nueva app. Ya has iniciado sesión." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Abrir chat de voz" msgid "Open voice & video settings" msgstr "Abrir ajustes de audio y vídeo" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Abre tu navegador, inicia sesión y, a continuación, introduce el código de abajo para vincular tu cuenta." @@ -23868,6 +23962,10 @@ msgstr "Abre tu navegador, inicia sesión y, a continuación, introduce el códi msgid "Open your messages" msgstr "Abre tus mensajes" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Abre tu antigua app de {PRODUCT_NAME} y selecciona \"Vincular un dispositivo nuevo\", luego introduce el código de abajo." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Abre la aplicación de mensajería de tu teléfono y crea un mensaje de texto nuevo." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Clave de acceso" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Nombre de la clave de acceso" @@ -28138,7 +28236,7 @@ msgstr "Saturación" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Vista previa al recorrer el vídeo" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Seleccionar texto" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Selecciona la cuenta con la que quieres iniciar sesión en la aplicación de escritorio." +msgid "Select the account you want to sign in with on your new device." +msgstr "Selecciona la cuenta con la que quieres iniciar sesión en tu nuevo dispositivo." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Enviar" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Enviar ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sesión" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "La sesión de {identifier} ha caducado. Inicia sesión de nuevo." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Los atajos se pausan durante la grabación." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Acorta el mensaje y vuelve a intentarlo." @@ -30217,6 +30315,11 @@ msgstr "Mostrar un máximo de 3 (insignia +5)" msgid "Show max 5 (+5 badge)" msgstr "Mostrar un máximo de 5 (insignia +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Muéstrame cómo" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Inicia sesión para reclamar tu regalo" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Iniciar sesión a través del navegador" @@ -30496,6 +30599,11 @@ msgstr "Iniciar sesión a través del navegador" msgid "Sign in with a passkey" msgstr "Iniciar sesión con una clave de acceso" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Inicia sesión con tu antigua app de {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Detalles de inicio de sesión" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Flujo de inicio de sesión" @@ -31030,7 +31138,7 @@ msgstr "No se pueden añadir algunos usuarios" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Hablar en este canal de voz. Sin este permiso, los miembros permanecerá msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Hablar en canales de voz. Sin este permiso, los miembros permanecerán silenciados hasta que alguien con el permiso \"Silenciar miembros\" les active el micrófono." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Toca Compartir y, a continuación, Añadir a pantalla de inicio." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "La verificación del teléfono ha quedado pendiente. Falta un paso por correo electrónico." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "La redirect_uri proporcionada no está registrada para esta aplicación." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "La redirect_uri proporcionada debe usar HTTP o HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Esta animación es demasiado larga o grande para recortarla. Prueba con un archivo más corto o pequeño." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Esta aplicación" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Este archivo adjunto ha caducado" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Este bot no es público y no puede ser añadido por otros usuarios." @@ -33757,7 +33870,7 @@ msgstr "Este grupo tiene demasiada gente" msgid "This group is full. Remove someone before adding more people." msgstr "Este grupo está lleno. Elimina a alguien antes de añadir a más personas." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Este código de transferencia ha caducado. Genera uno nuevo para continuar." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Desconocido" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplicación desconocida, sesión caducada o solicitud de autorización no válida." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Usos:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Usando el estado real de la cuenta" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Usando el estado real del inventario de regalos" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Usando el estado prémium real" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Usando el estado real" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Usando el estado de verificación real" @@ -37947,17 +38060,17 @@ msgstr "Espera aquí" msgid "Waiting for connection..." msgstr "Esperando conexión..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Esperando la clave de acceso…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Esperando a que el modo lento en {forwardChannelDisplayName} expire." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Esperando a que el modo lento caduque en uno o más canales seleccionados." @@ -38156,7 +38269,7 @@ msgstr "Hemos redimensionado y comprimido esta pegatina a 320x320 píxeles, pero msgid "We sent a code to {email}." msgstr "Hemos enviado un código a {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Rellenaremos automáticamente {prefillEmail} cuando se abra el inicio de sesión en el navegador." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Vistas previas de sitios web" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Te damos la bienvenida de nuevo" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Tu suscripción a {premiumProductName} ha caducado. Has perdido todas las ventajas de {premiumProductName}. Reactiva tu suscripción para recuperar el acceso." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Tu sesión de {productName} ha caducado. Inicia sesión de nuevo para seguir autorizando esta aplicación." @@ -39734,7 +39847,7 @@ msgstr "Tu cuenta es demasiado nueva para enviar mensajes en esta comunidad." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Tu solicitud de cuenta está pendiente de aprobación. Podrás iniciar sesión cuando un administrador la apruebe." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Tu cuenta requiere autenticación multifactor, pero no hay métodos compatibles configurados en este dispositivo. Inicia sesión en un dispositivo con tu aplicación de autenticación o llave de seguridad, o contacta con soporte." @@ -39757,6 +39870,11 @@ msgstr "No se ha podido completar tu acción porque ha sido marcada por nuestros msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Tu solicitud está pendiente de revisión. Aún puedes actualizar los datos de tu comunidad o retirar la solicitud." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Tu navegador no admite claves de acceso en este dominio. Actualiza tu navegador o inicia sesión con tu contraseña y código de dos factores." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Tu tarjeta cumple los requisitos. Te estamos redirigiendo a la página d msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Tu operador indica que este número no está en servicio. Compruébalo y vuelve a intentarlo, o ponte en contacto con soporte si es correcto." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Tu código" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Tu código está listo" diff --git a/fluxer_app/src/features/i18n/locales/fi/messages.po b/fluxer_app/src/features/i18n/locales/fi/messages.po index 34e0b48b5..876a740cf 100644 --- a/fluxer_app/src/features/i18n/locales/fi/messages.po +++ b/fluxer_app/src/features/i18n/locales/fi/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# estetty viesti} other {# estettyä viestiä}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# yhteisö löytyi} other {# yhteisöä löytyi}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# yhteisö on tilapäisesti pois käytöstä toimintahäiriön vuoksi.} other {# yhteisöä on tilapäisesti pois käytöstä toimintahäiriön vuoksi.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} havaitsi mahdollisen hyökkäyksen tässä yhteisössä. K msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} havaitsi mahdollisen hyökkäyksen, joten uudet käyttäjät eivät voi liittyä juuri nyt." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} on siirtynyt osoitteeseen {host}. Lisää se Koti-valikkoon tai Dockiin ja kirjaudu sitten sisään tällä sovelluksella." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} on siirtynyt osoitteeseen {host}. Asenna se selaimeesi ja kirjaudu sitten sisään tällä sovelluksella." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} on siirretty osoitteeseen {host}. Asenna uusi sovellus, niin olet jo kirjautuneena sisään." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} on siirtynyt osoitteeseen {host}. Avaa se selaimessasi asentaaksesi uuden sovelluksen." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} tarvitsee mikrofonin käyttöoikeuden, jotta se voi näytt msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} tarvitsee pääsyn näytön tallennukseen. Salli näytön tallennus käyttöjärjestelmäsi tietosuoja-asetuksissa ja käynnistä sovellus uudelleen." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} asuu nyt osoitteessa {host}. Asenna se sieltä ja kirjaudu sitten sisään tällä sovelluksella." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} asuu nyt osoitteessa {host}. Asenna se sieltä." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "kysely" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri vaaditaan, kun bot-käyttöoikeus ei ole ainoa käyttöoikeus." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Lisätä botin yhteisöön pyydetyillä käyttöoikeuksilla" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Lisää kommentti (valinnainen)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Lisää YouTube Data API -avain rikastamaan YouTube-linkkejä." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Lisää tili" @@ -4379,6 +4409,11 @@ msgstr "Hyväksyntä vaaditaan" msgid "Approve" msgstr "Hyväksy" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Hyväksy tämä sovellus jo käyttämästäsi {productName}-sovelluksesta. Salasanaa ei tarvita." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Valtuutus epäonnistui" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Valtuutus epäonnistui. Yritä uudelleen." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Botin käyttöoikeudet" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Botin käyttöoikeudet eivät voi olla negatiivisia." @@ -6329,10 +6364,10 @@ msgstr "Tiedostoja ei voi lähettää tähän" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Tiedostoja ei voi lähettää tähän" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Valitse tausta" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Valitse joko guild_id tai channel_id, älä molempia." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Valitse Tiedosto ja sitten Lisää Dockiin." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "koodilohko" msgid "Code blocks" msgstr "Koodilohkot" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Koodi vanhentunut" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Komennot" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Kommentointi on poistettu käytöstä, koska yhdellä tai useammalla valitulla kanavalla on hidas tila päällä. Hidas tila estäisi kommentin heti edelleenlähetyksen jälkeen." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Kommentointi on poistettu käytöstä, koska kanavalla {forwardChannelDisplayName} on hidas tila päällä. Hidas tila estäisi kommentin heti edelleenlähetyksen jälkeen." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Kommentit eivät ole käytettävissä, kun hidas tila on päällä." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Yhteisön viesti-ilmoitukset" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Yhteisön MFA-vaatimuksen ilmoituspalkki" @@ -9182,7 +9222,7 @@ msgstr "Jatka yhteisön ilmoittamiseen" msgid "Continue to report user" msgstr "Jatka käyttäjän ilmoittamiseen" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Jatka pääsyavaimella" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Koordinoi hyökkäyksiä, joukkohäirintää tai ahdistelua ihmisiä tai yhteisöjä vastaan." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Kopioitu" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kopioi kanavalinkki" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kopioi koodi" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Nykyiset käyttökerrat:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Tällä hetkellä suljettu" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Tällä hetkellä näkyvissä" @@ -12555,6 +12595,11 @@ msgstr "Ei estä. Voit avata keskustelun myöhemmin uudelleen." msgid "Domain" msgstr "Verkkotunnus" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Verkkotunnus siirretty" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "hollanti" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "esim. YubiKey, iPhone, työkone" @@ -14056,10 +14101,14 @@ msgstr "Syötä turva-avaimesi PIN-koodi jatkaaksesi." msgid "Enter the SMS code" msgstr "Anna tekstiviestikoodi" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Kirjoita tämä koodi selaimeesi kirjautumisen viimeistelemiseksi." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Syötä tämä koodi vanhaan {PRODUCT_NAME}-sovellukseesi kirjautumisen viimeistelemiseksi." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "enter <0><1>tallentaa" @@ -14441,7 +14490,7 @@ msgstr "Vanhenee {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Vanhenee välillä {earliest} ja {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Vanhenee {remaining} s kuluttua" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "SSO-kirjautumisen aloitus epäonnistui" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Tilin vaihtaminen epäonnistui" @@ -15413,17 +15462,17 @@ msgstr "Minulle" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Tässä tarkistuksessa lähetät meille tekstiviestin sen sijaan, että saisit sen meiltä." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Vahvista turvallisuussyistä henkilöllisyytesi, jotta voit jatkaa." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Pakotettu pois käytöstä" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Pakotettu käyttöön" @@ -15540,7 +15589,7 @@ msgstr "Salasana unohtunut" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Unohditko salasanasi?" @@ -15571,7 +15620,7 @@ msgstr "Välitä media" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Välitä viesti" @@ -15965,11 +16014,11 @@ msgstr "Luo jaettava linkki tai kopioi yhdistetty mukautettu CSS-koodisi." msgid "Generate invite link" msgstr "Luo kutsulinkki" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Luo uusi koodi" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Luodaan koodia…" @@ -16045,6 +16094,11 @@ msgstr "Hanki {productName}-työpöytäsovellus, niin saat järjestelmänlaajuis msgid "Get the desktop app" msgstr "Hanki työpöytäsovellus" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Hanki uusi {productName} -sovellus" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Lisää emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Asenna {productName}, niin saat käyttöösi täyden lähdevalitsimen ja sujuvammat näytönjaon hallintatoiminnot." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Asenna se selaimesi valikosta." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Asenna uusi sovellus" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Virheellinen valtuutuspyyntö" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Virheelliset botin käyttöoikeudet." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Virheellinen tai vanhentunut koodi. Yritä uudelleen." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Virheellinen redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Linkki" msgid "Link (mature)" msgstr "Linkki (aikuisille)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Linkitä uusi laite" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Viestien näyttöasetukset ovat käytettävissä vain työpöytäsovellu msgid "Message editing" msgstr "Viestin muokkaus" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Viesti välitetty" @@ -20746,7 +20815,7 @@ msgstr "Viestinsyötön painikkeet" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Viesti on liian pitkä" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Muut" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id puuttuu" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Nimen on oltava vähintään 2 merkkiä pitkä" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Nimeä pääsyavain" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Pääsyavaimen nimeämislomake" @@ -22381,7 +22450,7 @@ msgstr "Ei tallennettavia muutoksia" msgid "No channels available" msgstr "Ei kanavia saatavilla" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Kanavia ei löytynyt" @@ -22824,7 +22893,7 @@ msgstr "Käyttäjiä ei löytynyt" msgid "No verification is required." msgstr "Vahvistusta ei tarvita." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Vahvistustapoja ei ole saatavilla" @@ -23417,6 +23486,16 @@ msgstr "Avaa {contextName}" msgid "Open {displayName}" msgstr "Avaa {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Avaa {host} Safarissa." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Avaa {host} selaimessasi." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Avaa {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Avaa {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Avaa {productName} selaimessa" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Avaa banneriasetukset" msgid "Open bookmarks" msgstr "Avaa kirjanmerkit" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Avaa selain" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Avaa emoji selaimessa" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Avaa emojivalitsin" @@ -23809,6 +23893,16 @@ msgstr "Avaa alla oleva linkki selaimessasi, kopioi koko JSON-vastaus ja liitä msgid "Open the menu and choose an option." msgstr "Avaa valikko ja valitse vaihtoehto." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Avaa uusi sovellus ja valitse Kirjaudu sisään vanhalla {productName} -sovelluksellasi. Valitse sitten Linkitä uusi laite tähän ja anna näytetty koodi." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Avaa uusi sovellus. Olet jo kirjautuneena sisään." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Avaa äänikanava" msgid "Open voice & video settings" msgstr "Avaa ääni- ja videoasetukset" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Avaa selaimesi, kirjaudu sisään ja syötä sitten alla oleva koodi tilisi yhdistämiseksi." @@ -23868,6 +23962,10 @@ msgstr "Avaa selaimesi, kirjaudu sisään ja syötä sitten alla oleva koodi til msgid "Open your messages" msgstr "Avaa viestisi" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Avaa vanha {PRODUCT_NAME}-sovelluksesi ja valitse \"Linkitä uusi laite\" ja syötä sitten alla oleva koodi." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Avaa puhelimen viestisovellus ja luo uusi tekstiviesti." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Pääsyavain" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Pääsyavaimen nimi" @@ -28138,7 +28236,7 @@ msgstr "Kylläisyys" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Kelauksen esikatselu" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Valitse teksti" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Valitse tili, jolla haluat kirjautua sisään työpöytäsovellukseen." +msgid "Select the account you want to sign in with on your new device." +msgstr "Valitse tili, jolla haluat kirjautua sisään uudelle laitteellesi." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Lähetä" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Lähetä ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Istunto" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Tilin {identifier} istunto on vanhentunut. Kirjaudu sisään uudelleen." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Pikanäppäimet eivät ole käytössä näppäinyhdistelmän tallennuksen aikana." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Lyhennä viestiä ja yritä uudelleen." @@ -30217,6 +30315,11 @@ msgstr "Näytä enintään 3 (+5-merkki)" msgid "Show max 5 (+5 badge)" msgstr "Näytä enintään 5 (+5-merkki)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Näytä miten" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Kirjaudu sisään lunastaaksesi lahjasi" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Kirjaudu sisään selaimella" @@ -30496,6 +30599,11 @@ msgstr "Kirjaudu sisään selaimella" msgid "Sign in with a passkey" msgstr "Kirjaudu sisään pääsyavaimella" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Kirjaudu sisään vanhalla {productName}-sovelluksellasi" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Kirjautumistiedot" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Kirjautumisprosessi" @@ -31030,7 +31138,7 @@ msgstr "Joitakin käyttäjiä ei voi lisätä" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Puhu tällä äänikanavalla. Ilman tätä käyttöoikeutta jäsenet pys msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Puhu äänikanavilla. Ilman tätä käyttöoikeutta jäsenet pysyvät mykistettyinä, kunnes joku, jolla on \"Mykistä jäseniä\" -käyttöoikeus, poistaa heidän mykistyksensä." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Napauta Jaa ja sitten Lisää Koti-valikkoon." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Puhelinnumeron vahvistus siirrettiin sivuun. Yksi sähköpostivaihe on jäljellä." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Annettua redirect_uri-osoitetta ei ole rekisteröity tälle sovellukselle." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "redirect_uri-osoitteen on käytettävä HTTP- tai HTTPS-protokollaa." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Animaatio on liian pitkä tai liian suuri rajattavaksi. Kokeile lyhyempää tai pienempää tiedostoa." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Tämä sovellus" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Tämä liite on vanhentunut" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Tämä botti ei ole julkinen, eivätkä muut käyttäjät voi lisätä sitä." @@ -33757,7 +33870,7 @@ msgstr "Tässä ryhmässä on liikaa ihmisiä" msgid "This group is full. Remove someone before adding more people." msgstr "Tämä ryhmä on täynnä. Poista joku ennen kuin lisäät lisää ihmisiä." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Tämä siirtokoodi on vanhentunut. Luo uusi jatkaaksesi." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Tuntematon" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Tuntematon sovellus, vanhentunut istunto tai virheellinen valtuutuspyyntö." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Käyttökerrat:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Käytetään todellista tilin tilaa" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Käytetään todellista lahjavaraston tilaa" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Käytetään todellista premium-tilaa" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Käytetään todellista tilaa" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Käytetään todellista vahvistustilaa" @@ -37947,17 +38060,17 @@ msgstr "Odota tässä" msgid "Waiting for connection..." msgstr "Odotetaan yhteyttä..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Odotetaan pääsyavainta…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Odotetaan hitaan tilan päättymistä kanavalla {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Odotetaan hitaan tilan päättymistä yhdessä tai useammassa valitussa kanavassa." @@ -38156,7 +38269,7 @@ msgstr "Pienensimme ja pakkasimme tämän tarran 320x320 pikseliin, mutta sen ko msgid "We sent a code to {email}." msgstr "Lähetimme koodin osoitteeseen {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Esitäytämme sähköpostiosoitteen {prefillEmail}, kun kirjautuminen avautuu selaimessa." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Verkkosivujen esikatselut" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Tervetuloa takaisin" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "{premiumProductName}-tilauksesi on vanhentunut. Olet menettänyt kaikki {premiumProductName}-edut. Aktivoi tilauksesi uudelleen saadaksesi edut takaisin." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "{productName}-istuntosi vanheni. Kirjaudu sisään uudelleen jatkaaksesi tämän sovelluksen valtuuttamista." @@ -39734,7 +39847,7 @@ msgstr "Tilisi on liian uusi viestien lähettämiseen tässä yhteisössä." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Tilipyyntösi odottaa hyväksyntää. Voit kirjautua sisään, kun ylläpitäjä on hyväksynyt sen." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Tilisi edellyttää monivaiheista todennusta, mutta tällä laitteella ei ole tuettuja menetelmiä käytössä. Kirjaudu sisään laitteella, jossa on todennussovelluksesi tai turva-avaimesi, tai ota yhteyttä tukeen." @@ -39757,6 +39870,11 @@ msgstr "Toimintoasi ei voitu suorittaa, koska turvajärjestelmämme merkitsivät msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Hakemuksesi odottaa tarkistusta. Voit edelleen päivittää listauksesi tietoja tai peruuttaa hakemuksen." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Selaimesi ei tue salausavaimia tällä verkkotunnuksella. Päivitä selaimesi tai kirjaudu sisään salasanallasi ja kaksivaiheisella koodilla." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Korttisi kelpaa. Ohjaamme sinut nyt maksusivulle." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Operaattorisi mukaan numero ei ole käytössä. Tarkista se ja yritä uudelleen tai ota yhteyttä tukeen, jos se on oikein." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Koodisi" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Koodisi on valmis" diff --git a/fluxer_app/src/features/i18n/locales/fr/messages.po b/fluxer_app/src/features/i18n/locales/fr/messages.po index e85d2f0d6..04f3874ef 100644 --- a/fluxer_app/src/features/i18n/locales/fr/messages.po +++ b/fluxer_app/src/features/i18n/locales/fr/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# message bloqué} other {# messages bloqués}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# communauté trouvée} other {# communautés trouvées}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# communauté est temporairement indisponible en raison d'un dysfonctionnement du condensateur de flux.} other {# communautés sont temporairement indisponibles en raison d'un dysfonctionnement du condensateur de flux.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} a détecté une attaque potentielle dans cette communauté msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} a détecté une attaque potentielle. Les nouveaux utilisateurs ne peuvent donc pas rejoindre la communauté pour le moment." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} a déménagé sur {host}. Ajoutez-le à votre écran d’accueil ou à votre Dock, puis connectez-vous avec cette application." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} a déménagé sur {host}. Installez-le depuis votre navigateur, puis connectez-vous avec cette application." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} a déménagé sur {host}. Installez la nouvelle application et vous serez déjà connecté." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} a déménagé sur {host}. Ouvrez-le dans votre navigateur pour installer la nouvelle application." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} a besoin d'accéder à votre microphone pour afficher vos msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} a besoin d'accéder à l'enregistrement d'écran. Autorisez l'enregistrement d'écran dans les paramètres de confidentialité de votre système d'exploitation et redémarrez l'application." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} est maintenant disponible sur {host}. Installez-le à partir de là, puis connectez-vous avec cette application." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} est maintenant disponible sur {host}. Installez-le à partir de là." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "un sondage" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Le paramètre redirect_uri est requis lorsque bot n’est pas la seule portée demandée." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Ajouter un bot à une communauté avec les permissions demandées" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Ajouter un commentaire (facultatif)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Ajoutez une clé API YouTube Data pour enrichir les liens YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Ajouter un compte" @@ -4379,6 +4409,11 @@ msgstr "Approbation requise" msgid "Approve" msgstr "Approuver" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Approuvez cette application depuis l'application {productName} que vous utilisez déjà. Aucun mot de passe requis." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Échec de l'autorisation" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Échec de l'autorisation. Réessayez." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Permissions du bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "La valeur des permissions du bot ne peut pas être négative." @@ -6329,10 +6364,10 @@ msgstr "Impossible d’envoyer des fichiers ici" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Impossible d’envoyer des fichiers ici" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Choisir un arrière-plan" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Choisissez guild_id ou channel_id, pas les deux." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Choisissez Fichier, puis Ajouter à mon Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "bloc de code" msgid "Code blocks" msgstr "Blocs de code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Code expiré" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Commandes" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Les commentaires sont désactivés car un ou plusieurs des salons sélectionnés ont le mode lent activé. Le commentaire serait bloqué par le mode lent juste après le transfert." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Les commentaires sont désactivés, car le mode lent est activé dans {forwardChannelDisplayName}. Le commentaire serait bloqué par le mode lent juste après le transfert." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Les commentaires ne sont pas disponibles lorsque le mode lent est activé." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Notifications des messages de communauté" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Bannière d’exigence MFA de la communauté" @@ -9182,7 +9222,7 @@ msgstr "Poursuivre le signalement de la communauté" msgid "Continue to report user" msgstr "Poursuivre le signalement de l’utilisateur" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Continuer avec une clé d'accès" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coordonne des raids, des attaques collectives ou du harcèlement contre des personnes ou des communautés." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Copié" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Copier le lien du salon" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Copier le code" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Utilisations actuelles :" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Actuellement ignoré" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Affiché actuellement" @@ -12555,6 +12595,11 @@ msgstr "Ne bloque pas la personne. Vous pourrez rouvrir la conversation plus tar msgid "Domain" msgstr "Domaine" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Le domaine a déménagé" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Néerlandais" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "ex. : YubiKey, iPhone, ordinateur professionnel" @@ -14056,10 +14101,14 @@ msgstr "Entrez le code PIN de votre clé de sécurité pour continuer." msgid "Enter the SMS code" msgstr "Saisissez le code SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Saisissez ce code dans votre navigateur pour terminer la connexion." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Entrez ce code dans votre ancienne application {PRODUCT_NAME} pour terminer la connexion." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "appuyez sur Entrée pour <0><1>enregistrer" @@ -14441,7 +14490,7 @@ msgstr "Expire le {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Expire entre {earliest} et {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Expire dans {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Échec du démarrage de l'authentification unique (SSO)" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Échec du changement de compte" @@ -15413,17 +15462,17 @@ msgstr "Pour moi" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Pour cette vérification, vous nous envoyez un SMS au lieu d’en recevoir un de notre part." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Pour votre sécurité, veuillez confirmer votre identité pour continuer." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Désactivé de force" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Activé de force" @@ -15540,7 +15589,7 @@ msgstr "Mot de passe oublié" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Mot de passe oublié ?" @@ -15571,7 +15620,7 @@ msgstr "Transférer le média" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Transférer le message" @@ -15965,11 +16014,11 @@ msgstr "Générez un lien partageable ou copiez votre CSS personnalisé combiné msgid "Generate invite link" msgstr "Générer un lien d'invitation" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Générer un nouveau code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Génération du code…" @@ -16045,6 +16094,11 @@ msgstr "Téléchargez l’application de bureau {productName} pour utiliser « A msgid "Get the desktop app" msgstr "Télécharger l’application de bureau" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Obtenez la nouvelle application {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Insérer un emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Installez {productName} pour profiter de toutes les options de sélection de source et de commandes de partage d'écran plus fluides." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Installez-le depuis le menu de votre navigateur." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Installer la nouvelle application" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Requête d'autorisation non valide" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Permissions du bot non valides." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Code invalide ou expiré. Veuillez réessayer." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Paramètre redirect_uri non valide" @@ -19332,6 +19396,11 @@ msgstr "Lien" msgid "Link (mature)" msgstr "Lien (pour adultes)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Lier un nouvel appareil" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Les paramètres d'affichage des messages ne sont disponibles que sur ord msgid "Message editing" msgstr "Modification des messages" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Message transféré" @@ -20746,7 +20815,7 @@ msgstr "Boutons de saisie de message" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Message trop long" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Divers" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id manquant" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Le nom doit contenir au moins 2 caractères" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Nommer la clé d'accès" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formulaire pour nommer la clé d’accès" @@ -22381,7 +22450,7 @@ msgstr "Aucune modification à enregistrer" msgid "No channels available" msgstr "Aucun salon disponible" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Aucun salon trouvé" @@ -22824,7 +22893,7 @@ msgstr "Aucun utilisateur trouvé" msgid "No verification is required." msgstr "Aucune vérification n'est requise." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Aucune méthode de vérification disponible" @@ -23417,6 +23486,16 @@ msgstr "Ouvrir {contextName}" msgid "Open {displayName}" msgstr "Ouvrir {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Ouvrez {host} dans Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Ouvrez {host} dans votre navigateur." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Ouvrez « {macosSystemSettingsName} » → « {macosPrivacyAndSecurit msgid "Open {productName}" msgstr "Ouvrir {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Ouvrir {productName} dans votre navigateur" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Ouvrir les options de la bannière" msgid "Open bookmarks" msgstr "Ouvrir les messages enregistrés" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Ouvrir le navigateur" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Ouvrir l'emoji dans le navigateur" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Ouvrir le sélecteur d'emojis" @@ -23809,6 +23893,16 @@ msgstr "Ouvrez le lien ci-dessous dans votre navigateur, puis copiez l'intégral msgid "Open the menu and choose an option." msgstr "Ouvrez le menu et choisissez une option." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Ouvrez la nouvelle application et choisissez Se connecter avec votre ancienne application {productName}. Ensuite, choisissez Lier un nouvel appareil ici et entrez le code qui s'affiche." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Ouvrez la nouvelle application. Vous êtes déjà connecté." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Ouvrir la discussion vocale" msgid "Open voice & video settings" msgstr "Ouvrir les paramètres audio et vidéo" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Ouvrez votre navigateur, connectez-vous, puis saisissez le code ci-dessous pour associer votre compte." @@ -23868,6 +23962,10 @@ msgstr "Ouvrez votre navigateur, connectez-vous, puis saisissez le code ci-desso msgid "Open your messages" msgstr "Ouvrez vos messages" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Ouvrez votre ancienne application {PRODUCT_NAME} et choisissez \"Lier un nouvel appareil\", puis entrez le code ci-dessous." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Ouvrez l’application de SMS de votre téléphone et rédigez un nouveau message." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Clé d'accès" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Nom de la clé d'accès" @@ -28138,7 +28236,7 @@ msgstr "Saturation" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Aperçu lors du déplacement dans la vidéo" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Sélectionner le texte" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Sélectionnez le compte avec lequel vous souhaitez vous connecter sur l'application de bureau." +msgid "Select the account you want to sign in with on your new device." +msgstr "Sélectionnez le compte avec lequel vous souhaitez vous connecter sur votre nouvel appareil." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Envoyer" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Envoyer ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Session" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Votre session pour {identifier} a expiré. Veuillez vous reconnecter." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Les raccourcis sont suspendus pendant l’enregistrement." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Raccourcissez le message et réessayez." @@ -30217,6 +30315,11 @@ msgstr "Afficher 3 au maximum (badge +5)" msgid "Show max 5 (+5 badge)" msgstr "Afficher 5 au maximum (badge +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Montrez-moi comment" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Connectez-vous pour récupérer votre cadeau" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Se connecter via le navigateur" @@ -30496,6 +30599,11 @@ msgstr "Se connecter via le navigateur" msgid "Sign in with a passkey" msgstr "Se connecter avec une clé d'accès" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Connectez-vous avec votre ancienne application {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Informations de connexion" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Parcours de connexion" @@ -31030,7 +31138,7 @@ msgstr "Certains utilisateurs ne peuvent pas être ajoutés" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Parler dans ce salon vocal. Sans cette permission, le micro des membres msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Parler dans les salons vocaux. Sans cette permission, le micro des membres reste coupé jusqu’à ce qu’une personne disposant de la permission « Couper le micro des membres » le réactive." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Appuyez sur Partager, puis sur Ajouter à l'écran d'accueil." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "La vérification du téléphone est mise de côté. Il reste une étape par e-mail." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "L'URI de redirection fournie n'est pas enregistrée pour cette application." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "L'URI de redirection fournie doit utiliser HTTP ou HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Cette animation est trop longue ou trop volumineuse pour être rognée. Essayez un fichier plus court ou plus petit." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Cette application" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Cette pièce jointe a expiré" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Ce bot n'est pas public et ne peut pas être ajouté par d'autres utilisateurs." @@ -33757,7 +33870,7 @@ msgstr "Ce groupe contient trop de personnes" msgid "This group is full. Remove someone before adding more people." msgstr "Ce groupe est plein. Retirez un membre avant d’ajouter d’autres personnes." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Ce code de transfert a expiré. Générez-en un nouveau pour continuer." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Inconnu" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Application inconnue, session expirée ou requête d'autorisation invalide." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Utilisations :" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Utilise l'état réel du compte" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Utilisation de l’état réel de l’inventaire des cadeaux" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Utilise l'état réel de l'abonnement premium" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Utilise l'état réel" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Utilisation de l'état de vérification réel" @@ -37947,17 +38060,17 @@ msgstr "Veuillez patienter" msgid "Waiting for connection..." msgstr "En attente de connexion…" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "En attente de la clé d’accès…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "En attente de l'expiration du mode lent dans {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "En attente de l'expiration du mode lent dans un ou plusieurs salons sélectionnés." @@ -38156,7 +38269,7 @@ msgstr "Nous avons redimensionné et compressé ce sticker en 320 × 320 pixels, msgid "We sent a code to {email}." msgstr "Nous avons envoyé un code à {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Nous allons préremplir {prefillEmail} une fois que la connexion par navigateur s'ouvrira." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Aperçus des sites web" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Bon retour" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Votre abonnement {premiumProductName} a expiré. Vous avez perdu tous les avantages {premiumProductName}. Réactivez votre abonnement pour retrouver l'accès." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Votre session {productName} a expiré. Connectez-vous à nouveau pour continuer à autoriser cette application." @@ -39734,7 +39847,7 @@ msgstr "Votre compte est trop récent pour envoyer des messages dans cette commu msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Votre demande de compte est en attente d’approbation. Vous pourrez vous connecter lorsqu’un admin l’aura approuvée." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Votre compte nécessite une authentification multifacteur, mais aucune méthode prise en charge n'est configurée sur cet appareil. Connectez-vous sur un appareil doté de votre application d'authentification ou de votre clé de sécurité, ou contactez l'assistance." @@ -39757,6 +39870,11 @@ msgstr "Votre action n'a pas pu être effectuée, car elle a été signalée par msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Votre candidature est en attente d’examen. Vous pouvez encore modifier les détails de votre fiche ou retirer votre candidature." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Votre navigateur ne prend pas en charge les clés d'accès sur ce domaine. Mettez à jour votre navigateur, ou connectez-vous avec votre mot de passe et votre code à deux facteurs." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Votre carte est éligible. Redirection vers la page de paiement en cours msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Votre opérateur indique que ce numéro n’est pas en service. Vérifiez-le et réessayez, ou contactez l’assistance si le numéro est correct." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Votre code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Votre code est prêt" diff --git a/fluxer_app/src/features/i18n/locales/he/messages.po b/fluxer_app/src/features/i18n/locales/he/messages.po index 66e74db1a..e7fa2c07a 100644 --- a/fluxer_app/src/features/i18n/locales/he/messages.po +++ b/fluxer_app/src/features/i18n/locales/he/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# הודעה חסומה} other {# הודעות חס msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# קהילה נמצאה} other {# קהילות נמצאו}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# קהילה אינה זמינה באופן זמני עקב תקלה בקבל השטף.} other {# קהילות אינן זמינות באופן זמני עקב תקלה בקבל השטף.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} זיהה פשיטה אפשרית בקהילה זו. ההז msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} זיהה פשיטה אפשרית, ולכן משתמשים חדשים לא יכולים להצטרף כרגע." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} עבר ל-{host}. הוסף אותו למסך הבית או לעמדת העגינה, ואז היכנס באמצעות אפליקציה זו." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} עבר ל-{host}. התקן אותו מהדפדפן שלך, ואז היכנס עם האפליקציה הזו." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} עבר ל-{host}. התקן את האפליקציה החדשה וכבר תהיה מחובר." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "האפליקציה {productName} עברה ל- {host}. פתח אותה בדפדפן כדי להתקין את האפליקציה החדשה." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} זקוק לגישה למיקרופון כדי להציג msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} זקוק לגישה להקלטת מסך. אפשר הקלטת מסך בהגדרות הפרטיות של מערכת ההפעלה שלך והפעל מחדש את האפליקציה." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} נמצא כעת בכתובת {host}. התקן אותו משם, ולאחר מכן היכנס באמצעות האפליקציה הזו." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} עבר לכתובת {host}. התקן אותו משם." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "סקר" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "נדרש redirect_uri כאשר ההרשאה bot אינה ההרשאה היחידה." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "הוספת בוט לקהילה עם ההרשאות המבוקשות" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "הוסף הערה (אופציונלי)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "הוסף מפתח API של YouTube Data כדי להעשיר קישורי YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "הוספת חשבון" @@ -4379,6 +4409,11 @@ msgstr "אישור נדרש" msgid "Approve" msgstr "אשר" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "אשר את האפליקציה הזו מתוך אפליקציית {productName} שבה אתה כבר משתמש. אין צורך בסיסמה." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "ההרשאה נכשלה" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "ההרשאה נכשלה. יש לנסות שוב." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "הרשאות בוט" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "הרשאות בוט לא יכולות להיות שליליות." @@ -6329,10 +6364,10 @@ msgstr "לא ניתן להעלות קבצים כאן" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "לא ניתן להעלות קבצים כאן" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "בחר רקע" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "יש לבחור guild_id או channel_id, לא את שניהם." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "בחר קובץ, ואז הוסף ל-Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "בלוק קוד" msgid "Code blocks" msgstr "בלוקי קוד" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "פג תוקף הקוד" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "פקודות" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "התגובות מושבתות כי מצב איטי מופעל באחד או יותר מהערוצים שבחרת. התגובה תיחסם על ידי מצב איטי מיד לאחר ההעברה." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "התגובות מושבתות כי מצב איטי מופעל ב-{forwardChannelDisplayName}. התגובה תיחסם על ידי מצב איטי מיד לאחר ההעברה." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "התגובות אינן זמינות כשמצב איטי מופעל." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "התראות על הודעות קהילה" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "שורת התראה על דרישת אימות דו-שלבי בקהילה" @@ -9182,7 +9222,7 @@ msgstr "המשך לדווח על הקהילה" msgid "Continue to report user" msgstr "המשך לדווח על המשתמש" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "המשך עם מפתח גישה" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "מתאם פשיטות, התארגנויות או הטרדות נגד אנשים או קהילות." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "הועתק" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "העתקת קישור לערוץ" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "העתקת קוד" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "שימושים נוכחיים:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "מוסתר כעת" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "מוצג כעת" @@ -12555,6 +12595,11 @@ msgstr "לא חוסם. אפשר לפתוח שוב מאוחר יותר." msgid "Domain" msgstr "דומיין" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "הדומיין הועבר" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "הולנדית" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "לדוגמה: YubiKey, iPhone, מחשב עבודה" @@ -14056,10 +14101,14 @@ msgstr "הזן את ה-PIN של מפתח האבטחה שלך כדי להמשיך msgid "Enter the SMS code" msgstr "הזן את קוד ה-SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "הזן קוד זה בדפדפן שלך כדי להשלים את הכניסה." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "הזן את הקוד הזה באפליקציית ה-{PRODUCT_NAME} הישנה שלך כדי להשלים את הכניסה." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter כדי <0><1>לשמור" @@ -14441,7 +14490,7 @@ msgstr "יפוג ב-{absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "יפוג בין {earliest} ל-{latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "יפוג בעוד {remaining} שנ'" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "הפעלת ההתחברות באמצעות SSO נכשלה" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "החלפת החשבון נכשלה" @@ -15413,17 +15462,17 @@ msgstr "בשבילי" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "בבדיקה הזו יש לשלוח לנו הודעת טקסט במקום לקבל הודעה מאיתנו." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "למען אבטחתך, יש לאמת את זהותך כדי להמשיך." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "מושבת בכפייה" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "מופעל בכפייה" @@ -15540,7 +15589,7 @@ msgstr "שכחתי סיסמה" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "שכחת את הסיסמה?" @@ -15571,7 +15620,7 @@ msgstr "העבר מדיה" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "העברת הודעה" @@ -15965,11 +16014,11 @@ msgstr "יצירת קישור שניתן לשיתוף או העתקת קוד ה- msgid "Generate invite link" msgstr "יצירת קישור הזמנה" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "יצירת קוד חדש" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "יוצר קוד…" @@ -16045,6 +16094,11 @@ msgstr "קבל את אפליקציית {productName} למחשב כדי ליהנ msgid "Get the desktop app" msgstr "הורדת אפליקציית המחשב" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "הורד את אפליקציית {productName} החדשה" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "הוספת אימוג'י" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "התקן את {productName} כדי לקבל את בורר המקור המלא ובקרות חלקות יותר לשיתוף מסך." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "התקן אותו מתפריט הדפדפן שלך." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "התקן את האפליקציה החדשה" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "בקשת הרשאה לא תקינה" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "הרשאות בוט לא תקינות." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "קוד שגוי או שפג תוקפו. נסה שוב." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "כתובת הניתוב מחדש (redirect_uri) אינה תקינה" @@ -19332,6 +19396,11 @@ msgstr "קישור" msgid "Link (mature)" msgstr "קישור (למבוגרים בלבד)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "קשר מכשיר חדש" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "הגדרות תצוגת ההודעות זמינות רק במחשב." msgid "Message editing" msgstr "עריכת הודעה" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "ההודעה הועברה" @@ -20746,7 +20815,7 @@ msgstr "כפתורי שדה ההודעה" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "ההודעה ארוכה מדי" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "שונות" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "חסר client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "השם חייב להכיל לפחות 2 תווים" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "מתן שם למפתח גישה" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "טופס מתן שם למפתח גישה" @@ -22381,7 +22450,7 @@ msgstr "אין שינויים לשמירה" msgid "No channels available" msgstr "אין ערוצים זמינים" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "לא נמצאו ערוצים" @@ -22824,7 +22893,7 @@ msgstr "לא נמצאו משתמשים" msgid "No verification is required." msgstr "אין צורך באימות." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "אין שיטות אימות זמינות" @@ -23417,6 +23486,16 @@ msgstr "פתח את {contextName}" msgid "Open {displayName}" msgstr "פתח את {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "פתח את {host} בספארי." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "פתח את {host} בדפדפן שלך." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "פתח את {macosSystemSettingsName} → {macosPrivacyAndSecuritySettin msgid "Open {productName}" msgstr "פתח את {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "פתח את {productName} בדפדפן שלך" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "פתיחת אפשרויות באנר" msgid "Open bookmarks" msgstr "פתיחת סימניות" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "פתח דפדפן" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "פתח אימוג'י בדפדפן" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "פתח בורר אימוג'י" @@ -23809,6 +23893,16 @@ msgstr "יש לפתוח את הקישור למטה בדפדפן, להעתיק א msgid "Open the menu and choose an option." msgstr "פתח את התפריט ובחר אפשרות." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "פתח את האפליקציה החדשה ובחר \"התחבר עם אפליקציית {productName} הישנה שלך\". לאחר מכן בחר \"קשר מכשיר חדש כאן\" והזן את הקוד שיוצג." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "פתח את האפליקציה החדשה. אתה כבר מחובר." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "פתח שיחה קולית" msgid "Open voice & video settings" msgstr "פתיחת הגדרות שמע ווידאו" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "פתח את הדפדפן שלך, היכנס, ואז הזן את הקוד למטה כדי לקשר את חשבונך." @@ -23868,6 +23962,10 @@ msgstr "פתח את הדפדפן שלך, היכנס, ואז הזן את הקוד msgid "Open your messages" msgstr "פתח את ההודעות שלך" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "פתח את אפליקציית ה-{PRODUCT_NAME} הישנה שלך ובחר \"קשר מכשיר חדש\", ואז הזן את הקוד שלמטה." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "פתח את אפליקציית ההודעות בטלפון וצור הודעת טקסט חדשה." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "מפתח גישה" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "שם מפתח גישה" @@ -28138,7 +28236,7 @@ msgstr "רוויה" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "תצוגה מקדימה בגלילה" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "בחירת טקסט" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "יש לבחור את החשבון שאיתו רוצים להיכנס לאפליקציית המחשב." +msgid "Select the account you want to sign in with on your new device." +msgstr "בחר את החשבון שברצונך להתחבר איתו במכשיר החדש שלך." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "שליחה" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "שליחה ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "סשן" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "תוקף הסשן עבור {identifier} פג. יש להיכנס שוב." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "קיצורי הדרך מושהים בזמן הקלטת צירוף מקשים." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "יש לקצר את ההודעה ולנסות שוב." @@ -30217,6 +30315,11 @@ msgstr "הצג עד 3 (תג +5)" msgid "Show max 5 (+5 badge)" msgstr "הצג עד 5 (תג +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "הצג לי כיצד" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "היכנס כדי לקבל את המתנה שלך" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "התחבר דרך הדפדפן" @@ -30496,6 +30599,11 @@ msgstr "התחבר דרך הדפדפן" msgid "Sign in with a passkey" msgstr "היכנס באמצעות מפתח גישה" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "התחבר עם האפליקציה הישנה שלך של {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "פרטי כניסה" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "תהליך ההתחברות" @@ -31030,7 +31138,7 @@ msgstr "לא ניתן להוסיף חלק מהמשתמשים" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "דיבור בערוץ הקולי הזה. בלי הרשאה זו, חבר msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "דיבור בערוצי קול. בלי הרשאה זו, חברים יישארו מושתקים עד שמישהו עם הרשאת \"השתקת חברים\" יבטל את השתקתם." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "הקש על \"שתף\", ואז על \"הוסף למסך הבית\"." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "בדיקת הטלפון נדחתה. נותר שלב אחד של אימייל." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "כתובת ה-redirect_uri שצוינה אינה רשומה עבור האפליקציה הזו." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "ה-redirect_uri שסופק חייב להשתמש ב-HTTP או ב-HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "האנימציה הזו ארוכה מדי או גדולה מדי לחיתוך. נסה קובץ קצר או קטן יותר." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "האפליקציה הזו" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "פג תוקפו של הקובץ המצורף" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "הבוט הזה אינו ציבורי ולא ניתן להוסיף אותו על ידי משתמשים אחרים." @@ -33757,7 +33870,7 @@ msgstr "בקבוצה הזו יש יותר מדי אנשים" msgid "This group is full. Remove someone before adding more people." msgstr "הקבוצה הזו מלאה. הסר מישהו לפני הוספת אנשים נוספים." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "תוקף קוד ההעברה הזה פג. יש ליצור קוד חדש כדי להמשיך." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "לא ידוע" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "אפליקציה לא ידועה, סשן שפג תוקפו או בקשת הרשאה שגויה." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "שימושים:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "משתמש במצב החשבון האמיתי" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "משתמש במצב מלאי המתנות האמיתי" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "משתמש במצב פרימיום אמיתי" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "משתמש במצב אמיתי" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "משתמש במצב אימות אמיתי" @@ -37947,17 +38060,17 @@ msgstr "המתן כאן" msgid "Waiting for connection..." msgstr "ממתין לחיבור..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "ממתין למפתח גישה…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "ממתין שתוקף מצב איטי ב-{forwardChannelDisplayName} יפוג." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "ממתין שתוקף מצב איטי באחד או יותר מהערוצים שנבחרו יפוג." @@ -38156,7 +38269,7 @@ msgstr "שינינו את גודל המדבקה ודחסנו אותה ל-320x320 msgid "We sent a code to {email}." msgstr "שלחנו קוד לכתובת {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "נמלא מראש את {prefillEmail} לאחר שההתחברות בדפדפן תיפתח." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "תצוגות מקדימות של אתרים" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "ברוך שובך" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "תוקף המנוי שלך ל-{premiumProductName} פג. איבדת את כל ההטבות של {premiumProductName}. הפעל מחדש את המנוי שלך כדי לקבל גישה שוב." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "הסשן שלך ב-{productName} פג. היכנס שוב כדי להמשיך ולאשר את האפליקציה הזו." @@ -39734,7 +39847,7 @@ msgstr "החשבון שלך חדש מדי כדי לשלוח הודעות בקה msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "בקשת החשבון שלך ממתינה לאישור. אפשר להתחבר לאחר שמנהל יאשר אותה." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "החשבון שלך דורש אימות רב-שלבי, אך לא הוגדרו שיטות נתמכות במכשיר זה. היכנס באמצעות מכשיר עם אפליקציית האימות או מפתח האבטחה שלך, או פנה לתמיכה." @@ -39757,6 +39870,11 @@ msgstr "הפעולה שלך לא הושלמה כי היא סומנה על ידי msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "הבקשה שלך ממתינה לבדיקה. עדיין באפשרותך לעדכן את פרטי הרישום או לבטל את הבקשה." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "הדפדפן שלך אינו תומך במפתחות גישה בתחום זה. עדכן את הדפדפן שלך, או היכנס באמצעות הסיסמה שלך וקוד האימות הדו-שלבי." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "הכרטיס שלך מתאים. אנו מעבירים אותך עכשי msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "חברת הסלולר שלך מדווחת שהמספר הזה אינו פעיל. בדוק אותו ונסה שוב, או פנה לתמיכה אם הוא תקין." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "הקוד שלך" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "הקוד שלך מוכן" diff --git a/fluxer_app/src/features/i18n/locales/hi/messages.po b/fluxer_app/src/features/i18n/locales/hi/messages.po index 62cf8e51b..a119d0808 100644 --- a/fluxer_app/src/features/i18n/locales/hi/messages.po +++ b/fluxer_app/src/features/i18n/locales/hi/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# ब्लॉक किया गया मैस msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# कम्युनिटी मिली} other {# कम्युनिटी मिलीं}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# कम्युनिटी फ्लक्स कैपेसिटर में खराबी के कारण अस्थायी रूप से अनुपलब्ध है।} other {# कम्युनिटी फ्लक्स कैपेसिटर में खराबी के कारण अस्थायी रूप से अनुपलब्ध हैं।}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} ने इस कम्युनिटी में सं msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} ने संभावित रेड का पता लगाया है, इसलिए नए यूज़र अभी शामिल नहीं हो सकते।" +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} {host} पर चला गया है। इसे अपनी होम स्क्रीन या डॉक में जोड़ें, फिर इस ऐप से साइन इन करें।" + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} {host} पर चला गया है। इसे अपने ब्राउज़र से इंस्टॉल करें, फिर इस ऐप से साइन इन करें।" + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} {host} पर चला गया है। नया ऐप इंस्टॉल करें और आप पहले से ही साइन इन होंगे।" + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} {host} पर चला गया है। नया ऐप इंस्टॉल करने के लिए इसे अपने ब्राउज़र में खोलें।" + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} को आपके डिवाइस दिखाने msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} को स्क्रीन रिकॉर्डिंग का एक्सेस चाहिए। अपने ऑपरेटिंग सिस्टम की गोपनीयता सेटिंग्स में स्क्रीन रिकॉर्डिंग की अनुमति दें और ऐप को रीस्टार्ट करें।" +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} अब {host} पर उपलब्ध है। वहां से इसे इंस्टॉल करें, फिर इस ऐप से साइन इन करें।" + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} अब {host} पर उपलब्ध है। वहां से इसे इंस्टॉल करें।" + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "एक पोल" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "जब बॉट स्कोप ही एकमात्र स्कोप न हो, तो redirect_uri ज़रूरी है।" @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "मांगी गई अनुमतियों के साथ किसी कम्युनिटी में बॉट जोड़ना" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "कोई टिप्पणी जोड़ें (वैकल्पिक)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "YouTube लिंक को बेहतर बनाने के लिए YouTube Data API कुंजी जोड़ें।" #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "अकाउंट जोड़ें" @@ -4379,6 +4409,11 @@ msgstr "मंज़ूरी ज़रूरी" msgid "Approve" msgstr "स्वीकृत करें" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "उस ऐप से इस ऐप को मंज़ूरी दें जिसका आप पहले से इस्तेमाल कर रहे हैं, {productName}. पासवर्ड की ज़रूरत नहीं है।" + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "अधिकृत नहीं किया जा सका" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "अनुमति नहीं दी जा सकी। फिर से कोशिश करें।" @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "बॉट अनुमतियाँ" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "बॉट अनुमतियाँ ऋणात्मक नहीं हो सकतीं।" @@ -6329,10 +6364,10 @@ msgstr "यहां फ़ाइलें अपलोड नहीं की #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "यहां फ़ाइलें अपलोड नहीं की #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "बैकग्राउंड चुनें" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "guild_id या channel_id में से कोई एक चुनें, दोनों नहीं।" +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "फ़ाइल चुनें, फिर डॉक में जोड़ें।" + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "कोड ब्लॉक" msgid "Code blocks" msgstr "कोड ब्लॉक" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "कोड की समय-सीमा समाप्त हो गई" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "कमांड" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "टिप्पणियाँ बंद हैं क्योंकि एक या अधिक चुने गए चैनलों में स्लोमोड चालू है। फ़ॉरवर्ड के तुरंत बाद स्लोमोड टिप्पणी को ब्लॉक कर देगा।" #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "{forwardChannelDisplayName} में स्लोमोड चालू होने के कारण टिप्पणियाँ बंद हैं। फ़ॉरवर्ड के तुरंत बाद स्लोमोड टिप्पणी को ब्लॉक कर देगा।" #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "स्लोमोड चालू होने पर टिप्पणियाँ उपलब्ध नहीं हैं।" @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "कम्युनिटी मैसेज की सूचनाएँ" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "कम्युनिटी MFA आवश्यकता नागबार" @@ -9182,7 +9222,7 @@ msgstr "कम्युनिटी की रिपोर्ट करना msgid "Continue to report user" msgstr "यूज़र की रिपोर्ट करना जारी रखें" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "पासकी के साथ जारी रखें" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "लोगों या कम्युनिटी के ख़िलाफ़ रेड, ब्रिगेडिंग या उत्पीड़न का समन्वय करता है।" #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "कॉपी किया गया" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "चैनल लिंक कॉपी करें" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "कोड कॉपी करें" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "मौजूदा उपयोग:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "फ़िलहाल खारिज है" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "फ़िलहाल दिख रहा है" @@ -12555,6 +12595,11 @@ msgstr "ब्लॉक नहीं होगा। आप बाद में msgid "Domain" msgstr "डोमेन" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "डोमेन बदला गया" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "डच" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "जैसे: YubiKey, iPhone, ऑफ़िस का कंप्यूटर" @@ -14056,10 +14101,14 @@ msgstr "जारी रखने के लिए अपनी सुरक् msgid "Enter the SMS code" msgstr "एसएमएस कोड डालें" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "साइन-इन पूरा करने के लिए अपने ब्राउज़र में यह कोड डालें।" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "अपने साइन-इन को पूरा करने के लिए यह कोड अपने पुराने {PRODUCT_NAME} ऐप में दर्ज करें।" + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter दबाकर <0><1>सेव करें" @@ -14441,7 +14490,7 @@ msgstr "{absolute} को समाप्त होगा ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "{earliest} और {latest} के बीच समाप्त होगा" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "{remaining} सेकंड में समाप्त होगा" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "SSO शुरू नहीं हो पाया" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "अकाउंट नहीं बदला जा सका" @@ -15413,17 +15462,17 @@ msgstr "मेरे लिए" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "इस जाँच के लिए, आप हमसे मैसेज पाने के बजाय हमें एक टेक्स्ट मैसेज भेजेंगे।" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "आपकी सुरक्षा के लिए, आगे बढ़ने से पहले कृपया पुष्टि करें कि यह आप ही हैं।" #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "ज़बरदस्ती अक्षम किया गया" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "ज़बरदस्ती चालू किया गया" @@ -15540,7 +15589,7 @@ msgstr "पासवर्ड भूल गए" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "पासवर्ड भूल गए?" @@ -15571,7 +15620,7 @@ msgstr "मीडिया फ़ॉरवर्ड करें" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "मैसेज फ़ॉरवर्ड करें" @@ -15965,11 +16014,11 @@ msgstr "शेयर करने लायक लिंक जनरेट क msgid "Generate invite link" msgstr "इनवाइट लिंक जनरेट करें" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "नया कोड जनरेट करें" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "कोड जनरेट हो रहा है…" @@ -16045,6 +16094,11 @@ msgstr "सिस्टम-वाइड पुश-टू-टॉक और कु msgid "Get the desktop app" msgstr "डेस्कटॉप ऐप पाएँ" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "नया {productName} ऐप प्राप्त करें" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "इमोजी डालें" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "पूरे सोर्स पिकर और ज़्यादा स्मूद स्क्रीन-शेयरिंग कंट्रोल के लिए {productName} इंस्टॉल करें।" +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "अपने ब्राउज़र मेनू से इसे इंस्टॉल करें।" + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "नया ऐप इंस्टॉल करें" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "अमान्य ऑथराइज़ेशन अनुरोध" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "अमान्य बॉट अनुमतियाँ।" @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "कोड अमान्य है या उसकी समय-सीमा समाप्त हो चुकी है। फिर से कोशिश करें।" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "अमान्य redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "लिंक" msgid "Link (mature)" msgstr "लिंक (वयस्क)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "नया डिवाइस लिंक करें" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "मैसेज डिस्प्ले सेटिंग्स सि msgid "Message editing" msgstr "मैसेज एडिट करना" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "मैसेज फ़ॉरवर्ड किया गया" @@ -20746,7 +20815,7 @@ msgstr "मैसेज इनपुट बटन" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "मैसेज बहुत लंबा है" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "अन्य" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id मौजूद नहीं है" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "नाम कम से कम 2 वर्णों का होना चाहिए" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "पासकी को नाम दें" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "पासकी को नाम देने का फ़ॉर्म" @@ -22381,7 +22450,7 @@ msgstr "सेव करने के लिए कोई बदलाव नह msgid "No channels available" msgstr "कोई चैनल उपलब्ध नहीं है" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "कोई चैनल नहीं मिला" @@ -22824,7 +22893,7 @@ msgstr "कोई यूज़र नहीं मिला" msgid "No verification is required." msgstr "किसी वेरिफ़िकेशन की ज़रूरत नहीं है।" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "कोई वेरिफ़िकेशन तरीका उपलब्ध नहीं है" @@ -23417,6 +23486,16 @@ msgstr "{contextName} खोलें" msgid "Open {displayName}" msgstr "{displayName} खोलें" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Safari में {host} खोलें।" + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "अपने ब्राउज़र में {host} खोलें।" + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "{macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} → msgid "Open {productName}" msgstr "{productName} खोलें" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "{productName} को अपने ब्राउज़र में खोलें" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "बैनर विकल्प खोलें" msgid "Open bookmarks" msgstr "बुकमार्क खोलें" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "ब्राउज़र खोलें" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "ब्राउज़र में इमोजी खोलें" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "इमोजी पिकर खोलें" @@ -23809,6 +23893,16 @@ msgstr "नीचे दिए गए लिंक को अपने ब्र msgid "Open the menu and choose an option." msgstr "मेनू खोलें और एक विकल्प चुनें।" +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "नया ऐप खोलें और \"अपने पुराने {productName} ऐप से साइन इन करें\" चुनें। फिर \"यहां एक नया डिवाइस लिंक करें\" चुनें और जो कोड दिखाया गया है उसे दर्ज करें।" + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "नया ऐप खोलें। आप पहले से ही साइन इन हैं।" + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "वॉइस खोलें" msgid "Open voice & video settings" msgstr "वॉइस और वीडियो सेटिंग्स खोलें" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "अपना ब्राउज़र खोलें, साइन इन करें, फिर अपना अकाउंट लिंक करने के लिए नीचे दिया गया कोड डालें।" @@ -23868,6 +23962,10 @@ msgstr "अपना ब्राउज़र खोलें, साइन इ msgid "Open your messages" msgstr "अपने मैसेज खोलें" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "अपना पुराना {PRODUCT_NAME} ऐप खोलें और एक नया डिवाइस लिंक करें चुनें, फिर नीचे दिया गया कोड दर्ज करें।" + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "अपने फ़ोन का मैसेजिंग ऐप खोलें और एक नया टेक्स्ट मैसेज बनाएँ।" @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "पासकी" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "पासकी का नाम" @@ -28138,7 +28236,7 @@ msgstr "सैचुरेशन" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "स्क्रब प्रीव्यू" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "टेक्स्ट चुनें" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "वह अकाउंट चुनें जिससे आप डेस्कटॉप ऐप पर साइन इन करना चाहते हैं।" +msgid "Select the account you want to sign in with on your new device." +msgstr "अपने नए डिवाइस पर साइन इन करने के लिए वह खाता चुनें जिससे आप साइन इन करना चाहते हैं।" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "भेजें" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "भेजें ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "सेशन" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "{identifier} के लिए सेशन समाप्त हो गया है। फिर से साइन इन करें।" @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "रिकॉर्डिंग के दौरान कीबोर्ड शॉर्टकट रोक दिए जाते हैं।" #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "मैसेज छोटा करें और फिर से कोशिश करें।" @@ -30217,6 +30315,11 @@ msgstr "अधिकतम 3 दिखाएं (+5 बैज)" msgid "Show max 5 (+5 badge)" msgstr "अधिकतम 5 दिखाएं (+5 बैज)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "मुझे दिखाएँ" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "अपना उपहार पाने के लिए साइन #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "ब्राउज़र से साइन इन करें" @@ -30496,6 +30599,11 @@ msgstr "ब्राउज़र से साइन इन करें" msgid "Sign in with a passkey" msgstr "पासकी से साइन इन करें" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "अपने पुराने {productName} ऐप से साइन इन करें" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "साइन-इन विवरण" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "साइन-इन फ़्लो" @@ -31030,7 +31138,7 @@ msgstr "कुछ यूज़र को जोड़ा नहीं जा स #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "इस वॉइस चैनल में बोलें। इस अ msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "वॉइस चैनलों में बोलें। इस अनुमति के बिना, सदस्य तब तक म्यूट रहते हैं जब तक \"सदस्यों को म्यूट करें\" अनुमति वाला कोई व्यक्ति उन्हें अनम्यूट न कर दे।" +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "शेयर पर टैप करें, फिर होम स्क्रीन पर जोड़ें।" + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "फ़ोन जाँच को अलग रख दिया गया है। एक ईमेल चरण बचा है।" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "दिया गया redirect_uri इस ऐप्लिकेशन के लिए रजिस्टर नहीं है।" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "दिए गए redirect_uri में HTTP या HTTPS का इस्तेमाल होना चाहिए।" @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "यह एनीमेशन क्रॉप करने के लिए बहुत लंबा या बहुत बड़ा है। एक छोटी या हल्की फ़ाइल आज़माएँ।" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "यह ऐप्लिकेशन" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "इस अटैचमेंट की समय-सीमा खत्म हो गई है" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "यह बॉट सार्वजनिक नहीं है और इसे दूसरे यूज़र नहीं जोड़ सकते।" @@ -33757,7 +33870,7 @@ msgstr "इस ग्रुप में बहुत ज़्यादा ल msgid "This group is full. Remove someone before adding more people." msgstr "यह ग्रुप भरा हुआ है। और लोगों को जोड़ने से पहले किसी को हटाएँ।" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "यह हैंडऑफ़ कोड समाप्त हो गया है। जारी रखने के लिए एक नया कोड जनरेट करें।" @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "अज्ञात" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "अज्ञात ऐप्लिकेशन, समाप्त हो चुका सेशन या अमान्य ऑथराइज़ेशन अनुरोध।" @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "उपयोग:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "वास्तविक अकाउंट स्थिति का उपयोग किया जा रहा है" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "उपहार इन्वेंट्री की वास्तविक स्थिति का उपयोग किया जा रहा है" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "वास्तविक प्रीमियम स्थिति का उपयोग किया जा रहा है" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "वास्तविक स्थिति का उपयोग किया जा रहा है" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "वास्तविक वेरिफ़िकेशन स्थिति का उपयोग किया जा रहा है" @@ -37947,17 +38060,17 @@ msgstr "यहां इंतज़ार करें" msgid "Waiting for connection..." msgstr "कनेक्शन का इंतज़ार हो रहा है..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "पासकी का इंतज़ार हो रहा है…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "{forwardChannelDisplayName} में स्लोमोड के खत्म होने का इंतज़ार किया जा रहा है।" #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "एक या ज़्यादा चुने गए चैनलों में स्लोमोड के खत्म होने का इंतज़ार किया जा रहा है।" @@ -38156,7 +38269,7 @@ msgstr "हमने इस स्टिकर का आकार बदलक msgid "We sent a code to {email}." msgstr "हमने {email} पर एक कोड भेजा है।" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "ब्राउज़र साइन-इन खुलने पर हम {prefillEmail} पहले से भर देंगे।" @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "वेबसाइट प्रीव्यू" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "वापस आपका स्वागत है" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "आपकी {premiumProductName} सदस्यता समाप्त हो गई है। आपने सभी {premiumProductName} पर्क खो दिए हैं। एक्सेस फिर से पाने के लिए अपनी सदस्यता दोबारा सक्रिय करें।" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "आपका {productName} सेशन समाप्त हो गया है। इस ऐप्लिकेशन को अधिकृत करना जारी रखने के लिए फिर से साइन इन करें।" @@ -39734,7 +39847,7 @@ msgstr "इस कम्युनिटी में मैसेज भेज msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "आपके अकाउंट का अनुरोध मंज़ूरी के इंतज़ार में है। एडमिन के मंज़ूर करने के बाद आप साइन इन कर सकेंगे।" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "आपके अकाउंट के लिए मल्टी-फ़ैक्टर प्रमाणीकरण ज़रूरी है, लेकिन इस डिवाइस पर कोई भी समर्थित तरीका सेट अप नहीं है। अपने ऑथेंटिकेटर ऐप या सुरक्षा कुंजी वाले डिवाइस पर साइन इन करें या सहायता से संपर्क करें।" @@ -39757,6 +39870,11 @@ msgstr "आपकी कार्रवाई पूरी नहीं हो msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "आपका आवेदन समीक्षा के लिए लंबित है। आप अभी भी अपनी लिस्टिंग का विवरण अपडेट कर सकते हैं या आवेदन वापस ले सकते हैं।" +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "आपका ब्राउज़र इस डोमेन पर पासकी का समर्थन नहीं करता है। अपना ब्राउज़र अपडेट करें, या अपने पासवर्ड और दो-कारक कोड से साइन इन करें।" + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "आपका कार्ड योग्य है। आपको अ msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "आपका कैरियर कह रहा है कि यह नंबर सेवा में नहीं है। इसे जाँचें और फिर से कोशिश करें, या अगर यह सही है तो सहायता से संपर्क करें।" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "आपका कोड" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "आपका कोड तैयार है" diff --git a/fluxer_app/src/features/i18n/locales/hr/messages.po b/fluxer_app/src/features/i18n/locales/hr/messages.po index 7c5181f98..b7afec655 100644 --- a/fluxer_app/src/features/i18n/locales/hr/messages.po +++ b/fluxer_app/src/features/i18n/locales/hr/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# blokirana poruka} few {# blokirane poruke} other msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# zajednica pronađena} few {# zajednice pronađene} other {# zajednica pronađeno}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# zajednica privremeno nije dostupna zbog kvara na protočnom kondenzatoru.} few {# zajednice privremeno nisu dostupne zbog kvara na protočnom kondenzatoru.} other {# zajednica privremeno nije dostupno zbog kvara na protočnom kondenzatoru.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} je otkrio potencijalni napad u ovoj zajednici. Pozivnice s msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} je otkrio potencijalni napad, pa se novi korisnici trenutačno ne mogu pridružiti." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} se preselio na {host}. Dodajte ga na početni zaslon ili u Dock, a zatim se prijavite ovom aplikacijom." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} se preselio na {host}. Instalirajte ga iz preglednika, a zatim se prijavite ovom aplikacijom." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} se preselio na {host}. Instaliraj novu aplikaciju i automatski ćeš biti prijavljen." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} se preselio na {host}. Otvorite ga u pregledniku kako biste instalirali novu aplikaciju." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "Aplikacija {productName} treba pristup mikrofonu za prikaz vaših uređa msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "Aplikaciji {productName} potreban je pristup snimanju zaslona. Dopustite snimanje zaslona u postavkama privatnosti operativnog sustava i ponovno pokrenite aplikaciju." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} se sada nalazi na adresi {host}. Instalirajte ga od tamo, a zatim se prijavite ovom aplikacijom." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} se sada nalazi na adresi {host}. Instalirajte ga od tamo." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "anketa" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri je obavezan kada opseg bot nije jedini opseg." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Dodajte bota zajednici s traženim dopuštenjima" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Dodaj komentar (neobavezno)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Dodajte YouTube Data API ključ kako biste obogatili YouTube poveznice." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Dodaj račun" @@ -4379,6 +4409,11 @@ msgstr "Zahtijeva odobrenje" msgid "Approve" msgstr "Odobri" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Odobri ovu aplikaciju iz {productName} aplikacije koju već koristiš. Lozinka nije potrebna." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autorizacija nije uspjela" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorizacija nije uspjela. Pokušajte ponovno." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Dopuštenja bota" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Dopuštenja bota ne mogu biti negativna." @@ -6329,10 +6364,10 @@ msgstr "Ovdje nije moguće učitati datoteke" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Ovdje nije moguće učitati datoteke" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Odaberite pozadinu" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Odaberite guild_id ili channel_id, ne oboje." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Odaberite Datoteka, zatim Dodaj u Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "blok koda" msgid "Code blocks" msgstr "Blokovi koda" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Kod je istekao" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Naredbe" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Komentari su onemogućeni jer je na jednom ili više odabranih kanala uključen usporeni način rada. Komentar bi odmah nakon prosljeđivanja bio blokiran usporenim načinom rada." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Komentari su onemogućeni jer je u kanalu {forwardChannelDisplayName} uključen usporeni način rada. Komentar bi odmah nakon prosljeđivanja bio blokiran usporenim načinom rada." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Komentari su nedostupni dok je uključen usporeni način rada." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Obavijesti o porukama u zajednici" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Traka za obveznu MFA u zajednici" @@ -9182,7 +9222,7 @@ msgstr "Nastavi s prijavom zajednice" msgid "Continue to report user" msgstr "Nastavi s prijavom korisnika" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Nastavi s pristupnim ključem" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Koordinira napade, hajke ili uznemiravanje ljudi ili zajednica." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Kopirano" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kopiraj poveznicu kanala" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kopiraj kod" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Trenutni broj korištenja:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Trenutačno odbačeno" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Trenutačno prikazano" @@ -12555,6 +12595,11 @@ msgstr "Ne blokira. Možete ga kasnije ponovno otvoriti." msgid "Domain" msgstr "Domena" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Obavijest o premještanju domene" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Nizozemski" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "npr. YubiKey, iPhone, poslovno računalo" @@ -14056,10 +14101,14 @@ msgstr "Unesite PIN za svoj sigurnosni ključ kako biste nastavili." msgid "Enter the SMS code" msgstr "Unesite SMS kod" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Unesite ovaj kôd u preglednik kako biste dovršili prijavu." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Unesite ovaj kod u svoju staru {PRODUCT_NAME} aplikaciju kako biste dovršili prijavu." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter za <0><1>spremanje" @@ -14441,7 +14490,7 @@ msgstr "Istječe {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Istječe između {earliest} i {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Istječe za {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Pokretanje SSO-a nije uspjelo" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Prebacivanje računa nije uspjelo" @@ -15413,17 +15462,17 @@ msgstr "Za mene" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Za ovu provjeru vi nama šaljete tekstualnu poruku umjesto da je primite od nas." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Radi vaše sigurnosti, potvrdite svoj identitet za nastavak." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Prisilno onemogućeno" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Prisilno omogućeno" @@ -15540,7 +15589,7 @@ msgstr "Zaboravljena lozinka" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Zaboravili ste lozinku?" @@ -15571,7 +15620,7 @@ msgstr "Proslijedi medij" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Proslijedi poruku" @@ -15965,11 +16014,11 @@ msgstr "Generirajte poveznicu za dijeljenje ili kopirajte svoj kombinirani prila msgid "Generate invite link" msgstr "Generiraj poveznicu za pozivnicu" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generiraj novi kôd" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generiranje koda…" @@ -16045,6 +16094,11 @@ msgstr "Preuzmite desktop aplikaciju {productName} za značajku pritisni za govo msgid "Get the desktop app" msgstr "Preuzmite aplikaciju za računalo" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Preuzmi novu aplikaciju {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Umetni emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Instalirajte {productName} za potpuni birač izvora i uglađenije kontrole dijeljenja zaslona." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Instalirajte ga iz izbornika preglednika." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Instaliraj novu aplikaciju" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Nevažeći zahtjev za autorizaciju" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Nevažeća dopuštenja bota." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Nevažeći ili istekli kod. Pokušajte ponovno." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Nevažeći redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Poveznica" msgid "Link (mature)" msgstr "Poveznica (za odrasle)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Poveži novi uređaj" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Postavke prikaza poruka dostupne su samo na računalu." msgid "Message editing" msgstr "Uređivanje poruka" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Poruka proslijeđena" @@ -20746,7 +20815,7 @@ msgstr "Gumbi za unos poruke" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Poruka je predugačka" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Razno" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Nedostaje client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Naziv mora imati najmanje 2 znaka" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Imenujte pristupni ključ" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Obrazac za imenovanje pristupnog ključa" @@ -22381,7 +22450,7 @@ msgstr "Nema promjena za spremanje" msgid "No channels available" msgstr "Nema dostupnih kanala" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Nema pronađenih kanala" @@ -22824,7 +22893,7 @@ msgstr "Nema pronađenih korisnika" msgid "No verification is required." msgstr "Provjera nije potrebna." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Nema dostupnih metoda provjere" @@ -23417,6 +23486,16 @@ msgstr "Otvori {contextName}" msgid "Open {displayName}" msgstr "Otvori {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Otvori {host} u Safariju." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Otvorite {host} u svojem pregledniku." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Otvorite {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsN msgid "Open {productName}" msgstr "Otvori {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Otvori {productName} u svom pregledniku" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Otvori opcije naslovne slike" msgid "Open bookmarks" msgstr "Otvori oznake" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Otvori preglednik" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Otvori emoji u pregledniku" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Otvori birač emojija" @@ -23809,6 +23893,16 @@ msgstr "Otvorite donju poveznicu u pregledniku, zatim kopirajte cijeli JSON odgo msgid "Open the menu and choose an option." msgstr "Otvorite izbornik i odaberite opciju." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Otvorite novu aplikaciju i odaberite \"Prijavi se sa svojom starom aplikacijom {productName}\". Zatim odaberite \"Poveži novi uređaj ovdje\" i unesite prikazani kod." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Otvorite novu aplikaciju. Već ste prijavljeni." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Otvori glasovni chat" msgid "Open voice & video settings" msgstr "Otvori postavke zvuka i videa" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Otvorite preglednik, prijavite se, a zatim unesite kôd u nastavku za povezivanje računa." @@ -23868,6 +23962,10 @@ msgstr "Otvorite preglednik, prijavite se, a zatim unesite kôd u nastavku za po msgid "Open your messages" msgstr "Otvorite poruke" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Otvorite svoju staru {PRODUCT_NAME} aplikaciju i odaberite Poveži novi uređaj, a zatim unesite kod ispod." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Otvorite aplikaciju za poruke na svom telefonu i stvorite novu tekstualnu poruku." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Pristupni ključ" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Naziv pristupnog ključa" @@ -28138,7 +28236,7 @@ msgstr "Zasićenost" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Pregled pri pomicanju" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Odabir teksta" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Odaberite račun kojim se želite prijaviti u aplikaciju za stolna računala." +msgid "Select the account you want to sign in with on your new device." +msgstr "Odaberite račun s kojim se želite prijaviti na novom uređaju." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Pošalji" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Pošalji ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sesija" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sesija za {identifier} je istekla. Prijavite se ponovno." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Tipkovnički prečaci pauziraju se tijekom snimanja." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Skratite poruku i pokušajte ponovno." @@ -30217,6 +30315,11 @@ msgstr "Prikaži najviše 3 (značka +5)" msgid "Show max 5 (+5 badge)" msgstr "Prikaži najviše 5 (značka +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Pokaži mi kako" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Prijavite se kako biste preuzeli svoj poklon" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Prijavite se putem preglednika" @@ -30496,6 +30599,11 @@ msgstr "Prijavite se putem preglednika" msgid "Sign in with a passkey" msgstr "Prijavite se s pristupnim ključem" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Prijavi se sa svojom starom {productName} aplikacijom" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Podaci za prijavu" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Tijek prijave" @@ -31030,7 +31138,7 @@ msgstr "Neke korisnike nije moguće dodati" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Govori u ovom glasovnom kanalu. Bez ovog dopuštenja članovi ostaju uti msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Govori u glasovnim kanalima. Bez ovog dopuštenja članovi ostaju utišani dok im netko s dopuštenjem \"Utišaj članove\" ne poništi utišavanje." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Dodirnite Dijeli, a zatim Dodaj na početni zaslon." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Telefonska provjera je odgođena. Ostao je još jedan korak s e-poštom." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Navedeni redirect_uri nije registriran za ovu aplikaciju." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Navedeni redirect_uri mora koristiti HTTP ili HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ova animacija je predugačka ili prevelika za obrezivanje. Isprobajte kraću ili manju datoteku." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Ova aplikacija" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Ovaj je privitak istekao" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Ovaj bot nije javan i drugi ga korisnici ne mogu dodati." @@ -33757,7 +33870,7 @@ msgstr "Ova grupa ima previše ljudi" msgid "This group is full. Remove someone before adding more people." msgstr "Ova je grupa puna. Uklonite nekoga prije dodavanja novih osoba." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Ovaj je kod za prijenos istekao. Generirajte novi za nastavak." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Nepoznato" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Nepoznata aplikacija, istekla sesija ili nevažeći zahtjev za autorizaciju." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Broj korištenja:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Koristi se stvarno stanje računa" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Koristi se stvarno stanje inventara poklona" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Koristi se stvarno premium stanje" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Koristi se stvarno stanje" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Koristi se stvarno stanje provjere" @@ -37947,17 +38060,17 @@ msgstr "Pričekajte ovdje" msgid "Waiting for connection..." msgstr "Čekanje veze..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Čekanje pristupnog ključa…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Čeka se istek usporenog načina rada u kanalu {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Čeka se istek usporenog načina rada u jednom ili više odabranih kanala." @@ -38156,7 +38269,7 @@ msgstr "Promijenili smo veličinu i komprimirali ovu naljepnicu na 320x320 pikse msgid "We sent a code to {email}." msgstr "Poslali smo kôd na adresu {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Automatski ćemo popuniti {prefillEmail} kada se otvori prijava u pregledniku." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Pregledi web-stranica" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Dobro došli natrag" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Vaša pretplata na {premiumProductName} je istekla. Izgubili ste sve pogodnosti {premiumProductName}. Ponovno aktivirajte pretplatu kako biste ponovno dobili pristup." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Vaša je sesija za {productName} istekla. Prijavite se ponovno kako biste nastavili s autorizacijom ove aplikacije." @@ -39734,7 +39847,7 @@ msgstr "Vaš je račun prenov da biste slali poruke u ovoj zajednici." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Vaš zahtjev za račun čeka odobrenje. Možete se prijaviti nakon što ga administrator odobri." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Vaš račun zahtijeva višestruku provjeru autentičnosti, ali na ovom uređaju nisu postavljene podržane metode. Prijavite se na uređaju s aplikacijom za provjeru autentičnosti ili sigurnosnim ključem ili se obratite podršci." @@ -39757,6 +39870,11 @@ msgstr "Vaša radnja nije mogla biti dovršena jer ju je naš sigurnosni sustav msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Vaša prijava čeka pregled. I dalje možete ažurirati pojedinosti unosa ili povući prijavu." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Vaš preglednik ne podržava passkey na ovoj domeni. Ažurirajte preglednik ili se prijavite svojom lozinkom i dvofaktorskim kodom." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Vaša kartica ispunjava uvjete. Preusmjeravamo vas na stranicu za plaća msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Vaš operater kaže da taj broj nije u upotrebi. Provjerite ga i pokušajte ponovno, ili se obratite podršci ako je ispravan." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Vaš kod" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Vaš kod je spreman" diff --git a/fluxer_app/src/features/i18n/locales/hu/messages.po b/fluxer_app/src/features/i18n/locales/hu/messages.po index 2cb6916ac..fba8dfbf0 100644 --- a/fluxer_app/src/features/i18n/locales/hu/messages.po +++ b/fluxer_app/src/features/i18n/locales/hu/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# letiltott üzenet} other {# letiltott üzenet}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# közösség található} other {# közösség található}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# közösség átmenetileg nem elérhető egy „fluxuskondenzátor” meghibásodása miatt.} other {# közösség átmenetileg nem elérhető egy „fluxuskondenzátor” meghibásodása miatt.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} potenciális támadást észlelt ebben a közösségben. A msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} potenciális támadást észlelt, ezért az új felhasználók most nem csatlakozhatnak." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "A(z) {productName} ide költözött: {host}. Add hozzá a Főképernyőhöz vagy az Dockhoz, majd jelentkezz be ezzel az alkalmazással." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "A(z) {productName} ide költözött: {host}. Telepítsd a böngésződből, majd jelentkezz be ezzel az alkalmazással." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "A(z) {productName} átkerült ide: {host}. Telepítsd az új alkalmazást, és már be is leszel jelentkezve." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "A(z) {productName} ide költözött: {host}. Nyissa meg a böngészőben az új alkalmazás telepítéséhez." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "A(z) {productName} mikrofon-hozzáférést igényel az eszközök listá msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "A(z) {productName} képernyőrögzítési hozzáférést igényel. Engedélyezd a képernyőrögzítést az operációs rendszer adatvédelmi beállításaiban, majd indítsd újra az alkalmazást." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "A(z) {productName} mostantól a(z) {host} címen érhető el. Telepítsd onnan, majd jelentkezz be ezzel az alkalmazással." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "A(z) {productName} mostantól itt érhető el: {host}. Telepítsd onnan." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "szavazás" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "A redirect_uri megadása kötelező, ha nem a „bot” az egyetlen kért hatókör." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Bot hozzáadása egy közösséghez a kért jogosultságokkal" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Megjegyzés hozzáadása (opcionális)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Adj hozzá egy YouTube Data API-kulcsot a YouTube-hivatkozások gazdagításához." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Fiók hozzáadása" @@ -4379,6 +4409,11 @@ msgstr "Jóváhagyás szükséges" msgid "Approve" msgstr "Jóváhagyás" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Jóváhagytad ezt az alkalmazást a már használt {productName} alkalmazásból. Nincs szükség jelszóra." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Sikertelen engedélyezés" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Az engedélyezés sikertelen. Próbáld újra." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Botjogosultságok" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "A bot jogosultságainak értéke nem lehet negatív." @@ -6329,10 +6364,10 @@ msgstr "Ide nem tölthetsz fel fájlokat" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Ide nem tölthetsz fel fájlokat" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Háttér kiválasztása" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Vagy a guild_id, vagy a channel_id mezőt add meg, ne mindkettőt." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Válaszd a Fájl menüpontot, majd az Indítóhoz adás parancsot." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "kódblokk" msgid "Code blocks" msgstr "Kódblokkok" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "A kód lejárt" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Parancsok" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Nem adhatsz hozzá megjegyzést, mert legalább egy kiválasztott csatornában be van kapcsolva a lassú mód. A lassú mód közvetlenül a továbbítás után megakadályozná a megjegyzés elküldését." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Nem adhatsz hozzá megjegyzést, mert a(z) {forwardChannelDisplayName} csatornában be van kapcsolva a lassú mód. A lassú mód közvetlenül a továbbítás után megakadályozná a megjegyzés elküldését." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Bekapcsolt lassú mód mellett nem adhatsz hozzá megjegyzést." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Közösségi üzenetek értesítései" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Közösségi kétlépcsős azonosítást kérő sáv" @@ -9182,7 +9222,7 @@ msgstr "Tovább a közösség bejelentéséhez" msgid "Continue to report user" msgstr "Tovább a felhasználó bejelentéséhez" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Folytatás jelszókulccsal" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Személyek vagy közösségek ellen szervez összehangolt támadásokat, tömeges fellépést vagy zaklatást." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Másolva" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Csatornalink másolása" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kód másolása" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Jelenlegi használatok:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Jelenleg elvetve" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Jelenleg látható" @@ -12555,6 +12595,11 @@ msgstr "Nem tiltja le. Később újra megnyithatod." msgid "Domain" msgstr "Domain" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domain áthelyezve értesítősáv" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Holland" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "pl. YubiKey, iPhone, munkahelyi számítógép" @@ -14056,10 +14101,14 @@ msgstr "A folytatáshoz add meg a biztonsági kulcsod PIN-kódját." msgid "Enter the SMS code" msgstr "Add meg az SMS-kódot" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Írd be ezt a kódot a böngésződbe a bejelentkezés befejezéséhez." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Írd be ezt a kódot a régi {PRODUCT_NAME} alkalmazásodba a bejelentkezés befejezéséhez." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter a <0><1>mentéshez" @@ -14441,7 +14490,7 @@ msgstr "Lejár ekkor: {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Lejárati idő: {earliest} és {latest} között" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Lejár {remaining} mp múlva" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Nem sikerült elindítani az egységes bejelentkezést (SSO)" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Fiókváltás sikertelen" @@ -15413,17 +15462,17 @@ msgstr "Nekem" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Ehhez az ellenőrzéshez te küldesz nekünk SMS-t ahelyett, hogy tőlünk kapnál egyet." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "A biztonságod érdekében kérjük, erősítsd meg, hogy te vagy az, mielőtt folytatnád." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Kényszerített letiltás" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Kényszerített engedélyezés" @@ -15540,7 +15589,7 @@ msgstr "Elfelejtett jelszó" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Elfelejtetted a jelszavad?" @@ -15571,7 +15620,7 @@ msgstr "Média továbbítása" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Üzenet továbbítása" @@ -15965,11 +16014,11 @@ msgstr "Megosztható hivatkozás generálása vagy az egyesített egyéni CSS m msgid "Generate invite link" msgstr "Meghívólink generálása" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Új kód generálása" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Kód generálása…" @@ -16045,6 +16094,11 @@ msgstr "Töltsd le a(z) {productName} asztali alkalmazást a rendszerszintű ad msgid "Get the desktop app" msgstr "Töltsd le az asztali alkalmazást" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Töltsd le az új {productName} alkalmazást" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Emoji beszúrása" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Telepítsd a(z) {productName} alkalmazást a teljes forrásválasztó és a gördülékenyebb képernyőmegosztási vezérlők használatához." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Telepítsd a böngésző menüjéből." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Telepítsd az új alkalmazást" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Érvénytelen engedélyezési kérelem" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Érvénytelen botjogosultságok." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Érvénytelen vagy lejárt kód. Próbáld újra." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Érvénytelen redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (felnőtt tartalom)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Új eszköz csatolása" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Az üzenetmegjelenítési beállítások csak asztali gépen érhetők e msgid "Message editing" msgstr "Üzenet szerkesztése" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Üzenet továbbítva" @@ -20746,7 +20815,7 @@ msgstr "Üzenetbeviteli gombok" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Az üzenet túl hosszú" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Egyéb" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Hiányzó client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "A névnek legalább 2 karakter hosszúnak kell lennie" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Jelszókulcs elnevezése" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Jelszókulcs elnevezése – űrlap" @@ -22381,7 +22450,7 @@ msgstr "Nincs menthető változás" msgid "No channels available" msgstr "Nincsenek elérhető csatornák" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Nem található csatorna" @@ -22824,7 +22893,7 @@ msgstr "Nem található felhasználó" msgid "No verification is required." msgstr "Nincs szükség ellenőrzésre." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Nincs elérhető ellenőrzési módszer" @@ -23417,6 +23486,16 @@ msgstr "{contextName} megnyitása" msgid "Open {displayName}" msgstr "{displayName} megnyitása" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Nyisd meg a(z) {host} webhelyet a Szafariban." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Nyissa meg a böngészőjében a(z) {host} címet." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Nyisd meg a(z) {macosSystemSettingsName} → {macosPrivacyAndSecuritySet msgid "Open {productName}" msgstr "A(z) {productName} megnyitása" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Megnyitás a böngészőben: {productName}" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Borítókép beállításainak megnyitása" msgid "Open bookmarks" msgstr "Mentett üzenetek megnyitása" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Böngésző megnyitása" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Emoji megnyitása böngészőben" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Emojiválasztó megnyitása" @@ -23809,6 +23893,16 @@ msgstr "Nyisd meg az alábbi linket a böngésződben, majd másold ki a teljes msgid "Open the menu and choose an option." msgstr "Nyisd meg a menüt, és válassz egy lehetőséget." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Nyisd meg az új alkalmazást, és válaszd a Bejelentkezés a régi {productName} alkalmazásoddal lehetőséget. Ezután válaszd az Új eszköz csatolása itt lehetőséget, és add meg a megjelenő kódot." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Nyisd meg az új alkalmazást. Már be vagy jelentkezve." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Hangbeszélgetés megnyitása" msgid "Open voice & video settings" msgstr "Hang és videó beállítások megnyitása" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Nyisd meg a böngésződet, jelentkezz be, majd add meg az alábbi kódot a fiókod összekapcsolásához." @@ -23868,6 +23962,10 @@ msgstr "Nyisd meg a böngésződet, jelentkezz be, majd add meg az alábbi kódo msgid "Open your messages" msgstr "Nyisd meg az üzeneteidet" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Nyisd meg a régi {PRODUCT_NAME} alkalmazásodat, válaszd az „Új eszköz csatolása” lehetőséget, majd add meg az alábbi kódot." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Nyisd meg a telefonod üzenetküldő alkalmazását, és hozz létre egy új szöveges üzenetet." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Jelszókulcs" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Jelszókulcs neve" @@ -28138,7 +28236,7 @@ msgstr "Telítettség" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Előnézet tekerés közben" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Szöveg kijelölése" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Válaszd ki azt a fiókot, amellyel be szeretnél jelentkezni az asztali alkalmazásba." +msgid "Select the account you want to sign in with on your new device." +msgstr "Válaszd ki a fiókot, amellyel be szeretnél jelentkezni az új eszközödön." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Küldés" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Küldés ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Munkamenet" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "A(z) {identifier} munkamenete lejárt. Jelentkezz be újra." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "A billentyűparancsok a rögzítés idejére szünetelnek." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Rövidítsd le az üzenetet, és próbáld újra." @@ -30217,6 +30315,11 @@ msgstr "Legfeljebb 3 (+5 jelvény) megjelenítése" msgid "Show max 5 (+5 badge)" msgstr "Legfeljebb 5 megjelenítése (+5 jelvény)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Mutasd meg" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Jelentkezz be az ajándékodért" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Bejelentkezés böngészőn keresztül" @@ -30496,6 +30599,11 @@ msgstr "Bejelentkezés böngészőn keresztül" msgid "Sign in with a passkey" msgstr "Bejelentkezés jelszókulccsal" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Jelentkezz be a régi {productName} alkalmazásoddal" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Bejelentkezési adatok" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Bejelentkezési folyamat" @@ -31030,7 +31138,7 @@ msgstr "Néhány felhasználó nem adható hozzá" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Beszéd ebben a hangcsatornában. E jogosultság nélkül a tagok mikrof msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Beszéd a hangcsatornákban. E jogosultság nélkül a tagok mikrofonja némítva marad, amíg valaki a „Tagok mikrofonjának némítása” jogosultsággal fel nem oldja a némítást." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Érintsd meg a Megosztás lehetőséget, majd az Ugrás a kezdőképernyőre elemet." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "A telefonos ellenőrzést félretetted. Még egy e-mailes lépés van hátra." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "A megadott redirect_uri nincs regisztrálva ehhez az alkalmazáshoz." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "A megadott redirect_uri-nak HTTP vagy HTTPS protokollt kell használnia." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ez az animáció túl hosszú vagy túl nagy a vágáshoz. Próbálkozz rövidebb vagy kisebb fájllal." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Ez az alkalmazás" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Ez a melléklet lejárt" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Ez a bot nem nyilvános, és más felhasználók nem adhatják hozzá." @@ -33757,7 +33870,7 @@ msgstr "Túl sokan vannak ebben a csoportban" msgid "This group is full. Remove someone before adding more people." msgstr "Ez a csoport megtelt. Távolíts el valakit, mielőtt további személyeket adnál hozzá." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Ez az átadási kód lejárt. A folytatáshoz hozz létre újat." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Ismeretlen" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Ismeretlen alkalmazás, lejárt munkamenet vagy érvénytelen engedélyezési kérelem." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Használat:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Valós fiókállapot használata" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Valós ajándékállapot használata" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Tényleges prémium állapot használata" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Valós állapot használata" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Tényleges ellenőrzési állapot használata" @@ -37947,17 +38060,17 @@ msgstr "Várj itt" msgid "Waiting for connection..." msgstr "Kapcsolódásra vár..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Várakozás a jelszókulcsra…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Várakozás, amíg a lassú mód lejár a következő csatornán: {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Várakozás, amíg a lassú mód lejár egy vagy több kiválasztott csatornán." @@ -38156,7 +38269,7 @@ msgstr "Átméreteztük és tömörítettük ezt a matricát 320x320 képpontra, msgid "We sent a code to {email}." msgstr "Kódot küldtünk a következő címre: {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Előre kitöltjük a(z) {prefillEmail} címet, amint megnyílik a böngészős bejelentkezés." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Webhelyelőnézetek" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Üdv újra" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Lejárt a(z) {premiumProductName} előfizetésed. Elvesztetted a(z) {premiumProductName} összes előnyét. Aktiváld újra az előfizetésedet a hozzáférés visszaszerzéséhez." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Lejárt a(z) {productName} munkameneted. Jelentkezz be újra, hogy folytathasd az alkalmazás engedélyezését." @@ -39734,7 +39847,7 @@ msgstr "A fiókod túl új ahhoz, hogy üzeneteket küldj ebben a közösségben msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "A fiókkérelmed jóváhagyásra vár. Bejelentkezhetsz, miután egy admin jóváhagyta." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "A fiókodhoz többlépcsős azonosítás szükséges, de ezen az eszközön nincs beállítva támogatott módszer. Jelentkezz be egy olyan eszközön, amelyen elérhető a hitelesítő alkalmazásod vagy a biztonsági kulcsod, vagy lépj kapcsolatba az ügyfélszolgálattal." @@ -39757,6 +39870,11 @@ msgstr "A műveletet nem sikerült végrehajtani, mert a biztonsági rendszerein msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "A jelentkezésed felülvizsgálatra vár. Addig is frissítheted a bejegyzésed adatait, vagy visszavonhatod a jelentkezést." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Böngésződ nem támogatja a jelszókulcsokat ezen a domainen. Frissítsd a böngésződet, vagy jelentkezz be jelszavaddal és kétfaktoros kódoddal." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "A kártyád megfelel a feltételeknek. Most átirányítunk a fizetési msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "A szolgáltatód szerint ez a szám nem aktív. Ellenőrizd, és próbáld újra, vagy vedd fel a kapcsolatot az ügyfélszolgálattal, ha a szám helyes." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "A kódod" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Elkészült a kódod" diff --git a/fluxer_app/src/features/i18n/locales/id/messages.po b/fluxer_app/src/features/i18n/locales/id/messages.po index b09ce8b58..06e102ca5 100644 --- a/fluxer_app/src/features/i18n/locales/id/messages.po +++ b/fluxer_app/src/features/i18n/locales/id/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# pesan diblokir} other {# pesan diblokir}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# komunitas ditemukan} other {# komunitas ditemukan}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# komunitas sementara tidak tersedia karena kerusakan kapasitor fluks.} other {# komunitas sementara tidak tersedia karena kerusakan kapasitor fluks.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} mendeteksi potensi serangan di komunitas ini. Undangan dij msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} mendeteksi potensi serangan, jadi pengguna baru tidak bisa bergabung sekarang." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} telah pindah ke {host}. Tambahkan ke Layar Utama atau Dock Anda, lalu masuk dengan aplikasi ini." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} telah pindah ke {host}. Instal dari peramban Anda, lalu masuk dengan aplikasi ini." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} telah pindah ke {host}. Instal aplikasi baru dan Anda akan langsung masuk." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} telah pindah ke {host}. Buka di browser Anda untuk menginstal aplikasi baru." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} memerlukan akses mikrofon untuk menampilkan daftar perangk msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} memerlukan akses perekaman layar. Izinkan perekaman layar di pengaturan privasi sistem operasimu dan mulai ulang aplikasi." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} kini berada di {host}. Instal dari sana, lalu masuk dengan aplikasi ini." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} kini berada di {host}. Instal dari sana." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "polling" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri diperlukan jika cakupan bot bukan satu-satunya cakupan." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Tambahkan bot ke komunitas dengan izin yang diminta" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Tambah komentar (opsional)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Tambahkan kunci API Data YouTube untuk memperkaya tautan YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Tambah akun" @@ -4379,6 +4409,11 @@ msgstr "Persetujuan diperlukan" msgid "Approve" msgstr "Setujui" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Setujui aplikasi ini dari aplikasi {productName} yang sudah Anda gunakan. Tidak perlu kata sandi." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Otorisasi gagal" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Otorisasi gagal. Coba lagi." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Izin bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Izin bot tidak boleh negatif." @@ -6329,10 +6364,10 @@ msgstr "Tidak bisa mengunggah file di sini" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Tidak bisa mengunggah file di sini" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Pilih latar belakang" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Pilih guild_id atau channel_id, jangan keduanya." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Pilih File, lalu Tambahkan ke Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "blok kode" msgid "Code blocks" msgstr "Blok kode" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Kode kedaluwarsa" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Perintah" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Komentar dinonaktifkan karena satu atau beberapa saluran yang dipilih mengaktifkan mode lambat. Komentar akan langsung diblokir mode lambat setelah pesan diteruskan." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Komentar dinonaktifkan karena mode lambat aktif di {forwardChannelDisplayName}. Komentar akan langsung diblokir mode lambat setelah pesan diteruskan." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Komentar tidak tersedia saat mode lambat aktif." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Notifikasi pesan komunitas" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Bilah notifikasi persyaratan MFA komunitas" @@ -9182,7 +9222,7 @@ msgstr "Lanjutkan melaporkan komunitas" msgid "Continue to report user" msgstr "Lanjutkan melaporkan pengguna" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Lanjutkan dengan passkey" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Mengoordinasikan penyerangan, pengeroyokan, atau pelecehan terhadap orang atau komunitas." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Disalin" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Salin tautan saluran" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Salin kode" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Penggunaan saat ini:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Saat ini ditutup" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Saat ini ditampilkan" @@ -12555,6 +12595,11 @@ msgstr "Tidak memblokir. Kamu bisa membukanya lagi nanti." msgid "Domain" msgstr "Domain" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domain dipindahkan" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Belanda" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "misalnya, YubiKey, iPhone, komputer kantor" @@ -14056,10 +14101,14 @@ msgstr "Masukkan PIN kunci keamananmu untuk melanjutkan." msgid "Enter the SMS code" msgstr "Masukkan kode SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Masukkan kode ini di browsermu untuk menyelesaikan proses masuk." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Masukkan kode ini di aplikasi {PRODUCT_NAME} lama Anda untuk menyelesaikan masuk." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "tekan Enter untuk <0><1>menyimpan" @@ -14441,7 +14490,7 @@ msgstr "Kedaluwarsa {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Kedaluwarsa antara {earliest} dan {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Kedaluwarsa dalam {remaining} detik" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Gagal memulai SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Gagal beralih akun" @@ -15413,17 +15462,17 @@ msgstr "Untuk saya" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Untuk pemeriksaan ini, kamu mengirimkan SMS kepada kami, bukan menerima SMS dari kami." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Demi keamananmu, konfirmasi dulu bahwa ini benar kamu untuk melanjutkan." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Paksa dinonaktifkan" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Paksa diaktifkan" @@ -15540,7 +15589,7 @@ msgstr "Lupa kata sandi" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Lupa kata sandimu?" @@ -15571,7 +15620,7 @@ msgstr "Teruskan media" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Teruskan pesan" @@ -15965,11 +16014,11 @@ msgstr "Buat tautan yang dapat dibagikan atau salin CSS kustom gabunganmu." msgid "Generate invite link" msgstr "Buat tautan undangan" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Buat kode baru" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Membuat kode…" @@ -16045,6 +16094,11 @@ msgstr "Dapatkan aplikasi desktop {productName} untuk push-to-talk di seluruh si msgid "Get the desktop app" msgstr "Dapatkan aplikasi desktop" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Dapatkan aplikasi {productName} yang baru" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Sisipkan emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Instal {productName} untuk pemilih sumber lengkap dan kontrol berbagi layar yang lebih lancar." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Pasang dari menu peramban Anda." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Pasang aplikasi baru" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Permintaan otorisasi tidak valid" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Izin bot tidak valid." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Kode tidak valid atau kedaluwarsa. Coba lagi." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "redirect_uri tidak valid" @@ -19332,6 +19396,11 @@ msgstr "Tautan" msgid "Link (mature)" msgstr "Tautan (dewasa)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Tautkan perangkat baru" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Pengaturan tampilan pesan hanya tersedia di desktop." msgid "Message editing" msgstr "Edit pesan" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Pesan diteruskan" @@ -20746,7 +20815,7 @@ msgstr "Tombol input pesan" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Pesan terlalu panjang" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Lain-lain" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id tidak ada" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Nama minimal 2 karakter" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Beri nama passkey" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formulir nama passkey" @@ -22381,7 +22450,7 @@ msgstr "Tidak ada perubahan untuk disimpan" msgid "No channels available" msgstr "Tidak ada saluran yang tersedia" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Tidak ada saluran yang ditemukan" @@ -22824,7 +22893,7 @@ msgstr "Tidak ada pengguna yang ditemukan" msgid "No verification is required." msgstr "Tidak perlu verifikasi." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Metode verifikasi tidak tersedia" @@ -23417,6 +23486,16 @@ msgstr "Buka {contextName}" msgid "Open {displayName}" msgstr "Buka {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Buka {host} di Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Buka {host} di browser Anda." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Buka {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Buka {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Buka {productName} di browser Anda" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Buka opsi banner" msgid "Open bookmarks" msgstr "Buka bookmark" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Buka browser" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Buka emoji di browser" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Buka pemilih emoji" @@ -23809,6 +23893,16 @@ msgstr "Buka tautan di bawah di browsermu, lalu salin seluruh respons JSON dan t msgid "Open the menu and choose an option." msgstr "Buka menu dan pilih opsi." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Buka aplikasi baru dan pilih Masuk dengan aplikasi {productName} lama Anda. Kemudian pilih Tautkan perangkat baru di sini dan masukkan kode yang ditampilkan." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Buka aplikasi baru. Anda sudah masuk." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Buka obrolan suara" msgid "Open voice & video settings" msgstr "Buka pengaturan suara & video" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Buka browsermu, masuk, lalu masukkan kode di bawah untuk menautkan akunmu." @@ -23868,6 +23962,10 @@ msgstr "Buka browsermu, masuk, lalu masukkan kode di bawah untuk menautkan akunm msgid "Open your messages" msgstr "Buka pesanmu" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Buka aplikasi {PRODUCT_NAME} lama Anda dan pilih Tautkan perangkat baru, lalu masukkan kode di bawah ini." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Buka aplikasi pesan di ponselmu dan buat pesan teks baru." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Passkey" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Nama passkey" @@ -28138,7 +28236,7 @@ msgstr "Saturasi" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Pratinjau geser video" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Pilih teks" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Pilih akun yang ingin kamu gunakan untuk masuk di aplikasi desktop." +msgid "Select the account you want to sign in with on your new device." +msgstr "Pilih akun yang ingin Anda gunakan untuk masuk di perangkat baru Anda." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Kirim" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Kirim ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sesi" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sesi untuk {identifier} telah berakhir. Masuk lagi." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Pintasan dijeda selama perekaman." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Persingkat pesan dan coba lagi." @@ -30217,6 +30315,11 @@ msgstr "Tampilkan maks. 3 (lencana +5)" msgid "Show max 5 (+5 badge)" msgstr "Tampilkan maks. 5 (lencana +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Tunjukkan caranya" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Masuk untuk mengklaim hadiahmu" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Masuk melalui browser" @@ -30496,6 +30599,11 @@ msgstr "Masuk melalui browser" msgid "Sign in with a passkey" msgstr "Masuk dengan passkey" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Masuk dengan aplikasi {productName} lama Anda" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Detail masuk" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Alur masuk" @@ -31030,7 +31138,7 @@ msgstr "Beberapa pengguna tidak dapat ditambahkan" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Bicara di saluran suara ini. Tanpa izin ini, anggota tetap dibisukan sam msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Bicara di saluran suara. Tanpa izin ini, anggota tetap dibisukan sampai seseorang dengan izin Bisukan anggota membatalkan pembisuan mereka." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Ketuk Bagikan, lalu Tambahkan ke Layar Utama." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Pemeriksaan telepon ditunda. Tinggal satu langkah email lagi." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "redirect_uri yang diberikan tidak terdaftar untuk aplikasi ini." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "redirect_uri yang diberikan harus menggunakan HTTP atau HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Animasi ini terlalu panjang atau terlalu besar untuk dipotong. Coba file yang lebih pendek atau lebih kecil." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Aplikasi ini" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Lampiran ini sudah kedaluwarsa" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Bot ini tidak publik dan tidak dapat ditambahkan oleh pengguna lain." @@ -33757,7 +33870,7 @@ msgstr "Grup ini terlalu banyak orangnya" msgid "This group is full. Remove someone before adding more people." msgstr "Grup ini penuh. Keluarkan seseorang sebelum menambahkan orang lain." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Kode serah terima ini sudah kedaluwarsa. Buat yang baru untuk melanjutkan." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Tidak diketahui" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplikasi tidak dikenal, sesi kedaluwarsa, atau permintaan otorisasi tidak valid." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Penggunaan:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Menggunakan status akun sebenarnya" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Menggunakan status inventaris hadiah sebenarnya" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Menggunakan status premium sebenarnya" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Menggunakan status sebenarnya" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Menggunakan status verifikasi sebenarnya" @@ -37947,17 +38060,17 @@ msgstr "Tunggu di sini" msgid "Waiting for connection..." msgstr "Menunggu koneksi..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Menunggu passkey…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Menunggu mode lambat di {forwardChannelDisplayName} berakhir." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Menunggu mode lambat di satu atau beberapa saluran yang dipilih berakhir." @@ -38156,7 +38269,7 @@ msgstr "Kami mengubah ukuran dan mengompres stiker ini menjadi 320x320 piksel, t msgid "We sent a code to {email}." msgstr "Kami telah mengirim kode ke {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Kami akan mengisi otomatis {prefillEmail} setelah login browser terbuka." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Pratinjau situs web" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Selamat datang kembali" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Langganan {premiumProductName} kamu telah berakhir. Kamu kehilangan semua keuntungan {premiumProductName}. Aktifkan kembali langgananmu untuk mendapatkan akses lagi." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Sesi {productName} kamu telah berakhir. Masuk lagi untuk melanjutkan otorisasi aplikasi ini." @@ -39734,7 +39847,7 @@ msgstr "Akunmu terlalu baru untuk mengirim pesan di komunitas ini." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Permintaan akunmu menunggu persetujuan. Kamu dapat masuk setelah admin menyetujuinya." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Akunmu memerlukan autentikasi multifaktor, tetapi tidak ada metode yang didukung di perangkat ini. Masuk di perangkat yang punya aplikasi autentikator atau kunci keamananmu, atau hubungi dukungan." @@ -39757,6 +39870,11 @@ msgstr "Tindakanmu tidak dapat diselesaikan karena ditandai oleh sistem keamanan msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Pengajuanmu sedang ditinjau. Kamu masih bisa memperbarui detail pendaftaranmu atau menarik pengajuannya." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Peramban Anda tidak mendukung kunci sandi di domain ini. Perbarui peramban Anda, atau masuk dengan kata sandi dan kode dua faktor Anda." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Kartumu memenuhi syarat. Kamu akan dialihkan ke halaman pembayaran sekar msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Operatormu menyatakan nomor ini tidak aktif. Periksa kembali lalu coba lagi, atau hubungi dukungan jika nomornya sudah benar." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Kodemu" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Kodemu sudah siap" diff --git a/fluxer_app/src/features/i18n/locales/it/messages.po b/fluxer_app/src/features/i18n/locales/it/messages.po index a6706b6e5..fe41a91d2 100644 --- a/fluxer_app/src/features/i18n/locales/it/messages.po +++ b/fluxer_app/src/features/i18n/locales/it/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# messaggio bloccato} other {# messaggi bloccati}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community trovata} other {# community trovate}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community è temporaneamente non disponibile a causa di un malfunzionamento del condensatore di flusso.} other {# community sono temporaneamente non disponibili a causa di un malfunzionamento del condensatore di flusso.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} ha rilevato un potenziale raid in questa community. Gli in msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} ha rilevato un potenziale raid, quindi i nuovi utenti non possono unirsi al momento." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} si è spostato su {host}. Aggiungilo alla schermata Home o al Dock, quindi accedi con questa app." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} si è spostato su {host}. Installalo dal tuo browser, quindi accedi con questa app." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} si è spostato su {host}. Installa la nuova app e sarai già connesso." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} si è spostato su {host}. Aprilo nel browser per installare la nuova app." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} ha bisogno dell'accesso al microfono per elencare i tuoi d msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} necessita dell'accesso alla registrazione dello schermo. Abilita la registrazione dello schermo nelle impostazioni sulla privacy del tuo sistema operativo e riavvia l'app." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} ora si trova su {host}. Installalo da lì, quindi accedi con questa app." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} ora si trova su {host}. Installalo da lì." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "un sondaggio" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri è obbligatorio quando bot non è l’unico ambito di autorizzazione richiesto." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Aggiungi un bot a una community con le autorizzazioni richieste" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Aggiungi un commento (facoltativo)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Aggiungi una chiave API di YouTube Data per arricchire i link di YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Aggiungi account" @@ -4379,6 +4409,11 @@ msgstr "Approvazione richiesta" msgid "Approve" msgstr "Approva" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Approva questa app da {productName}, l'app che usi già. Non serve la password." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autorizzazione non riuscita" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorizzazione non riuscita. Riprova." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Permessi bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "I permessi del bot non possono essere negativi." @@ -6329,10 +6364,10 @@ msgstr "Impossibile caricare file qui" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Impossibile caricare file qui" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Scegli sfondo" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Scegli guild_id o channel_id, non entrambi." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Scegli File, quindi Aggiungi al Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "blocco di codice" msgid "Code blocks" msgstr "Blocchi di codice" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Codice scaduto" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Comandi" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "I commenti sono disabilitati perché uno o più canali selezionati hanno la modalità lenta attiva. Il commento verrebbe bloccato dalla modalità lenta subito dopo l'inoltro." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "I commenti sono disabilitati perché la modalità lenta è attiva in {forwardChannelDisplayName}. Il commento verrebbe bloccato dalla modalità lenta subito dopo l'inoltro." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "I commenti non sono disponibili con la modalità lenta attiva." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Notifiche messaggi community" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Banner requisito MFA della community" @@ -9182,7 +9222,7 @@ msgstr "Continua per segnalare la community" msgid "Continue to report user" msgstr "Continua per segnalare l’utente" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Continua con passkey" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coordina raid, attacchi organizzati o molestie contro persone o community." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Copiato" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Copia link canale" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Copia codice" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Utilizzi attuali:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Attualmente ignorato" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Attualmente visualizzato" @@ -12555,6 +12595,11 @@ msgstr "L’utente non verrà bloccato. Puoi riaprire il DM in seguito." msgid "Domain" msgstr "Dominio" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "La dominio è stata spostata" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Olandese" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "es. YubiKey, iPhone, computer di lavoro" @@ -14056,10 +14101,14 @@ msgstr "Inserisci il PIN della tua chiave di sicurezza per continuare." msgid "Enter the SMS code" msgstr "Inserisci il codice SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Inserisci questo codice nel browser per completare l'accesso." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Inserisci questo codice nella tua vecchia app {PRODUCT_NAME} per completare l'accesso." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Invio per <0><1>salvare" @@ -14441,7 +14490,7 @@ msgstr "Scade il {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Scade tra {earliest} e {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Scade tra {remaining}s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Accesso SSO non riuscito" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Impossibile cambiare account" @@ -15413,17 +15462,17 @@ msgstr "Per me" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Per questo controllo, devi inviarci un SMS anziché riceverne uno da noi." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Per la tua sicurezza, conferma la tua identità per continuare." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Disattivazione forzata" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Attivazione forzata" @@ -15540,7 +15589,7 @@ msgstr "Password dimenticata" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Hai dimenticato la password?" @@ -15571,7 +15620,7 @@ msgstr "Inoltra contenuto multimediale" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Inoltra messaggio" @@ -15965,11 +16014,11 @@ msgstr "Genera un link condivisibile o copia il tuo CSS personalizzato combinato msgid "Generate invite link" msgstr "Genera link d'invito" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Genera nuovo codice" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generazione codice…" @@ -16045,6 +16094,11 @@ msgstr "Scarica l'app desktop di {productName} per usare il \"push-to-talk\" a l msgid "Get the desktop app" msgstr "Scarica l'app desktop" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Scarica la nuova app {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Inserisci emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Installa {productName} per il selettore completo delle sorgenti e controlli di condivisione schermo più fluidi." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Installalo dal menu del tuo browser." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Installa la nuova app" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Richiesta di autorizzazione non valida" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Permessi bot non validi." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Codice non valido o scaduto. Riprova." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "redirect_uri non valido" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (contenuti per adulti)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Collega un nuovo dispositivo" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Le impostazioni di visualizzazione dei messaggi sono disponibili solo su msgid "Message editing" msgstr "Modifica messaggi" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Messaggio inoltrato" @@ -20746,7 +20815,7 @@ msgstr "Pulsanti di input del messaggio" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Il messaggio è troppo lungo" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Varie" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id mancante" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Il nome deve contenere almeno 2 caratteri" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Dai un nome alla passkey" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Modulo nome passkey" @@ -22381,7 +22450,7 @@ msgstr "Nessuna modifica da salvare" msgid "No channels available" msgstr "Nessun canale disponibile" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Nessun canale trovato" @@ -22824,7 +22893,7 @@ msgstr "Nessun utente trovato" msgid "No verification is required." msgstr "Nessuna verifica richiesta." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Nessun metodo di verifica disponibile" @@ -23417,6 +23486,16 @@ msgstr "Apri {contextName}" msgid "Open {displayName}" msgstr "Apri {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Apri {host} in Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Apri {host} nel tuo browser." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Apri {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Apri {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Apri {productName} nel tuo browser" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Apri opzioni banner" msgid "Open bookmarks" msgstr "Apri i messaggi salvati" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Apri il browser" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Apri emoji nel browser" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Apri selettore emoji" @@ -23809,6 +23893,16 @@ msgstr "Apri il link qui sotto nel tuo browser, poi copia l'intera risposta JSON msgid "Open the menu and choose an option." msgstr "Apri il menu e scegli un'opzione." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Apri la nuova app e scegli Accedi con la tua vecchia app {productName}. Poi scegli Collega un nuovo dispositivo qui e inserisci il codice che mostra." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Apri la nuova app. Sei già connesso." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Apri la chat vocale" msgid "Open voice & video settings" msgstr "Apri le impostazioni audio e video" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Apri il browser, accedi, quindi inserisci il codice qui sotto per collegare il tuo account." @@ -23868,6 +23962,10 @@ msgstr "Apri il browser, accedi, quindi inserisci il codice qui sotto per colleg msgid "Open your messages" msgstr "Apri i tuoi messaggi" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Apri la tua vecchia app {PRODUCT_NAME} e scegli Collega un nuovo dispositivo, quindi inserisci il codice qui sotto." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Apri l'app Messaggi del tuo telefono e crea un nuovo SMS." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Passkey" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Nome passkey" @@ -28138,7 +28236,7 @@ msgstr "Saturazione" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Anteprima durante lo scorrimento del video" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Seleziona testo" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Seleziona l'account con cui vuoi accedere all'app desktop." +msgid "Select the account you want to sign in with on your new device." +msgstr "Seleziona l'account con cui vuoi accedere sul tuo nuovo dispositivo." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Invia" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Invia ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sessione" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sessione scaduta per {identifier}. Accedi di nuovo." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Le scorciatoie sono sospese durante la registrazione." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Accorcia il messaggio e riprova." @@ -30217,6 +30315,11 @@ msgstr "Mostra al massimo 3 (badge +5)" msgid "Show max 5 (+5 badge)" msgstr "Mostra al massimo 5 (badge +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Mostrami come" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Accedi per riscattare il tuo regalo" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Accedi tramite browser" @@ -30496,6 +30599,11 @@ msgstr "Accedi tramite browser" msgid "Sign in with a passkey" msgstr "Accedi con una passkey" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Accedi con la tua vecchia app {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Dettagli accesso" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Flusso di accesso" @@ -31030,7 +31138,7 @@ msgstr "Alcuni utenti non possono essere aggiunti" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Parla in questo canale vocale. Senza questo permesso, i membri rimangono msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Parla nei canali vocali. Senza questo permesso, i membri rimangono con il microfono disattivato finché qualcuno con il permesso \"Silenzia membri\" non lo riattiva." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Tocca Condividi, quindi Aggiungi a schermata Home." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Il controllo del telefono è stato rimandato. Rimane un passaggio via email." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Il valore redirect_uri fornito non è registrato per questa applicazione." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Il valore redirect_uri fornito deve usare HTTP o HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Questa animazione è troppo lunga o troppo grande per essere ritagliata. Prova un file più corto o più piccolo." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Questa applicazione" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Questo allegato è scaduto" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Questo bot non è pubblico e non può essere aggiunto da altri utenti." @@ -33757,7 +33870,7 @@ msgstr "Questo gruppo ha troppe persone" msgid "This group is full. Remove someone before adding more people." msgstr "Questo gruppo è al completo. Rimuovi qualcuno prima di aggiungere altre persone." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Questo codice di trasferimento è scaduto. Generane uno nuovo per continuare." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Sconosciuto" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Applicazione sconosciuta, sessione scaduta o richiesta di autorizzazione non valida." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Utilizzi:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Utilizzo dello stato effettivo dell'account" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Utilizzo dello stato effettivo dell'inventario regali" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Utilizzo dello stato premium effettivo" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Utilizzo dello stato attuale" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Utilizzo dello stato di verifica effettivo" @@ -37947,17 +38060,17 @@ msgstr "Attendi qui" msgid "Waiting for connection..." msgstr "In attesa di connessione..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "In attesa della passkey…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "In attesa che la modalità lenta in {forwardChannelDisplayName} scada." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "In attesa che la modalità lenta scada in uno o più canali selezionati." @@ -38156,7 +38269,7 @@ msgstr "Abbiamo ridimensionato e compresso questo sticker a 320x320 pixel, ma la msgid "We sent a code to {email}." msgstr "Abbiamo inviato un codice a {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Precompileremo {prefillEmail} quando si aprirà l'accesso tramite browser." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Anteprime siti web" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Bentornato" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Il tuo abbonamento a {premiumProductName} è scaduto. Hai perso tutti i vantaggi di {premiumProductName}. Riattiva l'abbonamento per riottenere l'accesso." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "La tua sessione di {productName} è scaduta. Accedi di nuovo per continuare ad autorizzare questa applicazione." @@ -39734,7 +39847,7 @@ msgstr "Il tuo account è troppo nuovo per inviare messaggi in questa community. msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "La tua richiesta di account è in attesa di approvazione. Potrai accedere dopo l’approvazione di un amministratore." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Il tuo account richiede l'autenticazione a più fattori, ma nessun metodo supportato è configurato su questo dispositivo. Accedi da un dispositivo con la tua app di autenticazione o la chiave di sicurezza, oppure contatta il supporto." @@ -39757,6 +39870,11 @@ msgstr "La tua azione non può essere completata perché è stata segnalata dai msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "La tua candidatura è in attesa di valutazione. Puoi comunque aggiornare i dettagli della scheda o ritirare la candidatura." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Il tuo browser non supporta le passkey su questo dominio. Aggiorna il browser oppure accedi con la password e il codice di verifica in due passaggi." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "La tua carta è idonea. Ti stiamo reindirizzando alla pagina di pagament msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Il tuo operatore indica che questo numero non è attivo. Controllalo e riprova, oppure contatta il supporto se è corretto." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Il tuo codice" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Il tuo codice è pronto" diff --git a/fluxer_app/src/features/i18n/locales/ja/messages.po b/fluxer_app/src/features/i18n/locales/ja/messages.po index 5bc14ce37..bda870578 100644 --- a/fluxer_app/src/features/i18n/locales/ja/messages.po +++ b/fluxer_app/src/features/i18n/locales/ja/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {#件のブロックされたメッセージ} other msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {#件のコミュニティが見つかりました} other {#件のコミュニティが見つかりました}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {#件のコミュニティがフラックスキャパシタの不具合により一時的に利用できません。} other {#件のコミュニティがフラックスキャパシタの不具合により一時的に利用できません。}}" @@ -1692,6 +1692,26 @@ msgstr "{productName}がこのコミュニティで潜在的なレイドを検 msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName}がレイドの可能性を検出したため、現在新しいユーザーは参加できません。" +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} は {host} に移動しました。ホーム画面またはドックに追加してから、このアプリでサインインしてください。" + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName}は{host}に移動しました。ブラウザからインストールし、このアプリでサインインしてください。" + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} は {host} に移動しました。新しいアプリをインストールすると、すでにサインインされています。" + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} は {host} に移動しました。ブラウザで開いて新しいアプリをインストールしてください。" + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName}がデバイスを一覧表示するには、マイクへ msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName}には画面収録へのアクセスが必要です。OSのプライバシー設定で画面収録を許可し、アプリを再起動してください。" +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName}は{host}に移動しました。そちらからインストールし、このアプリでサインインしてください。" + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} は {host} に移動しました。そちらからインストールしてください。" + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "投票" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "ボットスコープが唯一のスコープではない場合、redirect_uriが必要です。" @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "リクエストされた権限でボットをコミュニティに追加" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "コメントを追加(任意)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "YouTube Data APIキーを追加すると、YouTubeリンクの表示を充実させられます。" #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "アカウントを追加" @@ -4379,6 +4409,11 @@ msgstr "承認が必要" msgid "Approve" msgstr "承認" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "お使いの {productName} アプリからこのアプリを承認してください。パスワードは不要です。" + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "認証に失敗しました" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "認証に失敗しました。もう一度お試しください。" @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "ボットの権限" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "ボットの権限に負の値は指定できません。" @@ -6329,10 +6364,10 @@ msgstr "ここにファイルをアップロードできません" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "ここにファイルをアップロードできません" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "背景を選択" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "guild_id または channel_id のいずれかを選択してください。両方は選択できません。" +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "「ファイル」メニューから「Dockに追加」を選択してください。" + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "コードブロック" msgid "Code blocks" msgstr "コードブロック" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "コードの有効期限が切れました" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "コマンド" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "選択したチャンネルのいずれかでスローモードがオンになっているため、コメントは無効です。転送後すぐにスローモードによってコメントがブロックされます。" #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "{forwardChannelDisplayName}ではスローモードがオンになっているため、コメントは無効です。転送直後にスローモードによってコメントがブロックされます。" #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "スローモード中はコメントできません。" @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "コミュニティメッセージ通知" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "コミュニティのMFA必須設定ナグバー" @@ -9182,7 +9222,7 @@ msgstr "コミュニティの報告に進む" msgid "Continue to report user" msgstr "ユーザーの報告に進む" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "パスキーで続行" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "個人またはコミュニティに対する襲撃、集団的嫌がらせ、またはハラスメントを組織する行為。" #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "コピーしました" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "チャンネルリンクをコピー" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "コードをコピー" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "現在の使用回数:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "現在非表示" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "現在表示中" @@ -12555,6 +12595,11 @@ msgstr "ブロックはしません。後でまた開けます。" msgid "Domain" msgstr "ドメイン" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "ドメイン移動のお知らせ" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "オランダ語" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "例: YubiKey、iPhone、会社のPC" @@ -14056,10 +14101,14 @@ msgstr "セキュリティキーのPINを入力して続行してください。 msgid "Enter the SMS code" msgstr "SMSコードを入力" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "サインインを完了するには、ブラウザでこのコードを入力してください。" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "このコードを古い{PRODUCT_NAME}アプリに入力して、サインインを完了してください。" + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enterで<0><1>保存" @@ -14441,7 +14490,7 @@ msgstr "有効期限: {absolute}({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "有効期限: {earliest}〜{latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "{remaining}秒で期限切れ" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "SSOを開始できませんでした" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "アカウントの切り替えに失敗しました" @@ -15413,17 +15462,17 @@ msgstr "自分用" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "この確認では、こちらからテキストメッセージを受け取るのではなく、こちらにテキストメッセージを送信していただきます。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "セキュリティ保護のため、ご本人確認をお願いします。" #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "強制的に無効" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "強制的に有効" @@ -15540,7 +15589,7 @@ msgstr "パスワードを忘れた場合" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "パスワードをお忘れですか?" @@ -15571,7 +15620,7 @@ msgstr "メディアを転送" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "メッセージを転送" @@ -15965,11 +16014,11 @@ msgstr "共有用のリンクを生成するか、まとめたカスタムCSSを msgid "Generate invite link" msgstr "招待リンクを生成" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "新しいコードを生成" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "コードを生成中…" @@ -16045,6 +16094,11 @@ msgstr "システム全体のプッシュトゥトークやその他の便利な msgid "Get the desktop app" msgstr "デスクトップアプリを入手" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "新しい{productName}アプリを入手する" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "絵文字を挿入" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "より詳細なソースピッカーとスムーズな画面共有コントロールには、{productName}をインストールしてください。" +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "ブラウザのメニューからインストールしてください。" + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "新しいアプリをインストール" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "無効な認証リクエスト" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "ボットの権限が無効です。" @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "コードが無効であるか、期限が切れています。もう一度お試しください。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "redirect_uriが無効です" @@ -19332,6 +19396,11 @@ msgstr "リンク" msgid "Link (mature)" msgstr "リンク (成人向け)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "新しいデバイスをリンク" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "メッセージ表示設定はデスクトップ版でのみ利用でき msgid "Message editing" msgstr "メッセージの編集" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "メッセージを転送しました" @@ -20746,7 +20815,7 @@ msgstr "メッセージ入力ボタン" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "メッセージが長すぎます" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "その他" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_idがありません" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "名前は2文字以上で入力してください" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "パスキーに名前を付ける" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "パスキーの名前" @@ -22381,7 +22450,7 @@ msgstr "保存する変更はありません" msgid "No channels available" msgstr "利用できるチャンネルがありません" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "チャンネルが見つかりませんでした" @@ -22824,7 +22893,7 @@ msgstr "ユーザーが見つかりませんでした" msgid "No verification is required." msgstr "認証は不要です。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "利用できる認証方法がありません" @@ -23417,6 +23486,16 @@ msgstr "{contextName}を開く" msgid "Open {displayName}" msgstr "{displayName}を開く" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Safariで{host}を開いてください。" + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "{host}をブラウザで開いてください。" + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "「{macosSystemSettingsName}」→「{macosPrivacyAndSecuritySettingsNam msgid "Open {productName}" msgstr "{productName}を開く" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "{productName}をブラウザで開く" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "バナーオプションを開く" msgid "Open bookmarks" msgstr "ブックマークを開く" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "ブラウザを開く" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "ブラウザで絵文字を開く" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "絵文字ピッカーを開く" @@ -23809,6 +23893,16 @@ msgstr "以下のリンクをブラウザで開き、JSONレスポンス全体 msgid "Open the menu and choose an option." msgstr "メニューを開いてオプションを選択します。" +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "新しいアプリを開き、「古い{productName}アプリでサインイン」を選択してください。次に、「新しいデバイスをこちらでリンク」を選択し、表示されるコードを入力します。" + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "新しいアプリを開く。すでにサインインしています。" + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "ボイスを開く" msgid "Open voice & video settings" msgstr "音声とビデオの設定を開く" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "ブラウザを開いてサインインし、以下のコードを入力してアカウントをリンクしてください。" @@ -23868,6 +23962,10 @@ msgstr "ブラウザを開いてサインインし、以下のコードを入力 msgid "Open your messages" msgstr "メッセージアプリを開く" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "古い{PRODUCT_NAME}アプリを開き、「新しいデバイスをリンク」を選択して、以下のコードを入力してください。" + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "スマートフォンのメッセージアプリを開き、新しいメッセージを作成してください。" @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "パスキー" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "パスキー名" @@ -28138,7 +28236,7 @@ msgstr "彩度" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "スクラブプレビュー" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "テキストを選択" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "デスクトップ版アプリでログインするアカウントを選択してください。" +msgid "Select the account you want to sign in with on your new device." +msgstr "新しいデバイスでサインインするアカウントを選択してください。" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "送信" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "送信 ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "セッション" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "{identifier} のセッションの有効期限が切れました。もう一度サインインしてください。" @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "記録中はショートカットが一時停止されます。" #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "メッセージを短くして、もう一度お試しください。" @@ -30217,6 +30315,11 @@ msgstr "最大3件表示(+5バッジ)" msgid "Show max 5 (+5 badge)" msgstr "最大5件表示(+5バッジ)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "手順を見る" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "サインインしてギフトを受け取る" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "ブラウザでサインイン" @@ -30496,6 +30599,11 @@ msgstr "ブラウザでサインイン" msgid "Sign in with a passkey" msgstr "パスキーでサインイン" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "古い {productName} アプリでサインインしてください" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "サインイン情報" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "サインインの流れ" @@ -31030,7 +31138,7 @@ msgstr "追加できないユーザーがいます" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "このボイスチャンネルで話すことができます。これが msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "ボイスチャンネルで話すことができます。これがないと、メンバーは「メンバーをミュート」権限を持つユーザーがミュート解除するまでミュートされたままになります。" +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "「共有」をタップし、「ホーム画面に追加」をタップします。" + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "電話番号の確認は保留されました。メールでの確認が1つ残っています。" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "指定されたredirect_uriは、このアプリケーションに登録されていません。" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "指定されたredirect_uriはHTTPまたはHTTPSを使用する必要があります。" @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "このアニメーションは長すぎるか、大きすぎるため、トリミングできません。より短く、またはより小さいファイルをお試しください。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "このアプリ" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "この添付ファイルの有効期限が切れました" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "このボットは公開されていません。他のユーザーが追加することはできません。" @@ -33757,7 +33870,7 @@ msgstr "このグループは人数が多すぎます" msgid "This group is full. Remove someone before adding more people." msgstr "このグループは満員です。メンバーを追加する前に、誰かを削除してください。" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "この引き継ぎコードは有効期限が切れました。新しいコードを生成して続行してください。" @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "不明" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "不明なアプリ、期限切れのセッション、または無効な認証リクエストです。" @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "使用回数:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "実際のアカウントの状態を使用中" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "実際のギフト在庫状況を使用中" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "実際のプレミアム状態を使用中" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "実際の状態を使用中" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "実際の認証状態を使用中" @@ -37947,17 +38060,17 @@ msgstr "そのままお待ちください" msgid "Waiting for connection..." msgstr "接続待機中…" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "パスキーを待機中…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "{forwardChannelDisplayName}のスローモードが解除されるまで待っています。" #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "選択したチャンネルのいずれかでスローモードが解除されるまで待っています。" @@ -38156,7 +38269,7 @@ msgstr "このスタンプを320x320ピクセルにリサイズして圧縮し msgid "We sent a code to {email}." msgstr "{email}にコードを送信しました。" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "ブラウザでサインイン画面が開いたら、{prefillEmail}を自動入力します。" @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "ウェブサイトのプレビュー" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "おかえりなさい" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "{premiumProductName}のサブスクリプションが期限切れになりました。すべての{premiumProductName}特典は失効しています。再開してアクセスを回復してください。" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "{productName}のセッションの有効期限が切れました。このアプリケーションの認証を続行するには、もう一度サインインしてください。" @@ -39734,7 +39847,7 @@ msgstr "アカウント作成から間もないため、このコミュニティ msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "アカウント申請は承認待ちです。管理者が承認するとサインインできます。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "アカウントで多要素認証が必須ですが、このデバイスではサポートされている方法が設定されていません。認証アプリまたはセキュリティキーが設定されているデバイスでサインインするか、サポートにお問い合わせください。" @@ -39757,6 +39870,11 @@ msgstr "安全システムによって不適切な操作と判断されたため msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "現在、申請内容を審査中です。引き続き掲載内容を更新したり、申請を取り下げたりすることができます。" +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "お使いのブラウザでは、このドメインでパスキーを使用できません。ブラウザをアップデートするか、パスワードと2段階認証コードでサインインしてください。" + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "カードが有効です。支払いページに移動します。" msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "お使いの通信事業者の情報によると、この番号は現在利用されていません。番号を確認してもう一度お試しいただくか、番号が正しい場合はサポートにご連絡ください。" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "あなたのコード" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "コードの準備ができました" diff --git a/fluxer_app/src/features/i18n/locales/ko/messages.po b/fluxer_app/src/features/i18n/locales/ko/messages.po index 191977f27..a35171ec9 100644 --- a/fluxer_app/src/features/i18n/locales/ko/messages.po +++ b/fluxer_app/src/features/i18n/locales/ko/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {#개 차단된 메시지} other {#개 차단된 메 msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {#개 커뮤니티 찾음} other {#개 커뮤니티 찾음}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {#개 커뮤니티가 플럭스 캐패시터 오작동으로 인해 일시적으로 이용할 수 없습니다.} other {#개 커뮤니티가 플럭스 캐패시터 오작동으로 인해 일시적으로 이용할 수 없습니다.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName}에서 이 커뮤니티에 잠재적인 레이드를 감지 msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName}에서 잠재적인 레이드를 감지했습니다. 현재 새 사용자는 참여할 수 없습니다." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName}이(가) {host}(으)로 이전했습니다. 홈 화면이나 Dock에 추가한 다음 이 앱으로 로그인하세요." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName}이(가) {host}(으)로 이전했습니다. 브라우저에서 설치한 후 이 앱으로 로그인하세요." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName}이(가) {host}(으)로 이전되었습니다. 새 앱을 설치하면 자동으로 로그인됩니다." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName}이(가) {host}(으)로 이전했습니다. 새 앱을 설치하려면 브라우저에서 열어주세요." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName}에서 기기 목록을 보려면 마이크 접근 권한 msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName}에 화면 녹화 접근 권한이 필요합니다. 운영체제 개인 정보 보호 설정에서 화면 녹화를 허용하고 앱을 다시 시작하세요." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName}이(가) 이제 {host}(으)로 이전했습니다. 해당 주소에서 설치한 후 이 앱으로 로그인하세요." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName}이(가) 이제 {host}(으)로 이전되었습니다. 해당 주소에서 설치하세요." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "투표" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "봇 스코프가 유일한 스코프가 아닌 경우 redirect_uri가 필요합니다." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "요청된 권한으로 봇을 커뮤니티에 추가" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "코멘트 추가 (선택 사항)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "YouTube 링크에 더 자세한 정보를 표시하려면 YouTube Data API 키를 추가하세요." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "계정 추가" @@ -4379,6 +4409,11 @@ msgstr "승인 필요" msgid "Approve" msgstr "승인" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "이미 사용 중인 {productName} 앱에서 이 앱을 승인하세요. 비밀번호가 필요하지 않습니다." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "승인 실패" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "승인에 실패했습니다. 다시 시도해 주세요." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "봇 권한" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "봇 권한은 음수일 수 없습니다." @@ -6329,10 +6364,10 @@ msgstr "여기에 파일을 올릴 수 없습니다" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "여기에 파일을 올릴 수 없습니다" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "배경 선택" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "guild_id 또는 channel_id 중 하나를 선택하세요. 둘 다 선택할 수 없습니다." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "파일을 선택한 다음 Dock에 추가를 선택하세요." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "코드 블록" msgid "Code blocks" msgstr "코드 블록" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "코드 만료됨" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "명령어" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "선택한 채널 중 하나 이상에 슬로우 모드가 켜져 있어 댓글을 사용할 수 없습니다. 전달 직후 슬로우 모드에 걸려 댓글이 전송되지 않습니다." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "{forwardChannelDisplayName} 채널에 슬로우 모드가 켜져 있어 댓글을 사용할 수 없습니다. 전달 직후 슬로우 모드에 걸려 댓글이 전송되지 않습니다." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "슬로우 모드 중에는 댓글을 사용할 수 없습니다." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "커뮤니티 메시지 알림" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "커뮤니티 MFA 필수 알림 표시줄" @@ -9182,7 +9222,7 @@ msgstr "커뮤니티 신고 계속하기" msgid "Continue to report user" msgstr "사용자 신고 계속하기" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "패스키로 계속하기" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "특정인이나 커뮤니티를 겨냥한 레이드, 집단 공격, 괴롭힘을 조율합니다." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "복사됨" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "채널 링크 복사" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "코드 복사" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "현재 사용 횟수:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "현재 닫힘" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "현재 표시 중" @@ -12555,6 +12595,11 @@ msgstr "차단하지 않습니다. 나중에 다시 열 수 있습니다." msgid "Domain" msgstr "도메인" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "도메인 이동 알림" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "네덜란드어" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "예: YubiKey, iPhone, 회사 컴퓨터" @@ -14056,10 +14101,14 @@ msgstr "계속하려면 보안 키의 PIN을 입력하세요." msgid "Enter the SMS code" msgstr "SMS 코드 입력" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "브라우저에 이 코드를 입력하여 로그인을 완료하세요." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "기존 {PRODUCT_NAME} 앱에 이 코드를 입력하여 로그인을 완료하세요." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter 키를 눌러 <0><1>저장" @@ -14441,7 +14490,7 @@ msgstr "{absolute} ({relativeText}) 만료" msgid "Expires between {earliest} and {latest}" msgstr "{earliest} ~ {latest} 만료" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "{remaining}초 후 만료" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "SSO를 시작하지 못했습니다" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "계정 전환 실패" @@ -15413,17 +15462,17 @@ msgstr "직접 구매" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "이 확인 절차에서는 메시지를 받는 대신 저희에게 문자 메시지를 보내주셔야 합니다." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "안전을 위해 본인 확인 후 계속해 주세요." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "강제 비활성화됨" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "강제 활성화됨" @@ -15540,7 +15589,7 @@ msgstr "비밀번호 찾기" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "비밀번호를 잊으셨나요?" @@ -15571,7 +15620,7 @@ msgstr "미디어 전달" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "메시지 전달" @@ -15965,11 +16014,11 @@ msgstr "공유 가능한 링크를 생성하거나 조합된 맞춤 CSS를 복 msgid "Generate invite link" msgstr "초대 링크 생성" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "새 코드 생성" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "코드 생성 중…" @@ -16045,6 +16094,11 @@ msgstr "시스템 전체 푸시 투 토크 및 기타 유용한 기능을 사용 msgid "Get the desktop app" msgstr "데스크톱 앱 다운로드" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "새로운 {productName} 앱을 받으세요" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "이모티콘 삽입" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "전체 소스 선택 도구와 더 원활한 화면 공유 제어 기능을 사용하려면 {productName}을(를) 설치하세요." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "브라우저 메뉴에서 설치하세요." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "새 앱 설치" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "유효하지 않은 승인 요청입니다" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "봇 권한이 유효하지 않습니다." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "코드가 유효하지 않거나 만료되었습니다. 다시 시도해 주세요." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "잘못된 redirect_uri입니다" @@ -19332,6 +19396,11 @@ msgstr "링크" msgid "Link (mature)" msgstr "링크 (성인용)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "새 기기 연결" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "메시지 표시 설정은 데스크톱에서만 사용할 수 있습니 msgid "Message editing" msgstr "메시지 수정" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "메시지 전달됨" @@ -20746,7 +20815,7 @@ msgstr "메시지 입력 버튼" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "메시지가 너무 깁니다" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "기타" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id가 누락되었습니다" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "이름은 2자 이상이어야 합니다" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "패스키 이름 지정" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "패스키 이름 양식" @@ -22381,7 +22450,7 @@ msgstr "저장할 변경사항 없음" msgid "No channels available" msgstr "채널을 찾을 수 없습니다" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "채널을 찾을 수 없습니다" @@ -22824,7 +22893,7 @@ msgstr "사용자를 찾을 수 없습니다" msgid "No verification is required." msgstr "인증이 필요하지 않습니다." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "사용 가능한 인증 방법 없음" @@ -23417,6 +23486,16 @@ msgstr "{contextName} 열기" msgid "Open {displayName}" msgstr "{displayName} 열기" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Safari에서 {host}를(을) 여세요." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "{host}을(를) 브라우저에서 열어주세요." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "{macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} → msgid "Open {productName}" msgstr "{productName} 열기" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "{productName}을(를) 브라우저에서 열기" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "배너 옵션 열기" msgid "Open bookmarks" msgstr "북마크 열기" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "브라우저 열기" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "브라우저에서 이모티콘 열기" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "이모티콘 선택기 열기" @@ -23809,6 +23893,16 @@ msgstr "아래 링크를 브라우저에서 연 다음, 전체 JSON 응답을 msgid "Open the menu and choose an option." msgstr "메뉴를 열고 옵션을 선택합니다." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "새 앱을 열고 기존 {productName} 앱으로 로그인하기를 선택하세요. 그런 다음 여기서 새 기기 연결을 선택하고 표시되는 코드를 입력하세요." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "새 앱을 열어보세요. 이미 로그인되어 있습니다." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "음성 대화 열기" msgid "Open voice & video settings" msgstr "오디오 및 비디오 설정 열기" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "브라우저를 열고 로그인한 다음 아래 코드를 입력하여 계정을 연결하세요." @@ -23868,6 +23962,10 @@ msgstr "브라우저를 열고 로그인한 다음 아래 코드를 입력하여 msgid "Open your messages" msgstr "메시지 열기" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "기존 {PRODUCT_NAME} 앱을 열고 '기기 연결'을 선택한 다음 아래 코드를 입력하세요." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "휴대폰 메시지 앱을 열고 새 문자 메시지를 작성해 주세요." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "패스키" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "패스키 이름" @@ -28138,7 +28236,7 @@ msgstr "채도" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "스크러빙 미리보기" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "텍스트 선택" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "데스크톱 앱에 로그인할 계정을 선택하세요." +msgid "Select the account you want to sign in with on your new device." +msgstr "새 기기에서 로그인할 계정을 선택하세요." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "보내기" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "보내기 ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "세션" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "{identifier} 계정의 세션이 만료되었습니다. 다시 로그인하세요." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "기록하는 동안에는 단축키가 일시 중지됩니다." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "메시지를 줄여서 다시 시도해 주세요." @@ -30217,6 +30315,11 @@ msgstr "최대 3개 표시 (+5개 배지)" msgid "Show max 5 (+5 badge)" msgstr "최대 5개 표시 (+5개 배지)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "설치 방법 보기" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "선물을 받으려면 로그인하세요" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "브라우저로 로그인" @@ -30496,6 +30599,11 @@ msgstr "브라우저로 로그인" msgid "Sign in with a passkey" msgstr "패스키로 로그인" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "기존 {productName} 앱으로 로그인" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "로그인 정보" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "로그인 절차" @@ -31030,7 +31138,7 @@ msgstr "일부 사용자는 추가할 수 없습니다" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "이 음성 채널에서 말할 수 있습니다. 이 권한이 없으면 msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "음성 채널에서 말할 수 있습니다. 이 권한이 없으면 \"멤버 음소거\" 권한이 있는 사람이 음소거를 해제할 때까지 멤버는 음소거 상태로 유지됩니다." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "공유를 탭한 다음 홈 화면에 추가를 탭하세요." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "휴대폰 인증은 보류되었습니다. 이메일 인증 단계가 하나 남았습니다." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "제공된 redirect_uri가 이 애플리케이션에 등록되지 않았습니다." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "제공된 redirect_uri는 HTTP 또는 HTTPS를 사용해야 합니다." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "이 애니메이션은 너무 길거나 커서 자를 수 없습니다. 더 짧거나 작은 파일을 사용해 보세요." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "이 애플리케이션" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "만료된 첨부파일입니다" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "이 봇은 비공개이며 다른 사용자가 추가할 수 없습니다." @@ -33757,7 +33870,7 @@ msgstr "그룹 인원이 너무 많습니다" msgid "This group is full. Remove someone before adding more people." msgstr "이 그룹이 가득 찼습니다. 다른 사람을 추가하려면 먼저 누군가를 내보내세요." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "이 핸드오프 코드가 만료되었습니다. 계속하려면 새 코드를 생성하세요." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "알 수 없음" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "알 수 없는 앱, 만료된 세션 또는 유효하지 않은 인증 요청입니다." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "사용 횟수:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "실제 계정 상태 사용 중" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "실제 선물 재고 상태 사용 중" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "실제 프리미엄 상태 사용 중" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "실제 상태 사용 중" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "실제 인증 상태 사용 중" @@ -37947,17 +38060,17 @@ msgstr "잠시만 기다려 주세요" msgid "Waiting for connection..." msgstr "연결 대기 중..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "패스키를 기다리는 중…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "{forwardChannelDisplayName} 채널의 슬로우 모드가 만료되기를 기다리는 중입니다." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "선택한 채널 중 일부 채널의 슬로우 모드 만료를 기다리는 중입니다." @@ -38156,7 +38269,7 @@ msgstr "스티커 크기를 320x320픽셀로 조정하고 압축했지만, 여 msgid "We sent a code to {email}." msgstr "{email}(으)로 코드를 전송했습니다." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "브라우저 로그인 창이 열리면 {prefillEmail}이(가) 자동으로 입력됩니다." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "웹사이트 미리보기" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "다시 오신 것을 환영합니다" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "{premiumProductName} 구독이 만료되었습니다. 모든 {premiumProductName} 혜택이 사라졌습니다. 구독을 다시 활성화하면 혜택을 다시 이용할 수 있습니다." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "{productName} 세션이 만료되었습니다. 이 애플리케이션의 인증을 계속하려면 다시 로그인하세요." @@ -39734,7 +39847,7 @@ msgstr "계정을 만든 지 얼마 되지 않아 이 커뮤니티에서 메시 msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "계정 요청이 승인 대기 중입니다. 관리자가 승인한 후 로그인할 수 있습니다." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "계정에 다단계 인증이 필요하지만, 이 기기에는 지원되는 방법이 설정되어 있지 않습니다. 인증 앱 또는 보안 키가 있는 기기에서 로그인하거나 지원팀에 문의하세요." @@ -39757,6 +39870,11 @@ msgstr "요청하신 작업이 안전 시스템에 감지되어 완료되지 않 msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "신청이 검토 대기 중입니다. 등록 정보를 업데이트하거나 신청을 철회할 수 있습니다." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "이 도메인에서는 브라우저에서 패스키를 지원하지 않습니다. 브라우저를 업데이트하거나 비밀번호와 2단계 인증 코드로 로그인하세요." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "카드 사용이 가능합니다. 지금 결제 페이지로 이동합 msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "이동통신사에서 이 번호가 사용되지 않는 번호라고 확인했습니다. 번호를 확인하고 다시 시도하거나, 번호가 정확하다면 고객지원팀에 문의하세요." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "내 코드" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "코드가 준비되었습니다" diff --git a/fluxer_app/src/features/i18n/locales/lt/messages.po b/fluxer_app/src/features/i18n/locales/lt/messages.po index a9b46c5ff..5d6bcf16f 100644 --- a/fluxer_app/src/features/i18n/locales/lt/messages.po +++ b/fluxer_app/src/features/i18n/locales/lt/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# užblokuota žinutė} few {# užblokuotos žinut msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Rasta # bendruomenė} few {Rastos # bendruomenės} other {Rasta # bendruomenių}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# bendruomenė laikinai nepasiekiama dėl srauto kondensatoriaus gedimo.} few {# bendruomenės laikinai nepasiekiamos dėl srauto kondensatoriaus gedimo.} other {# bendruomenių laikinai nepasiekiama dėl srauto kondensatoriaus gedimo.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} aptiko galimą ataką šioje bendruomenėje. Kvietimai pri msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} aptiko galimą ataką, todėl nauji naudotojai šiuo metu negali prisijungti." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} perkeltas į {host}. Pridėkite jį prie pagrindinio ekrano arba doko, tada prisijunkite naudodami šią programą." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} perkeltas į {host}. Įdiekite jį naršyklėje, tada prisijunkite naudodami šią programą." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} perkeltas į {host}. Įdiekite naują programėlę ir būsite automatiškai prisijungę." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} perkeltas į {host}. Atidarykite naršyklėje, kad įdiegtumėte naują programėlę." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} reikia prieigos prie mikrofono, kad būtų galima parodyti msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} reikia leidimo įrašyti ekraną. Leiskite įrašyti ekraną operacinės sistemos privatumo nustatymuose ir iš naujo paleiskite programą." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} dabar veikia adresu {host}. Įdiekite jį ten, tada prisijunkite naudodami šią programą." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} dabar veikia adresu {host}. Įdiekite jį iš ten." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "apklausa" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri yra būtinas, kai bot nėra vienintelė aprėptis." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Pridėti botą į bendruomenę su prašomais leidimais" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Pridėti komentarą (pasirinktinai)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Pridėkite YouTube Data API raktą, kad praturtintumėte YouTube nuorodas." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Pridėti paskyrą" @@ -4379,6 +4409,11 @@ msgstr "Reikalingas patvirtinimas" msgid "Approve" msgstr "Patvirtinti" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Patvirtinkite šią programėlę iš jau naudojamos {productName} programėlės. Slaptažodis nereikalingas." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autorizacija nepavyko" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorizacija nepavyko. Bandykite dar kartą." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "boto leidimai" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Boto leidimai negali būti neigiami." @@ -6329,10 +6364,10 @@ msgstr "Čia negalima įkelti failų" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Čia negalima įkelti failų" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Pasirinkite foną" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Pasirinkite „guild_id“ arba „channel_id“, bet ne abu." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Pasirinkite Failas, tada Pridėti prie Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "kodo blokas" msgid "Code blocks" msgstr "Kodo blokai" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Kodas baigė galioti" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Komandos" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Komentarai išjungti, nes viename ar daugiau pasirinktų kanalų įjungtas lėtasis režimas. Komentaras būtų užblokuotas lėtojo režimo iškart po persiuntimo." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Komentarai išjungti, nes kanale „{forwardChannelDisplayName}“ įjungtas lėtasis režimas. Komentaras būtų užblokuotas lėtojo režimo iškart po persiuntimo." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Komentarai nepasiekiami, kai įjungtas lėtasis režimas." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Bendruomenės žinučių pranešimai" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Bendruomenės MFA reikalavimo pranešimų juosta" @@ -9182,7 +9222,7 @@ msgstr "Tęsti ir pranešti apie bendruomenę" msgid "Continue to report user" msgstr "Tęsti ir pranešti apie naudotoją" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Tęsti su prieigos raktu" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Koordinuoja reidus, masinius puolimus ar priekabiavimą prieš asmenis ar bendruomenes." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Nukopijuota" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kopijuoti kanalo nuorodą" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kopijuoti kodą" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Panaudojimų:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Šiuo metu atmesta" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Šiuo metu rodoma" @@ -12555,6 +12595,11 @@ msgstr "Neužblokuoja. Vėliau galėsite jį vėl atidaryti." msgid "Domain" msgstr "Domenas" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domenas perkeltas" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "olandų" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "pvz., YubiKey, iPhone, darbinis kompiuteris" @@ -14056,10 +14101,14 @@ msgstr "Įveskite savo saugos rakto PIN kodą, kad tęstumėte." msgid "Enter the SMS code" msgstr "Įveskite SMS kodą" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Įveskite šį kodą naršyklėje, kad prisijungtumėte." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Įveskite šį kodą savo senojoje „{PRODUCT_NAME}“ programėlėje, kad užbaigtumėte prisijungimą." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Paspauskite „Enter“, kad <0><1>išsaugotumėte" @@ -14441,7 +14490,7 @@ msgstr "Baigiasi {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Baigia galioti tarp {earliest} ir {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Baigs galioti po {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Nepavyko paleisti SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Nepavyko perjungti paskyros" @@ -15413,17 +15462,17 @@ msgstr "Man" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Šiam patikrinimui jūs mums siunčiate tekstinę žinutę, o ne gaunate ją iš mūsų." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Siekdami užtikrinti jūsų saugumą, patvirtinkite, kad tai jūs." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Priverstinai išjungta" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Priverstinai įjungta" @@ -15540,7 +15589,7 @@ msgstr "Pamiršote slaptažodį" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Pamiršote slaptažodį?" @@ -15571,7 +15620,7 @@ msgstr "Persiųsti mediją" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Persiųsti žinutę" @@ -15965,11 +16014,11 @@ msgstr "Sugeneruokite bendrinamą nuorodą arba nukopijuokite sujungtą tinkint msgid "Generate invite link" msgstr "Generuoti kvietimo nuorodą" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generuoti naują kodą" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generuojamas kodas…" @@ -16045,6 +16094,11 @@ msgstr "Atsisiųskite {productName} darbalaukio programėlę, kad galėtumėte n msgid "Get the desktop app" msgstr "Gauti darbalaukio programėlę" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Gaukite naująją „{productName}“ programėlę" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Įterpti jaustuką" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Įdiekite „{productName}“, kad galėtumėte naudoti visą šaltinio parinkiklį ir sklandesnius ekrano bendrinimo valdiklius." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Įdiekite jį iš savo naršyklės meniu." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Įdiegti naują programą" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Netinkama autorizacijos užklausa" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Netinkami boto leidimai." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Neteisingas arba pasibaigęs kodas. Bandykite dar kartą." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Neteisingas „redirect_uri“" @@ -19332,6 +19396,11 @@ msgstr "Nuoroda" msgid "Link (mature)" msgstr "Nuoroda (suaugusiesiems)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Susiekite naują įrenginį" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Žinučių rodymo nustatymai pasiekiami tik darbalaukio programoje." msgid "Message editing" msgstr "Redaguoti žinutę" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Žinutė persiųsta" @@ -20746,7 +20815,7 @@ msgstr "Žinutės įvesties mygtukai" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Žinutė per ilga" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Įvairūs" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Trūksta client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Pavadinimas turi būti bent 2 simbolių ilgio" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Pavadinkite prieigos raktą" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Prieigos rakto pavadinimo forma" @@ -22381,7 +22450,7 @@ msgstr "Nėra išsaugotinų pakeitimų" msgid "No channels available" msgstr "Nėra pasiekiamų kanalų" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Kanalų nerasta" @@ -22824,7 +22893,7 @@ msgstr "Naudotojų nerasta" msgid "No verification is required." msgstr "Patvirtinimas nereikalingas." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Nėra patvirtinimo metodų" @@ -23417,6 +23486,16 @@ msgstr "Atidaryti „{contextName}“" msgid "Open {displayName}" msgstr "Atidaryti „{displayName}“" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Atidarykite {host} „Safari“." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Atidarykite {host} naršyklėje." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Atidarykite „{macosSystemSettingsName}“ → „{macosPrivacyAndSecur msgid "Open {productName}" msgstr "Atidaryti „{productName}“" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Atidarykite naršyklėje {productName}" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Atidaryti reklamjuostės parinktis" msgid "Open bookmarks" msgstr "Atidaryti žymes" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Atidaryti naršyklę" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Atidaryti jaustuką naršyklėje" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Atidaryti jaustukų parinkiklį" @@ -23809,6 +23893,16 @@ msgstr "Atidarykite toliau esančią nuorodą naršyklėje, tada nukopijuokite v msgid "Open the menu and choose an option." msgstr "Atidarykite meniu ir pasirinkite parinktį." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Atidarykite naują programėlę ir pasirinkite „Prisijungti naudojant senąją {productName} programėlę“. Tada pasirinkite „Susieti naują įrenginį čia“ ir įveskite rodomą kodą." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Atidarykite naują programėlę. Jūs jau esate prisijungę." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Atidaryti balso pokalbį" msgid "Open voice & video settings" msgstr "Atidaryti balso ir vaizdo nustatymus" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Atidarykite naršyklę, prisijunkite, tada įveskite toliau pateiktą kodą, kad susietumėte paskyrą." @@ -23868,6 +23962,10 @@ msgstr "Atidarykite naršyklę, prisijunkite, tada įveskite toliau pateiktą ko msgid "Open your messages" msgstr "Atidarykite savo žinutes" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Atidarykite savo senąją „{PRODUCT_NAME}“ programėlę ir pasirinkite „Susieti naują įrenginį“, tada įveskite žemiau pateiktą kodą." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Atidarykite telefono žinučių programėlę ir sukurkite naują tekstinę žinutę." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Prieigos raktas" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Prieigos rakto pavadinimas" @@ -28138,7 +28236,7 @@ msgstr "Sodrumas" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Prasukimo peržiūra" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Pažymėti tekstą" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Pasirinkite paskyrą, su kuria norite prisijungti prie darbalaukio programos." +msgid "Select the account you want to sign in with on your new device." +msgstr "Pasirinkite paskyrą, su kuria norite prisijungti naujame įrenginyje." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Siųsti" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Siųsti ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Seansas" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Seanso galiojimas baigėsi naudotojui „{identifier}“. Prisijunkite iš naujo." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Įrašant spartieji klavišai laikinai neveikia." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Sutrumpinkite žinutę ir bandykite dar kartą." @@ -30217,6 +30315,11 @@ msgstr "Rodyti daugiausia 3 (+5 ženklelis)" msgid "Show max 5 (+5 badge)" msgstr "Rodyti daugiausia 5 (+5 ženklelis)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Rodyk man kaip" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Prisijunkite, kad atsiimtumėte dovaną" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Prisijungti per naršyklę" @@ -30496,6 +30599,11 @@ msgstr "Prisijungti per naršyklę" msgid "Sign in with a passkey" msgstr "Prisijungti naudojant prieigos raktą" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Prisijunkite naudodami savo senąją {productName} programėlę" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Prisijungimo duomenys" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Prisijungimo eiga" @@ -31030,7 +31138,7 @@ msgstr "Kai kurių naudotojų negalima pridėti" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Kalbėti šiame balso kanale. Be šio leidimo nariai lieka nutildyti, ko msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Kalbėti balso kanaluose. Be šio leidimo nariai lieka nutildyti, kol kas nors, turintis leidimą „Nutildyti narius“, atšauks jų nutildymą." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Palieskite Bendrinti, tada – Pridėti prie pradžios ekrano." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Telefono patikra atidėta. Liko vienas el. pašto veiksmas." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Pateiktas „redirect_uri“ neužregistruotas šiai programai." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Pateiktas „redirect_uri“ turi naudoti HTTP arba HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ši animacija yra per ilga arba per didelė, kad ją būtų galima apkarpyti. Pabandykite trumpesnį arba mažesnį failą." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Ši programa" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Šis priedas nebegalioja" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Šis botas nėra viešas, todėl kiti naudotojai negali jo pridėti." @@ -33757,7 +33870,7 @@ msgstr "Šioje grupėje yra per daug žmonių" msgid "This group is full. Remove someone before adding more people." msgstr "Ši grupė pilna. Pašalinkite ką nors prieš pridėdami daugiau žmonių." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Šis perdavimo kodas nebegalioja. Norėdami tęsti, sugeneruokite naują." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Nežinoma" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Nežinoma programa, pasibaigusi sesija arba neteisinga autorizacijos užklausa." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Naudojimų skaičius:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Naudojama tikroji paskyros būsena" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Naudojama tikroji dovanų atsargų būsena" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Naudojama tikroji „Premium“ būsena" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Naudojama tikroji būsena" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Naudojama tikroji patvirtinimo būsena" @@ -37947,17 +38060,17 @@ msgstr "Palaukite čia" msgid "Waiting for connection..." msgstr "Laukiama prisijungimo..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Laukiama prieigos rakto…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Laukiama, kol pasibaigs lėtasis režimas kanale „{forwardChannelDisplayName}“." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Laukiama, kol pasibaigs lėtasis režimas viename ar keliuose pasirinktuose kanaluose." @@ -38156,7 +38269,7 @@ msgstr "Šį lipduką pakeitėme ir suglaudinome iki 320x320 pikselių, bet jo d msgid "We sent a code to {email}." msgstr "Išsiuntėme kodą į {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Atidarius prisijungimo naršyklėje langą, iš anksto užpildysime {prefillEmail}." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Svetainių peržiūros" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Sveiki sugrįžę" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Jūsų „{premiumProductName}“ prenumerata baigėsi. Praradote visas „{premiumProductName}“ privilegijas. Norėdami atgauti prieigą, aktyvuokite prenumeratą iš naujo." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Jūsų „{productName}“ seanso laikas baigėsi. Prisijunkite iš naujo, kad tęstumėte šios programos autorizavimą." @@ -39734,7 +39847,7 @@ msgstr "Jūsų paskyra per nauja, kad galėtų siųsti žinutes šioje bendruome msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Jūsų paskyros užklausa laukia patvirtinimo. Prisijungti galėsite, kai administratorius ją patvirtins." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Jūsų paskyrai reikalingas daugiafaktorinis autentifikavimas, tačiau šiame įrenginyje nėra nustatytų palaikomų metodų. Prisijunkite įrenginyje, kuriame yra jūsų autentifikavimo programa arba saugos raktas, arba susisiekite su palaikymo komanda." @@ -39757,6 +39870,11 @@ msgstr "Jūsų veiksmas negalėjo būti atliktas, nes jį pažymėjo mūsų saug msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Jūsų paraiška laukia peržiūros. Vis dar galite atnaujinti savo įrašo informaciją arba atšaukti paraišką." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Jūsų naršyklė nepalaiko slapta rakto šiame domene. Atnaujinkite naršyklę arba prisijunkite naudodami slaptažodį ir dviejų veiksnių kodą." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Jūsų kortelė tinkama. Dabar būsite nukreipti į mokėjimo puslapį." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Jūsų operatorius teigia, kad šis numeris nebenaudojamas. Patikrinkite jį ir bandykite dar kartą arba susisiekite su palaikymo tarnyba, jei numeris teisingas." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Jūsų kodas" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Jūsų kodas paruoštas" diff --git a/fluxer_app/src/features/i18n/locales/nl/messages.po b/fluxer_app/src/features/i18n/locales/nl/messages.po index 73713631f..3f4a1982d 100644 --- a/fluxer_app/src/features/i18n/locales/nl/messages.po +++ b/fluxer_app/src/features/i18n/locales/nl/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# geblokkeerd bericht} other {# geblokkeerde berich msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community gevonden} other {# communities gevonden}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community is tijdelijk niet beschikbaar door een storing in de fluxcondensator.} other {# communities zijn tijdelijk niet beschikbaar door een storing in de fluxcondensator.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} heeft een mogelijke raid gedetecteerd in deze community. U msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} heeft een mogelijke raid gedetecteerd, dus nieuwe gebruikers kunnen nu niet deelnemen." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} is verhuisd naar {host}. Voeg het toe aan je beginscherm of dock, en meld je daarna aan met deze app." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} is verhuisd naar {host}. Installeer het vanuit je browser en meld je daarna aan met deze app." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} is verhuisd naar {host}. Installeer de nieuwe app en je bent al ingelogd." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} is verhuisd naar {host}. Open het in je browser om de nieuwe app te installeren." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} heeft microfoontoegang nodig om je apparaten weer te geven msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} heeft toestemming nodig om het scherm op te nemen. Sta schermopname toe in de privacyinstellingen van je besturingssysteem en start de app opnieuw." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} is nu te vinden op {host}. Installeer het vanaf daar en log vervolgens in met deze app." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} is nu te vinden op {host}. Installeer het vanaf daar." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "een peiling" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Een redirect_uri is vereist als \"bot\" niet het enige machtigingsbereik is." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Voeg een bot toe aan een community met de gevraagde machtigingen" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Opmerking toevoegen (optioneel)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Voeg een YouTube Data API-sleutel toe om YouTube-links te verrijken." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Account toevoegen" @@ -4379,6 +4409,11 @@ msgstr "Goedkeuring vereist" msgid "Approve" msgstr "Goedkeuren" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Keur deze app goed vanuit de {productName}-app die je al gebruikt. Geen wachtwoord nodig." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autorisatie mislukt" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorisatie mislukt. Probeer opnieuw." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Botmachtigingen" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Botmachtigingen kunnen niet negatief zijn." @@ -6329,10 +6364,10 @@ msgstr "Kan hier geen bestanden uploaden" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Kan hier geen bestanden uploaden" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Kies achtergrond" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Kies guild_id of channel_id, niet beide." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Kies Bestand en vervolgens Toevoegen aan Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "codeblok" msgid "Code blocks" msgstr "Codeblokken" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Code verlopen" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Opdrachten" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Reacties zijn uitgeschakeld omdat slowmodus actief is in een of meer geselecteerde kanalen. De reactie zou direct na het doorsturen door slowmodus worden geblokkeerd." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Reacties zijn uitgeschakeld omdat slowmodus actief is in {forwardChannelDisplayName}. De reactie zou direct na het doorsturen door slowmodus worden geblokkeerd." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Reacties zijn niet beschikbaar als de slowmodus is ingeschakeld." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Meldingen voor communityberichten" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Banner voor MFA-vereiste van community" @@ -9182,7 +9222,7 @@ msgstr "Doorgaan met community melden" msgid "Continue to report user" msgstr "Doorgaan met gebruiker melden" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Doorgaan met passkey" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coördineert raids, georganiseerde verstoringsacties of intimidatie tegen personen of communities." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Gekopieerd" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kanaallink kopiëren" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Code kopiëren" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Aantal keer gebruikt:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Momenteel weggeklikt" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Wordt nu weergegeven" @@ -12555,6 +12595,11 @@ msgstr "Blokkeert niet. Je kunt later opnieuw openen." msgid "Domain" msgstr "Domein" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domein verplaatst nagbar" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Nederlands" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "bijv. YubiKey, iPhone, werkcomputer" @@ -14056,10 +14101,14 @@ msgstr "Voer de pincode van je beveiligingssleutel in om verder te gaan." msgid "Enter the SMS code" msgstr "Voer de sms-code in" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Voer deze code in je browser in om het aanmelden te voltooien." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Voer deze code in je oude {PRODUCT_NAME}-app in om het aanmelden te voltooien." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter om <0><1>op te slaan" @@ -14441,7 +14490,7 @@ msgstr "Verloopt op {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Verloopt tussen {earliest} en {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Verloopt over {remaining}s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Kan SSO niet starten" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Niet gelukt om van account te wisselen" @@ -15413,17 +15462,17 @@ msgstr "Voor mij" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Voor deze controle stuur je ons een sms in plaats van er een van ons te ontvangen." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Bevestig voor je veiligheid dat jij het bent om verder te gaan." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Geforceerd uitgeschakeld" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Geforceerd ingeschakeld" @@ -15540,7 +15589,7 @@ msgstr "Wachtwoord vergeten" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Wachtwoord vergeten?" @@ -15571,7 +15620,7 @@ msgstr "Media doorsturen" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Bericht doorsturen" @@ -15965,11 +16014,11 @@ msgstr "Genereer een deelbare link of kopieer je gecombineerde aangepaste CSS." msgid "Generate invite link" msgstr "Uitnodigingslink genereren" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Nieuwe code genereren" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Code genereren…" @@ -16045,6 +16094,11 @@ msgstr "Download de {productName}-desktopapp voor systeembrede push-to-talk en e msgid "Get the desktop app" msgstr "Download de desktopapp" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Download de nieuwe {productName}-app" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Emoji invoegen" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Installeer {productName} voor de volledige bronkiezer en soepelere bediening van scherm delen." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Installeer het vanuit je browsermenu." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Installeer de nieuwe app" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Ongeldig autorisatieverzoek" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Ongeldige botmachtigingen." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ongeldige of verlopen code. Probeer het opnieuw." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Ongeldige redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (voor volwassenen)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Koppel een nieuw apparaat" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Instellingen voor de weergave van berichten zijn alleen beschikbaar op d msgid "Message editing" msgstr "Bericht bewerken" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Bericht doorgestuurd" @@ -20746,7 +20815,7 @@ msgstr "Knoppen voor berichtinvoer" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Bericht is te lang" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Overig" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id ontbreekt" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Naam moet minimaal 2 tekens lang zijn" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Passkey een naam geven" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formulier om een passkey een naam te geven" @@ -22381,7 +22450,7 @@ msgstr "Geen wijzigingen om op te slaan" msgid "No channels available" msgstr "Geen kanalen beschikbaar" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Geen kanalen gevonden" @@ -22824,7 +22893,7 @@ msgstr "Geen gebruikers gevonden" msgid "No verification is required." msgstr "Geen verificatie vereist." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Geen verificatiemethoden beschikbaar" @@ -23417,6 +23486,16 @@ msgstr "Open {contextName}" msgid "Open {displayName}" msgstr "Open {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Open {host} in Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Open {host} in je browser." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Open {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Open {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Open {productName} in je browser" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Banneropties openen" msgid "Open bookmarks" msgstr "Opgeslagen berichten openen" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Browser openen" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Emoji in browser openen" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Emoji-kiezer openen" @@ -23809,6 +23893,16 @@ msgstr "Open de onderstaande link in je browser, kopieer het volledige JSON-antw msgid "Open the menu and choose an option." msgstr "Open het menu en kies een optie." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Open de nieuwe app en kies Aanmelden met je oude {productName}-app. Kies daarna Hier een nieuw apparaat koppelen en voer de code in die wordt weergegeven." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Open de nieuwe app. Je bent al aangemeld." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Spraakchat openen" msgid "Open voice & video settings" msgstr "Audio- en video-instellingen openen" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Open je browser, meld je aan en voer de onderstaande code in om je account te koppelen." @@ -23868,6 +23962,10 @@ msgstr "Open je browser, meld je aan en voer de onderstaande code in om je accou msgid "Open your messages" msgstr "Open je berichten" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Open je oude {PRODUCT_NAME}-app en kies 'Koppel een nieuw apparaat', voer daarna de onderstaande code in." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Open de berichtenapp van je telefoon en maak een nieuw sms-bericht aan." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Passkey" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Naam van passkey" @@ -28138,7 +28236,7 @@ msgstr "Verzadiging" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Voorbeeld bij spoelen" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Tekst selecteren" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Kies het account waarmee je wilt inloggen op de computerapp." +msgid "Select the account you want to sign in with on your new device." +msgstr "Selecteer het account waarmee je wilt inloggen op je nieuwe apparaat." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Verzenden" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Verzenden ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sessie" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sessie verlopen voor {identifier}. Log opnieuw in." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Sneltoetsen worden tijdens het opnemen gepauzeerd." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Verkort het bericht en probeer het opnieuw." @@ -30217,6 +30315,11 @@ msgstr "Max. 3 weergeven (+5 badge)" msgid "Show max 5 (+5 badge)" msgstr "Max. 5 weergeven (+5 badge)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Laat me zien hoe" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Log in om je cadeau in te wisselen" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Inloggen via browser" @@ -30496,6 +30599,11 @@ msgstr "Inloggen via browser" msgid "Sign in with a passkey" msgstr "Inloggen met een passkey" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Meld je aan met je oude {productName}-app" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Aanmeldingsgegevens" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Aanmeldingsproces" @@ -31030,7 +31138,7 @@ msgstr "Sommige gebruikers kunnen niet worden toegevoegd" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Praat in dit spraakkanaal. Zonder deze machtiging blijven leden gedempt msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Praat in spraakkanalen. Zonder deze machtiging blijven leden gedempt totdat iemand met de machtiging \"Leden dempen\" het dempen opheft." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Tik op Delen en vervolgens op Toevoegen aan beginscherm." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "De telefooncontrole is uitgesteld. Je moet nog één e-mailstap afronden." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "De opgegeven redirect_uri is niet geregistreerd voor deze app." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "De opgegeven redirect_uri moet HTTP of HTTPS gebruiken." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Deze animatie is te lang of te groot om bij te snijden. Probeer een korter of kleiner bestand." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Deze app" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Deze bijlage is verlopen" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Deze bot is niet openbaar en kan niet worden toegevoegd door andere gebruikers." @@ -33757,7 +33870,7 @@ msgstr "Deze groep heeft te veel mensen" msgid "This group is full. Remove someone before adding more people." msgstr "Deze groep is vol. Verwijder iemand voordat je meer mensen toevoegt." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Deze overdrachtscode is verlopen. Genereer een nieuwe om verder te gaan." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Onbekend" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Onbekende app, sessie verlopen of ongeldige autorisatieaanvraag." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Aantal keren gebruikt:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Gebruikt de werkelijke accountstatus" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Werkelijke status van cadeau-inventaris wordt gebruikt" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Gebruikt de daadwerkelijke premiumstatus" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Gebruikt de werkelijke status" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Werkelijke verificatiestatus gebruiken" @@ -37947,17 +38060,17 @@ msgstr "Even wachten" msgid "Waiting for connection..." msgstr "Wachten op verbinding..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Wachten op passkey…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Wachten tot de slowmodus in {forwardChannelDisplayName} is verlopen." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Wachten tot de slowmodus in een of meer geselecteerde kanalen is verlopen." @@ -38156,7 +38269,7 @@ msgstr "We hebben deze sticker verkleind en gecomprimeerd naar 320x320 pixels, m msgid "We sent a code to {email}." msgstr "We hebben een code gestuurd naar {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "We vullen {prefillEmail} alvast in zodra het inlogscherm in je browser opent." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Websitevoorbeelden" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Welkom terug" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Je {premiumProductName}-abonnement is verlopen. Je bent alle {premiumProductName}-voordelen kwijtgeraakt. Activeer je abonnement opnieuw om weer toegang te krijgen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Je {productName}-sessie is verlopen. Log opnieuw in om door te gaan met het autoriseren van deze app." @@ -39734,7 +39847,7 @@ msgstr "Je account is te nieuw om berichten te versturen in deze community." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Je accountaanvraag wacht op goedkeuring. Je kunt inloggen nadat een beheerder deze heeft goedgekeurd." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Je account vereist meervoudige authenticatie, maar er zijn geen ondersteunde methoden ingesteld op dit apparaat. Meld je aan op een apparaat met je authenticator-app of beveiligingssleutel, of neem contact op met de ondersteuning." @@ -39757,6 +39870,11 @@ msgstr "Je actie kon niet worden voltooid omdat deze is gemarkeerd door onze vei msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Je aanvraag wacht op beoordeling. Je kunt de gegevens van je vermelding nog bijwerken of de aanvraag intrekken." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Uw browser ondersteunt geen passkeys op dit domein. Werk uw browser bij, of meld u aan met uw wachtwoord en tweefactorcode." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Je kaart is geschikt. Je wordt nu doorgestuurd naar de betaalpagina." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Je provider geeft aan dat dit nummer niet in gebruik is. Controleer het en probeer het opnieuw, of neem contact op met support als het wel klopt." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Je code" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Je code is klaar" diff --git a/fluxer_app/src/features/i18n/locales/no/messages.po b/fluxer_app/src/features/i18n/locales/no/messages.po index cf714a3e1..23ba76ef4 100644 --- a/fluxer_app/src/features/i18n/locales/no/messages.po +++ b/fluxer_app/src/features/i18n/locales/no/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# blokkert melding} other {# blokkerte meldinger}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# fellesskap funnet} other {# fellesskap funnet}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# fellesskap er midlertidig utilgjengelig på grunn av en feil i flukskondensatoren.} other {# fellesskap er midlertidig utilgjengelige på grunn av en feil i flukskondensatoren.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} oppdaget et potensielt raid i dette fellesskapet. Invitasj msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} oppdaget et potensielt raid, så nye brukere kan ikke bli med akkurat nå." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} har flyttet til {host}. Legg det til på Hjem-skjermen eller Dock, og logg deretter inn med denne appen." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} har flyttet til {host}. Installer fra nettleseren din, og logg deretter inn med denne appen." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} har flyttet til {host}. Installer den nye appen, så er du allerede logget inn." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} har flyttet til {host}. Åpne i nettleseren for å installere den nye appen." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} trenger mikrofontilgang for å vise enhetene dine." msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} trenger tilgang til skjermopptak. Tillat skjermopptak i operativsystemets personverninnstillinger og start appen på nytt." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} holder nå til på {host}. Installer det derfra, og logg deretter inn med denne appen." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} holder nå til på {host}. Installer det derfra." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "en avstemning" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "En redirect_uri er påkrevd når bot ikke er det eneste tilgangsomfanget." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Legg til en bot i et fellesskap med forespurte tillatelser" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Legg til en kommentar (valgfritt)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Legg til en API-nøkkel for YouTube Data for å hente mer informasjon om YouTube-lenker." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Legg til konto" @@ -4379,6 +4409,11 @@ msgstr "Krever godkjenning" msgid "Approve" msgstr "Godkjenn" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Godkjenn denne appen fra {productName}-appen du allerede bruker. Ingen passord trengs." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autorisasjon mislyktes" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorisasjon mislyktes. Prøv igjen." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Bottillatelser" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Verdien for bottillatelser kan ikke være negativ." @@ -6329,10 +6364,10 @@ msgstr "Kan ikke laste opp filer her" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Kan ikke laste opp filer her" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Velg bakgrunn" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Velg enten guild_id eller channel_id, ikke begge." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Velg Arkiv, deretter Legg til i Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "kodeblokk" msgid "Code blocks" msgstr "Kodeblokker" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Koden er utløpt" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Kommandoer" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Kommentarer er deaktivert fordi én eller flere valgte kanaler har sakte modus aktivert. Kommentaren ville blitt blokkert av sakte modus rett etter videresendingen." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Kommentarer er deaktivert fordi sakte modus er på i {forwardChannelDisplayName}. Kommentaren ville blitt blokkert av sakte modus rett etter videresendingen." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Kommentarer er utilgjengelige når sakte modus er på." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Varsler for fellesskapsmeldinger" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Varsellinje for fellesskapets MFA-krav" @@ -9182,7 +9222,7 @@ msgstr "Fortsett for å rapportere fellesskapet" msgid "Continue to report user" msgstr "Fortsett for å rapportere brukeren" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Fortsett med passnøkkel" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Koordinerer raid, organiserte angrep eller trakassering mot personer eller fellesskap." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Kopiert" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kopier kanallenke" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kopier kode" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Antall ganger brukt:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Avvist for øyeblikket" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Viser nå" @@ -12555,6 +12595,11 @@ msgstr "Blokkerer ikke. Du kan åpne den igjen senere." msgid "Domain" msgstr "Domene" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domene flyttet nagbar" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Nederlandsk" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "f.eks. YubiKey, iPhone, jobb-PC" @@ -14056,10 +14101,14 @@ msgstr "Skriv inn PIN-koden for sikkerhetsnøkkelen din for å fortsette." msgid "Enter the SMS code" msgstr "Skriv inn SMS-koden" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Skriv inn denne koden i nettleseren din for å fullføre påloggingen." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Skriv inn denne koden i din gamle {PRODUCT_NAME}-app for å fullføre innloggingen." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter for å <0><1>lagre" @@ -14441,7 +14490,7 @@ msgstr "Utløper {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Utløper mellom {earliest} og {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Utløper om {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Kunne ikke starte SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Kunne ikke bytte konto" @@ -15413,17 +15462,17 @@ msgstr "For meg" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "For denne kontrollen sender du oss en tekstmelding i stedet for å motta en fra oss." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Av sikkerhetshensyn må du bekrefte at det er deg, for å fortsette." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Tvangsdeaktivert" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Tvangsaktivert" @@ -15540,7 +15589,7 @@ msgstr "Glemt passord" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Glemt passordet ditt?" @@ -15571,7 +15620,7 @@ msgstr "Videresend medieinnhold" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Videresend melding" @@ -15965,11 +16014,11 @@ msgstr "Generer en delbar lenke eller kopier din kombinerte egendefinerte CSS." msgid "Generate invite link" msgstr "Generer invitasjonslenke" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generer ny kode" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Genererer kode …" @@ -16045,6 +16094,11 @@ msgstr "Skaff deg skrivebordsappen til {productName} for å bruke «Trykk for å msgid "Get the desktop app" msgstr "Skaff deg skrivebordsappen" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Hent den nye {productName}-appen" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Sett inn emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Installer {productName} for å få en fullverdig kildevelger og mer smidige kontroller for skjermdeling." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Installer det fra nettlesermenyen din." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Installer den nye appen" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Ugyldig autoriseringsforespørsel" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Ugyldige bot-tillatelser." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ugyldig eller utløpt kode. Prøv igjen." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Ugyldig redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Lenke" msgid "Link (mature)" msgstr "Lenke (voksent innhold)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Koble til en ny enhet" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Innstillinger for meldingsvisning er bare tilgjengelige på datamaskin." msgid "Message editing" msgstr "Redigering av meldinger" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Melding videresendt" @@ -20746,7 +20815,7 @@ msgstr "Knapper i meldingsfeltet" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Meldingen er for lang" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Diverse" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Mangler client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Navnet må være på minst 2 tegn" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Gi passnøkkelen et navn" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Skjema for å gi passnøkkelen et navn" @@ -22381,7 +22450,7 @@ msgstr "Ingen endringer å lagre" msgid "No channels available" msgstr "Ingen kanaler tilgjengelige" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Ingen kanaler funnet" @@ -22824,7 +22893,7 @@ msgstr "Ingen brukere funnet" msgid "No verification is required." msgstr "Ingen verifisering er nødvendig." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Ingen bekreftelsesmetoder tilgjengelig" @@ -23417,6 +23486,16 @@ msgstr "Åpne {contextName}" msgid "Open {displayName}" msgstr "Åpne {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Åpne {host} i Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Åpne {host} i nettleseren din." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Åpne {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName msgid "Open {productName}" msgstr "Åpne {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Åpne {productName} i nettleseren din" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Åpne bannermenyen" msgid "Open bookmarks" msgstr "Åpne lagrede meldinger" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Åpne nettleser" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Åpne emoji i nettleser" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Åpne emojivelger" @@ -23809,6 +23893,16 @@ msgstr "Åpne lenken nedenfor i nettleseren din, kopier hele JSON-svaret og lim msgid "Open the menu and choose an option." msgstr "Åpne menyen og velg et alternativ." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Åpne den nye appen og velg «Logg inn med din gamle {productName}-app». Velg deretter «Koble til en ny enhet her» og skriv inn koden som vises." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Åpne den nye appen. Du er allerede logget inn." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Åpne talechat" msgid "Open voice & video settings" msgstr "Åpne innstillinger for tale og video" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Åpne nettleseren din, logg på, og skriv inn koden nedenfor for å koble til kontoen din." @@ -23868,6 +23962,10 @@ msgstr "Åpne nettleseren din, logg på, og skriv inn koden nedenfor for å kobl msgid "Open your messages" msgstr "Åpne meldingene dine" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Åpne din gamle {PRODUCT_NAME}-app og velg «Koble til en ny enhet», og skriv deretter inn koden nedenfor." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Åpne telefonens meldingsapp og opprett en ny tekstmelding." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Passnøkkel" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Passnøkkelnavn" @@ -28138,7 +28236,7 @@ msgstr "Metning" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Forhåndsvisning ved spoling" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Marker tekst" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Velg kontoen du vil logge på med i skrivebordsappen." +msgid "Select the account you want to sign in with on your new device." +msgstr "Velg kontoen du vil logge på med på den nye enheten din." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Send" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Send ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Økt" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Økten er utløpt for {identifier}. Logg på igjen." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Snarveier er satt på pause mens du registrerer en tastkombinasjon." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Forkort meldingen og prøv igjen." @@ -30217,6 +30315,11 @@ msgstr "Vis maks. 3 (+5-merke)" msgid "Show max 5 (+5 badge)" msgstr "Vis maks. 5 (+5-merke)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Vis meg hvordan" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Logg på for å hente gaven din" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Logg på via nettleser" @@ -30496,6 +30599,11 @@ msgstr "Logg på via nettleser" msgid "Sign in with a passkey" msgstr "Logg på med passnøkkel" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Logg inn med din gamle {productName}-app" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Påloggingsopplysninger" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Påloggingsflyt" @@ -31030,7 +31138,7 @@ msgstr "Noen brukere kan ikke legges til" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Snakk i denne talekanalen. Uten denne tillatelsen forblir medlemmer demp msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Snakk i talekanaler. Uten denne tillatelsen forblir medlemmer dempet til noen med tillatelsen «Demp medlemmer» opphever dempingen." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Trykk Del, deretter Legg til på Hjem-skjerm." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Telefonsjekken er satt til side. Ett e-posttrinn gjenstår." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Den oppgitte redirect_uri er ikke registrert for denne appen." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Den oppgitte redirect_uri må bruke HTTP eller HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Denne animasjonen er for lang eller for stor til å beskjæres. Prøv en kortere eller mindre fil." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Denne appen" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Dette vedlegget har utløpt" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Denne boten er ikke offentlig og kan ikke legges til av andre brukere." @@ -33757,7 +33870,7 @@ msgstr "Denne gruppen har for mange personer" msgid "This group is full. Remove someone before adding more people." msgstr "Denne gruppen er full. Fjern noen før du legger til flere." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Denne overføringskoden har utløpt. Generer en ny for å fortsette." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Ukjent" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Ukjent app, utløpt økt eller ugyldig autorisasjonsforespørsel." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Antall bruk:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Bruker faktisk kontostatus" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Bruker faktisk gavebeholdningsstatus" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Bruker faktisk premiumstatus" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Bruker faktisk tilstand" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Bruker faktisk bekreftelsesstatus" @@ -37947,17 +38060,17 @@ msgstr "Vent her" msgid "Waiting for connection..." msgstr "Venter på tilkobling …" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Venter på passnøkkel …" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Venter på at sakte modus i {forwardChannelDisplayName} skal utløpe." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Venter på at sakte modus skal utløpe i én eller flere valgte kanaler." @@ -38156,7 +38269,7 @@ msgstr "Vi endret størrelsen og komprimerte dette klistremerket til 320x320 pik msgid "We sent a code to {email}." msgstr "Vi sendte en kode til {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Vi fyller ut {prefillEmail} automatisk når nettleserpåloggingen åpnes." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Forhåndsvisning av nettsteder" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Velkommen tilbake" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Abonnementet ditt på {premiumProductName} har utløpt. Du har mistet alle fordelene med {premiumProductName}. Reaktiver abonnementet ditt for å få tilgang igjen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Økten din i {productName} har utløpt. Logg på igjen for å fortsette å autorisere denne appen." @@ -39734,7 +39847,7 @@ msgstr "Kontoen din er for ny til å sende meldinger i dette fellesskapet." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Kontoforespørselen din venter på godkjenning. Du kan logge på når en administrator har godkjent den." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Kontoen din krever multifaktorautentisering, men ingen støttede metoder er konfigurert på denne enheten. Logg på fra en enhet med autentiseringsappen eller sikkerhetsnøkkelen din, eller kontakt kundestøtte." @@ -39757,6 +39870,11 @@ msgstr "Handlingen din kunne ikke fullføres fordi den ble flagget av sikkerhets msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Søknaden din venter på gjennomgang. Du kan fortsatt oppdatere oppføringsdetaljene eller trekke tilbake søknaden." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Nettleseren din støtter ikke sikkerhetsnøkler på dette domenet. Oppdater nettleseren, eller logg inn med passordet ditt og tofaktor-kode." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Kortet ditt oppfyller kravene. Du blir nå sendt videre til betalingssid msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Mobiloperatøren din sier at dette nummeret ikke er i bruk. Sjekk det og prøv igjen, eller kontakt kundestøtte hvis det er riktig." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Koden din" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Koden din er klar" diff --git a/fluxer_app/src/features/i18n/locales/pl/messages.po b/fluxer_app/src/features/i18n/locales/pl/messages.po index 659b55ffe..e999f1c2f 100644 --- a/fluxer_app/src/features/i18n/locales/pl/messages.po +++ b/fluxer_app/src/features/i18n/locales/pl/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# zablokowana wiadomość} few {# zablokowane wiado msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Znaleziono # społeczność} few {Znaleziono # społeczności} many {Znaleziono # społeczności} other {Znaleziono # społeczności}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# społeczność jest tymczasowo niedostępna z powodu awarii \"kondensatora strumienia\".} few {# społeczności są tymczasowo niedostępne z powodu awarii \"kondensatora strumienia\".} many {# społeczności jest tymczasowo niedostępnych z powodu awarii \"kondensatora strumienia\".} other {# społeczności są tymczasowo niedostępne z powodu awarii \"kondensatora strumienia\".}}" @@ -1692,6 +1692,26 @@ msgstr "Aplikacja {productName} wykryła potencjalny nalot na tę społeczność msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "Aplikacja {productName} wykryła potencjalny nalot, więc nowi użytkownicy nie mogą teraz dołączyć." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} przeniósł się do {host}. Dodaj go do ekranu początkowego lub Docka, a następnie zaloguj się za pomocą tej aplikacji." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} przeniósł się na {host}. Zainstaluj go z przeglądarki, a następnie zaloguj się za pomocą tej aplikacji." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} przeniósł się do {host}. Zainstaluj nową aplikację, a będziesz już zalogowany." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} przeniósł się do {host}. Otwórz go w przeglądarce, aby zainstalować nową aplikację." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} potrzebuje dostępu do mikrofonu, aby wyświetlić Twoje u msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} potrzebuje dostępu do nagrywania ekranu. Włącz nagrywanie ekranu w ustawieniach prywatności systemu operacyjnego i uruchom ponownie aplikację." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} znajduje się teraz pod adresem {host}. Zainstaluj go stamtąd, a następnie zaloguj się za pomocą tej aplikacji." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} znajduje się teraz pod adresem {host}. Zainstaluj go stamtąd." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "ankieta" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Parametr \"redirect_uri\" jest wymagany, gdy zakres \"bot\" nie jest jedynym zakresem." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Dodaj bota do społeczności z żądanymi uprawnieniami" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Dodaj komentarz (opcjonalnie)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Dodaj klucz YouTube Data API, aby wzbogacić linki do YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Dodaj konto" @@ -4379,6 +4409,11 @@ msgstr "Wymagana akceptacja" msgid "Approve" msgstr "Zatwierdź" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Zatwierdź tę aplikację z aplikacji {productName}, której już używasz. Hasło nie jest potrzebne." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autoryzacja nieudana" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autoryzacja nie powiodła się. Spróbuj ponownie." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Uprawnienia bota" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Uprawnienia bota nie mogą być ujemne." @@ -6329,10 +6364,10 @@ msgstr "Nie można tu przesyłać plików" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Nie można tu przesyłać plików" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Wybierz tło" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Wybierz guild_id lub channel_id, nie oba." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Wybierz Plik, a następnie Dodaj do Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "blok kodu" msgid "Code blocks" msgstr "Bloki kodu" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Kod wygasł" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Polecenia" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Komentarze są wyłączone, ponieważ w co najmniej jednym wybranym kanale włączono tryb wolny. Komentarz zostałby przez niego zablokowany zaraz po przekazaniu wiadomości." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Komentarze są wyłączone, ponieważ w kanale „{forwardChannelDisplayName}” włączono tryb wolny. Komentarz zostałby przez niego zablokowany zaraz po przekazaniu wiadomości." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Komentarze są niedostępne, gdy włączony jest tryb wolny." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Powiadomienia o wiadomościach społeczności" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Pasek z wymogiem MFA dla społeczności" @@ -9182,7 +9222,7 @@ msgstr "Kontynuuj zgłaszanie społeczności" msgid "Continue to report user" msgstr "Kontynuuj zgłaszanie użytkownika" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Kontynuuj z kluczem dostępu" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Koordynuje naloty, zmasowane ataki lub nękanie osób bądź społeczności." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Skopiowano" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kopiuj link do kanału" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kopiuj kod" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Dotychczasowa liczba użyć:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Obecnie ukryty" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Aktualnie wyświetlane" @@ -12555,6 +12595,11 @@ msgstr "Nie blokuje. Możesz ponownie otworzyć później." msgid "Domain" msgstr "Domena" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domena przeniesiona" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Niderlandzki" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "np. YubiKey, iPhone, komputer służbowy" @@ -14056,10 +14101,14 @@ msgstr "Wprowadź kod PIN swojego klucza bezpieczeństwa, aby kontynuować." msgid "Enter the SMS code" msgstr "Wpisz kod SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Wpisz ten kod w przeglądarce, aby dokończyć logowanie." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Wpisz ten kod w swojej starej aplikacji {PRODUCT_NAME}, aby dokończyć logowanie." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "naciśnij Enter, aby <0><1>zapisać" @@ -14441,7 +14490,7 @@ msgstr "Wygasa {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Wygasa między {earliest} a {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Wygasa za {remaining} s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Nie udało się uruchomić SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Nie udało się przełączyć konta" @@ -15413,17 +15462,17 @@ msgstr "Dla mnie" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "W ramach tej weryfikacji wyślesz nam wiadomość SMS, zamiast ją od nas otrzymać." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Dla Twojego bezpieczeństwa potwierdź swoją tożsamość, aby kontynuować." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Wymuszone wyłączenie" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Wymuszone włączenie" @@ -15540,7 +15589,7 @@ msgstr "Nie pamiętasz hasła" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Nie pamiętasz hasła?" @@ -15571,7 +15620,7 @@ msgstr "Przekaż dalej multimedia" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Przekaż wiadomość dalej" @@ -15965,11 +16014,11 @@ msgstr "Wygeneruj link do udostępnienia lub skopiuj swój połączony niestanda msgid "Generate invite link" msgstr "Wygeneruj link z zaproszeniem" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Wygeneruj nowy kod" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Generowanie kodu…" @@ -16045,6 +16094,11 @@ msgstr "Pobierz aplikację komputerową {productName}, aby korzystać z funkcji msgid "Get the desktop app" msgstr "Pobierz aplikację komputerową" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Pobierz nową aplikację {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Wstaw emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Zainstaluj aplikację {productName}, aby uzyskać pełny selektor źródeł i płynniejsze sterowanie udostępnianiem ekranu." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Zainstaluj z menu przeglądarki." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Zainstaluj nową aplikację" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Nieprawidłowe żądanie autoryzacji" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Nieprawidłowe uprawnienia bota." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Nieprawidłowy lub wygasły kod. Spróbuj ponownie." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Nieprawidłowy parametr redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (treści dla dorosłych)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Połącz nowe urządzenie" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Ustawienia wyświetlania wiadomości są dostępne tylko na komputerze." msgid "Message editing" msgstr "Edytowanie wiadomości" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Wiadomość przesłana dalej" @@ -20746,7 +20815,7 @@ msgstr "Przyciski wprowadzania wiadomości" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Wiadomość jest za długa" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Różne" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Brak client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Nazwa musi mieć co najmniej 2 znaki" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Nazwij klucz dostępu" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formularz nazwy klucza dostępu" @@ -22381,7 +22450,7 @@ msgstr "Brak zmian do zapisania" msgid "No channels available" msgstr "Brak dostępnych kanałów" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Nie znaleziono kanałów" @@ -22824,7 +22893,7 @@ msgstr "Nie znaleziono użytkowników" msgid "No verification is required." msgstr "Weryfikacja nie jest wymagana." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Brak dostępnych metod weryfikacji" @@ -23417,6 +23486,16 @@ msgstr "Otwórz {contextName}" msgid "Open {displayName}" msgstr "Otwórz {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Otwórz {host} w Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Otwórz {host} w przeglądarce." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Otwórz {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsNa msgid "Open {productName}" msgstr "Otwórz {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Otwórz {productName} w przeglądarce" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Otwórz opcje banera" msgid "Open bookmarks" msgstr "Otwórz zakładki" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Otwórz przeglądarkę" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Otwórz emoji w przeglądarce" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Otwórz wybór emoji" @@ -23809,6 +23893,16 @@ msgstr "Otwórz poniższy link w przeglądarce, a następnie skopiuj całą odpo msgid "Open the menu and choose an option." msgstr "Otwórz menu i wybierz opcję." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Otwórz nową aplikację i wybierz „Zaloguj się za pomocą starej aplikacji {productName}”. Następnie wybierz „Połącz nowe urządzenie tutaj” i wpisz wyświetlony kod." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Otwórz nową aplikację. Jesteś już zalogowany." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Otwórz czat głosowy" msgid "Open voice & video settings" msgstr "Otwórz ustawienia głosu i wideo" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Otwórz przeglądarkę, zaloguj się, a następnie wprowadź poniższy kod, aby połączyć swoje konto." @@ -23868,6 +23962,10 @@ msgstr "Otwórz przeglądarkę, zaloguj się, a następnie wprowadź poniższy k msgid "Open your messages" msgstr "Otwórz wiadomości" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Otwórz starą aplikację {PRODUCT_NAME} i wybierz „Połącz nowe urządzenie”, a następnie wpisz poniższy kod." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Otwórz aplikację do SMS-ów w telefonie i utwórz nową wiadomość." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Klucz dostępu" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Nazwa klucza dostępu" @@ -28138,7 +28236,7 @@ msgstr "Nasycenie" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Podgląd podczas przewijania" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Zaznacz tekst" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Wybierz konto, na które chcesz się zalogować w aplikacji komputerowej." +msgid "Select the account you want to sign in with on your new device." +msgstr "Wybierz konto, na które chcesz się zalogować na nowym urządzeniu." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Wyślij" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Wyślij ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sesja" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sesja dla {identifier} wygasła. Zaloguj się ponownie." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Skróty klawiszowe są wstrzymane podczas nagrywania." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Skróć wiadomość i spróbuj ponownie." @@ -30217,6 +30315,11 @@ msgstr "Pokaż maks. 3 (znacznik +5)" msgid "Show max 5 (+5 badge)" msgstr "Pokaż maks. 5 (znacznik +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Pokaż mi jak" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Zaloguj się, aby odebrać prezent" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Zaloguj się przez przeglądarkę" @@ -30496,6 +30599,11 @@ msgstr "Zaloguj się przez przeglądarkę" msgid "Sign in with a passkey" msgstr "Zaloguj się za pomocą klucza dostępu" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Zaloguj się za pomocą swojej starej aplikacji {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Dane logowania" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Proces logowania" @@ -31030,7 +31138,7 @@ msgstr "Niektórych użytkowników nie można dodać" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Mów w tym kanale głosowym. Bez tego uprawnienia mikrofony członków p msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Mów w kanałach głosowych. Bez tego uprawnienia mikrofony członków pozostają wyciszone, dopóki ktoś z uprawnieniem „Wyciszanie mikrofonów członków” ich nie włączy." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Stuknij Udostępnij, a następnie Dodaj do ekranu początkowego." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Odłożono weryfikację numeru telefonu. Pozostał jeden etap weryfikacji e-mail." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Podany adres redirect_uri nie jest zarejestrowany dla tej aplikacji." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Podany adres redirect_uri musi używać protokołu HTTP lub HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ten plik animacji jest za długi lub za duży, aby go przyciąć. Spróbuj użyć krótszego lub mniejszego pliku." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Ta aplikacja" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Ten załącznik wygasł" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Ten bot nie jest publiczny i nie może być dodawany przez innych użytkowników." @@ -33757,7 +33870,7 @@ msgstr "Ta grupa ma za dużo osób" msgid "This group is full. Remove someone before adding more people." msgstr "Ta grupa jest pełna. Usuń kogoś, zanim dodasz więcej osób." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Ten kod przekazania wygasł. Wygeneruj nowy, aby kontynuować." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Nieznany" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Nieznana aplikacja, wygasła sesja lub nieprawidłowe żądanie autoryzacji." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Użycia:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Używasz rzeczywistego stanu konta" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Używanie rzeczywistego stanu ekwipunku prezentów" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Używasz rzeczywistego stanu Premium" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Używasz rzeczywistego stanu" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Używasz rzeczywistego stanu weryfikacji" @@ -37947,17 +38060,17 @@ msgstr "Poczekaj tutaj" msgid "Waiting for connection..." msgstr "Oczekiwanie na połączenie…" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Czekam na klucz dostępu…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Oczekiwanie na koniec odstępu między wiadomościami wymaganego przez tryb wolny w kanale „{forwardChannelDisplayName}”." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Oczekiwanie na koniec odstępu między wiadomościami wymaganego przez tryb wolny w co najmniej jednym z wybranych kanałów." @@ -38156,7 +38269,7 @@ msgstr "Zmieniliśmy rozmiar i skompresowaliśmy tę naklejkę do 320x320 piksel msgid "We sent a code to {email}." msgstr "Wysłaliśmy kod na adres {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Wypełnimy wstępnie pole adresem {prefillEmail}, gdy otworzy się logowanie w przeglądarce." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Podglądy stron internetowych" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Witamy ponownie" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Twoja subskrypcja {premiumProductName} wygasła. Wszystkie korzyści z {premiumProductName} zostały wyłączone. Aktywuj subskrypcję ponownie, aby odzyskać dostęp." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Twoja sesja {productName} wygasła. Zaloguj się ponownie, aby kontynuować autoryzację tej aplikacji." @@ -39734,7 +39847,7 @@ msgstr "Twoje konto jest zbyt nowe, by wysyłać wiadomości w tej społecznośc msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Twoja prośba o konto oczekuje na zatwierdzenie. Możesz się zalogować po zatwierdzeniu jej przez administratora." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Twoje konto wymaga uwierzytelniania wieloskładnikowego, ale na tym urządzeniu nie skonfigurowano żadnych obsługiwanych metod. Zaloguj się na urządzeniu z aplikacją uwierzytelniającą lub kluczem bezpieczeństwa albo skontaktuj się z pomocą techniczną." @@ -39757,6 +39870,11 @@ msgstr "Nie udało się wykonać tej czynności, ponieważ nasze systemy bezpiec msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Twoje zgłoszenie oczekuje na rozpatrzenie. Nadal możesz zaktualizować szczegóły wpisu lub wycofać zgłoszenie." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Twoja przeglądarka nie obsługuje kluczy dostępu dla tej domeny. Zaktualizuj przeglądarkę lub zaloguj się przy użyciu hasła i kodu dwuskładnikowego." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Twoja karta spełnia warunki. Przekierowujemy Cię teraz do strony płat msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Twój operator twierdzi, że ten numer jest wyłączony. Sprawdź go i spróbuj ponownie lub skontaktuj się z pomocą techniczną, jeśli numer jest poprawny." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Twój kod" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Twój kod jest gotowy" diff --git a/fluxer_app/src/features/i18n/locales/pt-BR/messages.po b/fluxer_app/src/features/i18n/locales/pt-BR/messages.po index 78c45615e..6ebaf5e8b 100644 --- a/fluxer_app/src/features/i18n/locales/pt-BR/messages.po +++ b/fluxer_app/src/features/i18n/locales/pt-BR/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, =0 {# mensagens bloqueadas} one {# mensagem bloqueada} o msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, =0 {# comunidades encontradas} one {# comunidade encontrada} other {# comunidades encontradas}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, =0 {# comunidades estão temporariamente indisponíveis devido a um defeito no capacitor de fluxo.} one {# comunidade está temporariamente indisponível devido a um defeito no capacitor de fluxo.} other {# comunidades estão temporariamente indisponíveis devido a um defeito no capacitor de fluxo.}}" @@ -1692,6 +1692,26 @@ msgstr "O {productName} detectou um possível ataque nesta comunidade. Os convit msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} detectou um ataque em potencial, então novos usuários não podem entrar agora." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "O {productName} mudou para {host}. Adicione-o à sua Tela de Início ou Dock, depois faça login com este app." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "O {productName} mudou para {host}. Instale-o pelo navegador e depois faça login com este app." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "O {productName} mudou para {host}. Instale o novo app e você já estará conectado." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "O {productName} mudou para {host}. Abra no seu navegador para instalar o novo app." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "O {productName} precisa de acesso ao microfone para listar seus disposit msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} precisa de acesso à gravação de tela. Permita a gravação de tela nas configurações de privacidade do seu sistema operacional e reinicie o aplicativo." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "O {productName} agora está em {host}. Instale-o de lá e depois faça login com este app." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "O {productName} agora está em {host}. Instale-o de lá." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "uma enquete" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Um redirect_uri é obrigatório quando o escopo do bot não é o único escopo." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Adicionar um bot a uma comunidade com as permissões solicitadas" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Adicionar um comentário (opcional)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Adicione uma chave da YouTube Data API para enriquecer os links do YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Adicionar conta" @@ -4379,6 +4409,11 @@ msgstr "Aprovação necessária" msgid "Approve" msgstr "Aprovar" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Aprove este app pelo app {productName} que você já usa. Nenhuma senha é necessária." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Falha na autorização" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Falha na autorização. Tente novamente." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Permissões do bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "As permissões do bot não podem ser negativas." @@ -6329,10 +6364,10 @@ msgstr "Não é possível enviar arquivos aqui" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Não é possível enviar arquivos aqui" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Escolher plano de fundo" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Escolha guild_id ou channel_id, não ambos." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Escolha Arquivo e depois Adicionar à Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "bloco de código" msgid "Code blocks" msgstr "Blocos de código" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Código expirado" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Comandos" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Os comentários estão desativados porque um ou mais canais selecionados estão com o modo lento ativado. O comentário seria bloqueado pelo modo lento logo após o encaminhamento." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Os comentários estão desativados porque o modo lento está ativado em {forwardChannelDisplayName}. O comentário seria bloqueado pelo modo lento logo após o encaminhamento." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Os comentários não estão disponíveis enquanto o modo lento estiver ativado." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Notificações de mensagens da comunidade" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Barra de aviso de exigência de MFA na comunidade" @@ -9182,7 +9222,7 @@ msgstr "Continuar para denunciar a comunidade" msgid "Continue to report user" msgstr "Continuar para denunciar o usuário" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Continuar com chave de acesso" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coordena invasões, ataques em massa ou assédio contra pessoas ou comunidades." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Copiado" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Copiar link do canal" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Copiar código" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Usos atuais:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Atualmente dispensado" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Exibindo agora" @@ -12555,6 +12595,11 @@ msgstr "Não bloqueia. Você pode reabrir depois." msgid "Domain" msgstr "Domínio" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domínio movido na barra de notificações" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Holandês" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "ex: YubiKey, iPhone, computador do trabalho" @@ -14056,10 +14101,14 @@ msgstr "Digite o PIN da sua chave de segurança para continuar." msgid "Enter the SMS code" msgstr "Insira o código SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Insira este código no seu navegador para concluir o login." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Digite este código no seu app antigo do {PRODUCT_NAME} para concluir o login." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Pressione Enter para <0><1>salvar" @@ -14441,7 +14490,7 @@ msgstr "Expira em {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Expira entre {earliest} e {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Expira em {remaining}s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Falha ao iniciar o SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Não foi possível trocar de conta" @@ -15413,17 +15462,17 @@ msgstr "Para mim" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Para esta verificação, você nos envia uma mensagem de texto em vez de receber uma nossa." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Para sua segurança, confirme que é você para continuar." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Forçar desativação" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Forçar ativação" @@ -15540,7 +15589,7 @@ msgstr "Esqueceu a senha" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Esqueceu a senha?" @@ -15571,7 +15620,7 @@ msgstr "Encaminhar mídia" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Encaminhar mensagem" @@ -15965,11 +16014,11 @@ msgstr "Gerar um link compartilhável ou copiar seu CSS personalizado combinado. msgid "Generate invite link" msgstr "Gerar link de convite" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Gerar novo código" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Gerando código…" @@ -16045,6 +16094,11 @@ msgstr "Baixe o aplicativo do {productName} para computador e use \"Apertar para msgid "Get the desktop app" msgstr "Baixar o app para computador" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Baixe o novo app {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Inserir emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Instale o {productName} para ter o seletor de fonte completo e controles de compartilhamento de tela mais fluidos." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Instale-o pelo menu do seu navegador." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Instalar o novo app" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Solicitação de autorização inválida" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Permissões de bot inválidas." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Código inválido ou expirado. Tente novamente." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "redirect_uri inválido" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (conteúdo adulto)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Vincular um novo dispositivo" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "As configurações de exibição de mensagens estão disponíveis apenas msgid "Message editing" msgstr "Edição de mensagem" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Mensagem encaminhada" @@ -20746,7 +20815,7 @@ msgstr "Botões de entrada de mensagem" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "A mensagem é muito longa" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Diversos" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id ausente" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "O nome deve ter pelo menos 2 caracteres" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Nomear chave de acesso" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formulário de nome da chave de acesso" @@ -22381,7 +22450,7 @@ msgstr "Nenhuma alteração para salvar" msgid "No channels available" msgstr "Nenhum canal disponível" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Nenhum canal encontrado" @@ -22824,7 +22893,7 @@ msgstr "Nenhum usuário encontrado" msgid "No verification is required." msgstr "Nenhuma verificação é necessária." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Nenhum método de verificação disponível" @@ -23417,6 +23486,16 @@ msgstr "Abrir {contextName}" msgid "Open {displayName}" msgstr "Abrir {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Abra {host} no Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Abra {host} no seu navegador." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Abra {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Abrir {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Abrir {productName} no seu navegador" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Abrir opções do banner" msgid "Open bookmarks" msgstr "Abrir mensagens salvas" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Abrir navegador" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Abrir emoji no navegador" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Abrir seletor de emojis" @@ -23809,6 +23893,16 @@ msgstr "Abra o link abaixo no seu navegador, depois copie a resposta JSON inteir msgid "Open the menu and choose an option." msgstr "Abra o menu e escolha uma opção." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Abra o novo app e escolha Entrar com seu app {productName} antigo. Em seguida, escolha Vincular um novo dispositivo aqui e insira o código exibido." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Abra o novo app. Você já está conectado." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Abrir chat de voz" msgid "Open voice & video settings" msgstr "Abrir configurações de voz e vídeo" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Abra seu navegador, faça login e insira o código abaixo para vincular sua conta." @@ -23868,6 +23962,10 @@ msgstr "Abra seu navegador, faça login e insira o código abaixo para vincular msgid "Open your messages" msgstr "Abra suas mensagens" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Abra seu app antigo do {PRODUCT_NAME} e escolha \"Vincular um novo dispositivo\", depois insira o código abaixo." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Abra o app de mensagens do seu celular e crie uma nova mensagem de texto." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Chave de acesso" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Nome da chave de acesso" @@ -28138,7 +28236,7 @@ msgstr "Saturação" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Prévia ao percorrer o vídeo" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Selecionar texto" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Selecione a conta com a qual você quer entrar no aplicativo para computador." +msgid "Select the account you want to sign in with on your new device." +msgstr "Selecione a conta com a qual você deseja entrar no seu novo dispositivo." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Enviar" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Enviar ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sessão" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "A sessão de {identifier} expirou. Faça login novamente." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Os atalhos ficam pausados durante a gravação." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Encurte a mensagem e tente novamente." @@ -30217,6 +30315,11 @@ msgstr "Mostrar no máximo 3 (indicador +5)" msgid "Show max 5 (+5 badge)" msgstr "Mostrar no máximo 5 (indicador +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Mostre como" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Faça login para resgatar seu presente" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Entrar pelo navegador" @@ -30496,6 +30599,11 @@ msgstr "Entrar pelo navegador" msgid "Sign in with a passkey" msgstr "Entrar com uma chave de acesso" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Faça login com seu app antigo do {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Detalhes de login" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Fluxo de login" @@ -31030,7 +31138,7 @@ msgstr "Alguns usuários não podem ser adicionados" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Falar neste canal de voz. Sem esta permissão, os membros permanecem sil msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Falar em canais de voz. Sem esta permissão, os membros permanecem silenciados até que alguém com a permissão \"Silenciar membros\" reative seus microfones." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Toque em Compartilhar e, em seguida, em Adicionar à Tela de Início." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "A verificação do telefone foi adiada. Falta uma etapa por e-mail." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "O redirect_uri fornecido não está registrado para este aplicativo." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "O redirect_uri fornecido precisa usar HTTP ou HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Esta animação é muito longa ou grande para ser cortada. Tente um arquivo menor ou mais curto." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Este aplicativo" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Este anexo expirou" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Este bot não é público e não pode ser adicionado por outros usuários." @@ -33757,7 +33870,7 @@ msgstr "Este grupo tem pessoas demais" msgid "This group is full. Remove someone before adding more people." msgstr "Este grupo está cheio. Remova alguém antes de adicionar mais pessoas." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Este código de transferência expirou. Gere um novo para continuar." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Desconhecido" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplicativo desconhecido, sessão expirada ou solicitação de autorização inválida." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Usos:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Usando o estado real da conta" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Usando o status real do inventário de presentes" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Usando o status premium real" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Usando o estado real" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Usando o estado de verificação real" @@ -37947,17 +38060,17 @@ msgstr "Aguarde" msgid "Waiting for connection..." msgstr "Aguardando conexão..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Aguardando a chave de acesso…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Aguardando o modo lento em {forwardChannelDisplayName} expirar." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Aguardando o modo lento expirar em um ou mais canais selecionados." @@ -38156,7 +38269,7 @@ msgstr "Redimensionamos e compactamos esta figurinha para 320x320 pixels, mas el msgid "We sent a code to {email}." msgstr "Enviamos um código para {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Vamos preencher automaticamente {prefillEmail} assim que o login no navegador abrir." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Pré-visualizações de sites" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Boas-vindas de volta" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Sua assinatura {premiumProductName} expirou. Você perdeu todos os benefícios do {premiumProductName}. Reative sua assinatura para ter acesso novamente." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Sua sessão do {productName} expirou. Faça login novamente para continuar autorizando este aplicativo." @@ -39734,7 +39847,7 @@ msgstr "Sua conta é muito nova para enviar mensagens nesta comunidade." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Sua solicitação de conta está aguardando aprovação. Você poderá entrar depois que um administrador aprová-la." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Sua conta exige autenticação multifator, mas nenhum método compatível está configurado neste dispositivo. Faça login em um dispositivo com seu aplicativo autenticador ou chave de segurança, ou entre em contato com o suporte." @@ -39757,6 +39870,11 @@ msgstr "Não foi possível concluir sua ação porque ela foi sinalizada pelos n msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Sua inscrição está aguardando análise. Você ainda pode atualizar os detalhes da página da comunidade ou retirar a inscrição." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Seu navegador não oferece suporte a chaves de acesso neste domínio. Atualize seu navegador ou faça login com sua senha e código de dois fatores." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Seu cartão é elegível. Redirecionando para a página de pagamento." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Sua operadora informa que este número não está em serviço. Verifique-o e tente novamente, ou entre em contato com o suporte se estiver correto." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Seu código" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Seu código está pronto" diff --git a/fluxer_app/src/features/i18n/locales/ro/messages.po b/fluxer_app/src/features/i18n/locales/ro/messages.po index c97f7051b..a0d72befc 100644 --- a/fluxer_app/src/features/i18n/locales/ro/messages.po +++ b/fluxer_app/src/features/i18n/locales/ro/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# mesaj blocat} few {# mesaje blocate} other {# de msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# comunitate găsită} few {# comunități găsite} other {# de comunități găsite}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# comunitate este temporar indisponibilă din cauza unei defecțiuni a condensatorului de flux.} few {# comunități sunt temporar indisponibile din cauza unei defecțiuni a condensatorului de flux.} other {# de comunități sunt temporar indisponibile din cauza unei defecțiuni a condensatorului de flux.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} a detectat un potențial raid în această comunitate. Inv msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} a detectat un potențial raid, așa că utilizatorii noi nu se pot alătura momentan." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} s-a mutat pe {host}. Adaugă-l pe ecranul principal sau în Dock, apoi conectează-te cu această aplicație." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} s-a mutat pe {host}. Instalează-l din browser, apoi conectează-te cu această aplicație." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} s-a mutat pe {host}. Instalează noua aplicație și vei fi deja conectat." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} s-a mutat pe {host}. Deschide-l în browser pentru a instala noua aplicație." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} are nevoie de acces la microfon pentru a-ți afișa dispoz msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} are nevoie de acces la înregistrarea ecranului. Permite înregistrarea ecranului în setările de confidențialitate ale sistemului de operare și repornește aplicația." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} se găsește acum la {host}. Instalează-l de acolo, apoi conectează-te cu această aplicație." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} se află acum la adresa {host}. Instalează-l de acolo." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "un sondaj" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Este necesar un redirect_uri atunci când domeniul bot nu este singurul domeniu." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Adaugă un bot într-o comunitate cu permisiunile solicitate" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Adaugă un comentariu (opțional)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Adaugă o cheie API YouTube Data pentru a îmbogăți linkurile YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Adaugă cont" @@ -4379,6 +4409,11 @@ msgstr "Aprobare necesară" msgid "Approve" msgstr "Aprobă" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Aprobă această aplicație din aplicația {productName} pe care o folosești deja. Nu este necesară parola." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Autorizarea a eșuat" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Autorizarea a eșuat. Încearcă din nou." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Permisiuni bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Permisiunile botului nu pot fi negative." @@ -6329,10 +6364,10 @@ msgstr "Nu se pot încărca fișiere aici" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Nu se pot încărca fișiere aici" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Alege fundalul" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Alege fie guild_id, fie channel_id, nu ambele." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Alege Fișier, apoi Adaugă în Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "bloc de cod" msgid "Code blocks" msgstr "Blocuri de cod" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Cod expirat" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Comenzi" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Comentariile sunt dezactivate deoarece unul sau mai multe canale selectate au modul lent activat. Comentariul ar fi blocat de modul lent imediat după redirecționare." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Comentariile sunt dezactivate deoarece modul lent este activat în {forwardChannelDisplayName}. Comentariul ar fi blocat de modul lent imediat după redirecționare." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Comentariile nu sunt disponibile când modul lent este activat." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Notificări pentru mesajele comunității" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Bara de notificare pentru cerința MFA a comunității" @@ -9182,7 +9222,7 @@ msgstr "Continuă cu raportarea comunității" msgid "Continue to report user" msgstr "Continuă cu raportarea utilizatorului" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Continuă cu cheia de acces" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Coordonează raiduri, atacuri în grup sau hărțuire împotriva unor persoane sau comunități." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Copiat" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Copiază linkul canalului" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Copiază codul" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Utilizări curente:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Ascuns în prezent" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Afișat în prezent" @@ -12555,6 +12595,11 @@ msgstr "Nu blochează. Poți redeschide mai târziu." msgid "Domain" msgstr "Domeniu" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domeniul s-a mutat" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Olandeză" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "de ex.: YubiKey, iPhone, computer de serviciu" @@ -14056,10 +14101,14 @@ msgstr "Introdu codul PIN al cheii tale de securitate pentru a continua." msgid "Enter the SMS code" msgstr "Introdu codul SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Introdu acest cod în browser pentru a finaliza conectarea." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Introdu acest cod în aplicația ta veche {PRODUCT_NAME} pentru a finaliza autentificarea." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter pentru a <0><1>salva" @@ -14441,7 +14490,7 @@ msgstr "Expiră la {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Expiră între {earliest} și {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Expiră în {remaining}s" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Nu s-a putut iniția autentificarea SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Nu s-a putut schimba contul" @@ -15413,17 +15462,17 @@ msgstr "Pentru mine" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Pentru această verificare, ne trimiți un mesaj text în loc să primești unul de la noi." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Pentru siguranța ta, te rugăm să confirmi că ești tu pentru a continua." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Dezactivat forțat" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Activat forțat" @@ -15540,7 +15589,7 @@ msgstr "Am uitat parola" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Ai uitat parola?" @@ -15571,7 +15620,7 @@ msgstr "Redirecționează media" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Redirecționează mesajul" @@ -15965,11 +16014,11 @@ msgstr "Generează un link partajabil sau copiază CSS-ul tău personalizat comb msgid "Generate invite link" msgstr "Generează link de invitație" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generează cod nou" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Se generează codul…" @@ -16045,6 +16094,11 @@ msgstr "Obține aplicația desktop {productName} pentru funcția push-to-talk la msgid "Get the desktop app" msgstr "Descarcă aplicația pentru desktop" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Descarcă noua aplicație {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Inserează emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Instalează {productName} pentru selectorul complet de surse și controale mai fluide pentru partajarea ecranului." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Instalează-l din meniul browserului tău." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Instalează aplicația nouă" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Solicitare de autorizare nevalidă" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Permisiuni bot nevalide." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Cod nevalid sau expirat. Încearcă din nou." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "redirect_uri nevalid" @@ -19332,6 +19396,11 @@ msgstr "Link" msgid "Link (mature)" msgstr "Link (matur)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Conectează un dispozitiv nou" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Setările de afișare a mesajelor sunt disponibile doar pe desktop." msgid "Message editing" msgstr "Editare mesaj" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Mesaj redirecționat" @@ -20746,7 +20815,7 @@ msgstr "Butoane de introducere a mesajului" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Mesajul este prea lung" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Diverse" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Lipsește client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Numele trebuie să aibă cel puțin 2 caractere" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Denumește cheia de acces" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formular de denumire a cheii de acces" @@ -22381,7 +22450,7 @@ msgstr "Nicio modificare de salvat" msgid "No channels available" msgstr "Niciun canal disponibil" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Nu s-au găsit canale" @@ -22824,7 +22893,7 @@ msgstr "Nu s-au găsit utilizatori" msgid "No verification is required." msgstr "Nu este necesară verificarea." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Nicio metodă de verificare disponibilă" @@ -23417,6 +23486,16 @@ msgstr "Deschide {contextName}" msgid "Open {displayName}" msgstr "Deschide {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Deschide {host} în Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Deschide {host} în browserul tău." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Deschide {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsN msgid "Open {productName}" msgstr "Deschide {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Deschide {productName} în browserul tău" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Deschide opțiunile bannerului" msgid "Open bookmarks" msgstr "Deschide mesajele salvate" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Deschide browserul" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Deschide emoji în browser" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Deschide selectorul de emoji" @@ -23809,6 +23893,16 @@ msgstr "Deschide linkul de mai jos în browser, apoi copiază întregul răspuns msgid "Open the menu and choose an option." msgstr "Deschide meniul și alege o opțiune." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Deschide noua aplicație și alege Autentificare cu vechea aplicație {productName}. Apoi alege Conectează un dispozitiv nou aici și introdu codul afișat." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Deschide noua aplicație. Ești deja conectat." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Deschide conversația vocală" msgid "Open voice & video settings" msgstr "Deschide setările de voce și video" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Deschide browserul, conectează-te, apoi introdu codul de mai jos pentru a-ți asocia contul." @@ -23868,6 +23962,10 @@ msgstr "Deschide browserul, conectează-te, apoi introdu codul de mai jos pentru msgid "Open your messages" msgstr "Deschide mesajele" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Deschide vechiul tău aplicație {PRODUCT_NAME} și alege Conectează un dispozitiv nou, apoi introdu codul de mai jos." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Deschide aplicația de mesagerie a telefonului și creează un mesaj text nou." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Cheie de acces" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Numele cheii de acces" @@ -28138,7 +28236,7 @@ msgstr "Saturație" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Previzualizare la glisare" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Selectează textul" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Selectează contul cu care vrei să te conectezi în aplicația desktop." +msgid "Select the account you want to sign in with on your new device." +msgstr "Selectează contul cu care vrei să te conectezi pe noul tău dispozitiv." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Trimite" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Trimite ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Sesiune" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sesiunea a expirat pentru {identifier}. Conectează-te din nou." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Comenzile rapide sunt suspendate în timpul înregistrării." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Scurtează mesajul și încearcă din nou." @@ -30217,6 +30315,11 @@ msgstr "Afișează max. 3 (insignă +5)" msgid "Show max 5 (+5 badge)" msgstr "Afișează max. 5 (insignă +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Arată-mi cum" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Conectează-te pentru a-ți revendica cadoul" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Conectează-te prin browser" @@ -30496,6 +30599,11 @@ msgstr "Conectează-te prin browser" msgid "Sign in with a passkey" msgstr "Conectează-te cu o cheie de acces" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Conectează-te cu vechea ta aplicație {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Detalii de conectare" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Flux de conectare" @@ -31030,7 +31138,7 @@ msgstr "Unii utilizatori nu pot fi adăugați" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Vorbește în acest canal vocal. Fără această permisiune, membrii ră msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Vorbește în canalele vocale. Fără această permisiune, membrii rămân cu microfonul dezactivat până când cineva cu permisiunea „Dezactivează microfonul membrilor” le activează microfonul." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Atingeți Partajare, apoi Adăugați pe ecranul principal." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Verificarea telefonică a fost amânată. A mai rămas un pas cu e-mailul." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Adresa redirect_uri furnizată nu este înregistrată pentru această aplicație." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Adresa redirect_uri furnizată trebuie să utilizeze HTTP sau HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Animația este prea lungă sau prea mare pentru a fi decupată. Încearcă un fișier mai scurt sau mai mic." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Această aplicație" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Acest atașament a expirat" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Acest bot nu este public și nu poate fi adăugat de alți utilizatori." @@ -33757,7 +33870,7 @@ msgstr "Acest grup are prea multe persoane" msgid "This group is full. Remove someone before adding more people." msgstr "Acest grup este plin. Elimină pe cineva înainte de a adăuga mai multe persoane." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Acest cod de transfer a expirat. Generează unul nou pentru a continua." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Necunoscut" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplicație necunoscută, sesiune expirată sau solicitare de autorizare nevalidă." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Utilizări:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Se folosește starea reală a contului" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Se folosește starea reală a inventarului de cadouri" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Se folosește starea reală de premium" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Se folosește starea reală" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Se folosește starea reală de verificare" @@ -37947,17 +38060,17 @@ msgstr "Așteaptă aici" msgid "Waiting for connection..." msgstr "Se așteaptă conexiunea..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Se așteaptă cheia de acces…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Se așteaptă expirarea modului lent în {forwardChannelDisplayName}." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Se așteaptă expirarea modului lent în unul sau mai multe canale selectate." @@ -38156,7 +38269,7 @@ msgstr "Am redimensionat și am comprimat acest sticker la 320x320 pixeli, dar a msgid "We sent a code to {email}." msgstr "Am trimis un cod la {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Vom precompleta {prefillEmail} odată ce se deschide autentificarea în browser." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Previzualizări site-uri web" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Bine ai revenit" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Abonamentul tău {premiumProductName} a expirat. Ai pierdut toate beneficiile {premiumProductName}. Reactivează-ți abonamentul pentru a recăpăta accesul." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Sesiunea ta {productName} a expirat. Conectează-te din nou pentru a continua autorizarea acestei aplicații." @@ -39734,7 +39847,7 @@ msgstr "Contul tău este prea nou pentru a trimite mesaje în această comunitat msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Solicitarea ta de cont așteaptă aprobare. Te poți conecta după ce un administrator o aprobă." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Contul tău necesită autentificare multi-factor, dar nu sunt configurate metode acceptate pe acest dispozitiv. Conectează-te pe un dispozitiv cu aplicația de autentificare sau cu cheia de securitate, sau contactează asistența." @@ -39757,6 +39870,11 @@ msgstr "Acțiunea ta nu a putut fi finalizată deoarece a fost semnalată de sis msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Cererea ta este în curs de examinare. Poți actualiza în continuare detaliile listării sau poți retrage cererea." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Browserul tău nu acceptă chei de acces pe acest domeniu. Actualizează-ți browserul sau conectează-te cu parola și codul de doi factori." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Cardul tău este eligibil. Te redirecționăm acum către pagina de plat msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Operatorul tău spune că acest număr nu este activ. Verifică-l și încearcă din nou sau contactează asistența dacă este corect." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Codul tău" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Codul tău este gata" diff --git a/fluxer_app/src/features/i18n/locales/ru/messages.po b/fluxer_app/src/features/i18n/locales/ru/messages.po index f3b717333..9ffd9df6d 100644 --- a/fluxer_app/src/features/i18n/locales/ru/messages.po +++ b/fluxer_app/src/features/i18n/locales/ru/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# заблокированное сообщение msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Найдено # сообщество} few {Найдено # сообщества} many {Найдено # сообществ} other {Найдено # сообщества}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# сообщество временно недоступно из-за неисправности потокового накопителя.} few {# сообщества временно недоступны из-за неисправности потокового накопителя.} many {# сообществ временно недоступны из-за неисправности потокового накопителя.} other {# сообществ временно недоступны из-за неисправности потокового накопителя.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} обнаружил в этом сообществе пот msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} обнаружил потенциальный рейд, поэтому новые пользователи пока не могут присоединиться." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} перемещён на {host}. Добавьте его на главный экран или в Dock, а затем войдите через это приложение." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} переехал на {host}. Установите его из браузера, а затем войдите в это приложение." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} переехал на {host}. Установите новое приложение, и вы уже будете авторизованы." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} переехал на {host}. Откройте в браузере, чтобы установить новое приложение." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} требуется доступ к микрофону, ч msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} требуется доступ к записи экрана. Разрешите запись экрана в настройках конфиденциальности вашей операционной системы и перезапустите приложение." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} теперь находится по адресу {host}. Установите его оттуда, а затем войдите в систему с помощью этого приложения." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} теперь находится по адресу {host}. Установите его оттуда." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "опрос" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Параметр redirect_uri обязателен, если область действия bot не единственная." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Добавить бота в сообщество с запрошенными разрешениями" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Добавить комментарий (необязательно)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Добавьте API-ключ YouTube Data, чтобы дополнять ссылки на YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Добавить аккаунт" @@ -4379,6 +4409,11 @@ msgstr "Требуется одобрение" msgid "Approve" msgstr "Одобрить" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Подтвердите это приложение из приложения {productName}, которое вы уже используете. Пароль не требуется." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Не удалось авторизоваться" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Не удалось авторизоваться. Повторите попытку." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Разрешения бота" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Разрешения бота не могут быть отрицательными." @@ -6329,10 +6364,10 @@ msgstr "Сюда нельзя загружать файлы" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Сюда нельзя загружать файлы" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Выбрать фон" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Выберите либо guild_id, либо channel_id, но не оба сразу." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Выберите «Файл», затем «Добавить в Dock»." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "блок кода" msgid "Code blocks" msgstr "Блоки кода" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Срок действия кода истёк" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Команды" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Комментарии отключены, так как в одном или нескольких выбранных каналах включен медленный режим. Комментарий будет заблокирован медленным режимом сразу после пересылки." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Комментарии отключены, так как в канале «{forwardChannelDisplayName}» включен медленный режим. Комментарий будет заблокирован медленным режимом сразу после пересылки." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Комментарии недоступны, пока включён медленный режим." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Уведомления о сообщениях в сообществах" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Баннер с требованием MFA для сообщества" @@ -9182,7 +9222,7 @@ msgstr "Перейти к жалобе на сообщество" msgid "Continue to report user" msgstr "Перейти к жалобе на пользователя" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Продолжить с ключом доступа" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Координирует рейды, травлю или преследование людей или сообществ." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Скопировано" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Скопировать ссылку на канал" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Скопировать код" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Использований:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Сейчас скрыт" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Сейчас показывается" @@ -12555,6 +12595,11 @@ msgstr "Не блокирует. Можно будет открыть позже msgid "Domain" msgstr "Домен" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Домен перемещен" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Нидерландский" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "напр., YubiKey, iPhone, рабочий компьютер" @@ -14056,10 +14101,14 @@ msgstr "Введите PIN-код ключа безопасности, чтоб msgid "Enter the SMS code" msgstr "Введите код из SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Введите этот код в браузере, чтобы завершить вход." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Введите этот код в старом приложении {PRODUCT_NAME}, чтобы завершить вход." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Enter, чтобы <0><1>сохранить" @@ -14441,7 +14490,7 @@ msgstr "Истекает {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Истекает между {earliest} и {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Истекает через {remaining} с" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Не удалось запустить SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Не удалось сменить аккаунт" @@ -15413,17 +15462,17 @@ msgstr "Для меня" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Для этой проверки СМС отправляете нам вы, а не мы вам." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Для вашей безопасности подтвердите, что это вы, чтобы продолжить." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Принудительно отключено" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Принудительно включено" @@ -15540,7 +15589,7 @@ msgstr "Восстановление пароля" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Забыли пароль?" @@ -15571,7 +15620,7 @@ msgstr "Переслать медиафайл" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Переслать сообщение" @@ -15965,11 +16014,11 @@ msgstr "Создайте ссылку для публикации или ско msgid "Generate invite link" msgstr "Сгенерировать ссылку-приглашение" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Сгенерировать новый код" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Генерируем код…" @@ -16045,6 +16094,11 @@ msgstr "Установите приложение {productName} для ПК, ч msgid "Get the desktop app" msgstr "Скачать приложение для компьютера" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Скачайте новое приложение {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Вставить эмодзи" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Установите {productName}, чтобы получить полный выбор источников и более плавное управление демонстрацией экрана." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Установите его из меню браузера." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Установить новое приложение" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Недействительный запрос авторизации" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Недействительные разрешения для бота." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Неверный или истёкший код. Попробуйте ещё раз." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Недействительный redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Ссылка" msgid "Link (mature)" msgstr "Ссылка (18+)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Связать новое устройство" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Настройки отображения сообщений досту msgid "Message editing" msgstr "Редактирование сообщений" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Сообщение переслано" @@ -20746,7 +20815,7 @@ msgstr "Кнопки ввода сообщения" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Сообщение слишком длинное" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Разное" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Отсутствует client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Название должно содержать не менее 2 символов" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Назвать ключ доступа" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Форма для названия ключа доступа" @@ -22381,7 +22450,7 @@ msgstr "Нет изменений для сохранения" msgid "No channels available" msgstr "Нет доступных каналов" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Каналы не найдены" @@ -22824,7 +22893,7 @@ msgstr "Пользователи не найдены" msgid "No verification is required." msgstr "Проверка не требуется." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Нет доступных способов подтверждения" @@ -23417,6 +23486,16 @@ msgstr "Открыть «{contextName}»" msgid "Open {displayName}" msgstr "Открыть {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Откройте {host} в Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Откройте {host} в браузере." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Откройте «{macosSystemSettingsName}» → «{macosPrivacyAndSec msgid "Open {productName}" msgstr "Открыть {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Открыть {productName} в браузере" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Открыть параметры баннера" msgid "Open bookmarks" msgstr "Открыть закладки" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Открыть браузер" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Открыть эмодзи в браузере" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Открыть выбор эмодзи" @@ -23809,6 +23893,16 @@ msgstr "Откройте ссылку ниже в браузере, затем msgid "Open the menu and choose an option." msgstr "Откройте меню и выберите один из вариантов." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Откройте новое приложение и выберите «Войти через старое приложение {productName}». Затем выберите «Связать новое устройство» и введите отображаемый код." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Откройте новое приложение. Вы уже вошли в систему." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Открыть голосовой чат" msgid "Open voice & video settings" msgstr "Открыть настройки аудио и видео" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Откройте браузер, войдите в аккаунт, затем введите код ниже, чтобы привязать свой аккаунт." @@ -23868,6 +23962,10 @@ msgstr "Откройте браузер, войдите в аккаунт, за msgid "Open your messages" msgstr "Откройте сообщения" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Откройте старое приложение {PRODUCT_NAME} и выберите «Привязать новое устройство», затем введите код ниже." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Откройте приложение для обмена сообщениями на телефоне и создайте новое текстовое сообщение." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Ключ доступа" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Название ключа доступа" @@ -28138,7 +28236,7 @@ msgstr "Насыщенность" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Предпросмотр при перемотке" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Выделить текст" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Выберите аккаунт, с которым хотите войти в приложение на компьютере." +msgid "Select the account you want to sign in with on your new device." +msgstr "Выберите аккаунт, с которым хотите войти на новом устройстве." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Отправить" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Отправить ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Сессия" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Срок действия сессии для {identifier} истёк. Войдите снова." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Во время записи сочетания клавиш не срабатывают." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Сократите сообщение и попробуйте снова." @@ -30217,6 +30315,11 @@ msgstr "Показывать максимум 3 (значок +5)" msgid "Show max 5 (+5 badge)" msgstr "Показывать максимум 5 (значок +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Показать как" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Войдите, чтобы получить подарок" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Войти через браузер" @@ -30496,6 +30599,11 @@ msgstr "Войти через браузер" msgid "Sign in with a passkey" msgstr "Войти с помощью ключа доступа" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Войдите с помощью вашего старого приложения {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Данные для входа" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Вход в аккаунт" @@ -31030,7 +31138,7 @@ msgstr "Некоторых пользователей нельзя добави #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Говорите в этом голосовом канале. Без э msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Говорите в голосовых каналах. Без этого разрешения микрофон участников остаётся выключенным, пока кто-то с разрешением «Отключать микрофон участникам» не включит его." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Нажмите «Поделиться», затем «Добавить на главный экран»." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Проверка телефона отложена. Остался один шаг: подтверждение почты." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Указанный redirect_uri не зарегистрирован для этого приложения." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Указанный redirect_uri должен использовать HTTP или HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Этот анимированный файл слишком длинный или большой для обрезки. Попробуйте более короткий или меньший файл." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Это приложение" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Срок действия этого вложения истёк" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Этот бот не является публичным, и другие пользователи не могут его добавить." @@ -33757,7 +33870,7 @@ msgstr "В этой группе слишком много участников" msgid "This group is full. Remove someone before adding more people." msgstr "Эта группа заполнена. Удалите кого-нибудь, прежде чем добавлять новых участников." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Срок действия этого кода истёк. Создайте новый, чтобы продолжить." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Неизвестно" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Неизвестное приложение, просроченная сессия или недействительный запрос авторизации." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Число использований:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Используется фактическое состояние аккаунта" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Используется фактическое состояние инвентаря подарков" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Используется фактическое состояние премиум-подписки" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Используется фактическое состояние" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Используется фактическое состояние проверки" @@ -37947,17 +38060,17 @@ msgstr "Подождите" msgid "Waiting for connection..." msgstr "Ожидание подключения..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Ожидание ключа доступа…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Ожидание окончания медленного режима в канале «{forwardChannelDisplayName}»." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Ожидание окончания медленного режима в одном или нескольких выбранных каналах." @@ -38156,7 +38269,7 @@ msgstr "Мы изменили размер и сжали этот стикер msgid "We sent a code to {email}." msgstr "Мы отправили код на {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Мы автоматически заполним поле с адресом {prefillEmail}, как только откроется окно входа в браузере." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Предпросмотр ссылок" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "С возвращением" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Срок действия вашей подписки {premiumProductName} истёк. Вы потеряли все привилегии {premiumProductName}. Чтобы восстановить доступ, продлите подписку." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Срок действия вашей сессии {productName} истёк. Войдите снова, чтобы продолжить авторизацию этого приложения." @@ -39734,7 +39847,7 @@ msgstr "Ваш аккаунт слишком новый, чтобы отправ msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Ваш запрос на создание аккаунта ожидает одобрения. Вы сможете войти после одобрения администратором." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Для вашего аккаунта требуется многофакторная аутентификация, но на этом устройстве не настроены поддерживаемые методы. Войдите на устройстве с приложением-аутентификатором или ключом безопасности либо обратитесь в службу поддержки." @@ -39757,6 +39870,11 @@ msgstr "Ваше действие не удалось выполнить, так msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Ваша заявка находится на рассмотрении. Вы можете обновить данные объявления или отозвать заявку." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Ваш браузер не поддерживает ключи доступа для этого домена. Обновите браузер или войдите с помощью пароля и двухфакторного кода." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Ваша карта подходит. Перенаправляем ва msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Ваш оператор сообщил, что номер не обслуживается. Проверьте его и попробуйте снова или обратитесь в службу поддержки, если номер указан верно." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Ваш код" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Ваш код готов" diff --git a/fluxer_app/src/features/i18n/locales/sv-SE/messages.po b/fluxer_app/src/features/i18n/locales/sv-SE/messages.po index dc6838b40..0022dbdab 100644 --- a/fluxer_app/src/features/i18n/locales/sv-SE/messages.po +++ b/fluxer_app/src/features/i18n/locales/sv-SE/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# blockerat meddelande} other {# blockerade meddela msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community hittades} other {# communities hittades}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community är tillfälligt otillgänglig på grund av ett fel i flödeskondensatorn.} other {# communities är tillfälligt otillgängliga på grund av ett fel i flödeskondensatorn.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} upptäckte en potentiell räd i den här communityn. Inbju msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} upptäckte en potentiell räd, så nya användare kan inte gå med just nu." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} har flyttat till {host}. Lägg till det på hemskärmen eller i Dock, logga sedan in med den här appen." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} har flyttat till {host}. Installera det från din webbläsare och logga sedan in med den här appen." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} har flyttat till {host}. Installera den nya appen så är du redan inloggad." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} har flyttat till {host}. Öppna i webbläsaren för att installera den nya appen." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} behöver mikrofonåtkomst för att visa dina enheter." msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} behöver åtkomst till skärminspelning. Tillåt skärminspelning i operativsystemets sekretessinställningar och starta om appen." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} finns nu på {host}. Installera det därifrån och logga sedan in med den här appen." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} finns nu på {host}. Installera det därifrån." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "en omröstning" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri krävs när bot inte är det enda begärda behörighetsomfånget." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Lägg till en bot i en community med begärda behörigheter" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Lägg till en kommentar (valfritt)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Lägg till en YouTube Data API-nyckel för att visa utökade förhandsvisningar av YouTube-länkar." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Lägg till konto" @@ -4379,6 +4409,11 @@ msgstr "Godkännande krävs" msgid "Approve" msgstr "Godkänn" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Godkänn den här appen från {productName}-appen som du redan använder. Inget lösenord behövs." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Auktorisering misslyckades" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Auktoriseringen misslyckades. Försök igen." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Botbehörigheter" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Botbehörigheter kan inte vara negativa." @@ -6329,10 +6364,10 @@ msgstr "Kan inte ladda upp filer här" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Kan inte ladda upp filer här" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Välj bakgrund" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Välj antingen guild_id eller channel_id, inte båda." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Välj Arkiv och sedan Lägg till i Docka." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "kodblock" msgid "Code blocks" msgstr "Kodblock" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Koden har gått ut" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Kommandon" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Kommentarer är inaktiverade eftersom en eller flera valda kanaler har långsamt läge aktiverat. Kommentaren skulle blockeras av långsamt läge direkt efter vidarebefordran." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Kommentarer är inaktiverade eftersom långsamt läge är aktiverat i {forwardChannelDisplayName}. Kommentaren skulle blockeras av långsamt läge direkt efter vidarebefordran." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Kommentarer är inte tillgängliga när långsamt läge är aktiverat." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Aviseringar för communitymeddelanden" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Meddelandefält för MFA-krav i communityn" @@ -9182,7 +9222,7 @@ msgstr "Fortsätt för att anmäla communityn" msgid "Continue to report user" msgstr "Fortsätt för att anmäla användaren" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Fortsätt med lösennyckel" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Samordnar räder, samlade påhopp eller trakasserier mot personer eller communities." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Kopierat" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kopiera kanallänk" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kopiera kod" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Antal användningar hittills:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Avfärdad för närvarande" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Visas nu" @@ -12555,6 +12595,11 @@ msgstr "Blockerar inte. Du kan öppna igen senare." msgid "Domain" msgstr "Domän" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Domän flyttad" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Nederländska" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "t.ex. YubiKey, iPhone, jobbdator" @@ -14056,10 +14101,14 @@ msgstr "Ange PIN-koden för din säkerhetsnyckel för att fortsätta." msgid "Enter the SMS code" msgstr "Ange SMS-koden" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Ange den här koden i din webbläsare för att slutföra inloggningen." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Ange den här koden i din gamla {PRODUCT_NAME}-app för att slutföra inloggningen." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Retur för att <0><1>spara" @@ -14441,7 +14490,7 @@ msgstr "Förfaller {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Går ut mellan {earliest} och {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Giltig i {remaining} sekunder" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Kunde inte starta SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Kunde inte byta konto" @@ -15413,17 +15462,17 @@ msgstr "För mig" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Vid den här kontrollen skickar du ett SMS till oss i stället för att ta emot ett från oss." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "För din säkerhet, bekräfta att det är du för att fortsätta." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Tvinga inaktivering" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Tvinga aktivering" @@ -15540,7 +15589,7 @@ msgstr "Glömt lösenord" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Glömt ditt lösenord?" @@ -15571,7 +15620,7 @@ msgstr "Vidarebefordra medieobjekt" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Vidarebefordra meddelande" @@ -15965,11 +16014,11 @@ msgstr "Generera en delbar länk eller kopiera din kombinerade anpassade CSS." msgid "Generate invite link" msgstr "Generera inbjudningslänk" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Generera ny kod" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Genererar kod…" @@ -16045,6 +16094,11 @@ msgstr "Skaffa datorappen för {productName} för att använda tryck för att pr msgid "Get the desktop app" msgstr "Skaffa datorappen" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Skaffa den nya {productName}-appen" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Infoga emoji" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Installera {productName} för att få hela källväljaren och smidigare kontroller för skärmdelning." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Installera det från din webbläsarmeny." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Installera den nya appen" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Ogiltig auktoriseringsförfrågan" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Ogiltiga bot-behörigheter." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ogiltig eller utgången kod. Försök igen." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Ogiltig redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Länk" msgid "Link (mature)" msgstr "Länk (vuxet innehåll)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Länka en ny enhet" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Inställningar för meddelandevisning är bara tillgängliga på datorn. msgid "Message editing" msgstr "Redigera meddelande" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Meddelande vidarebefordrat" @@ -20746,7 +20815,7 @@ msgstr "Knappar för meddelandeinmatning" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Meddelandet är för långt" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Diverse" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id saknas" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Namnet måste vara minst 2 tecken långt" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Namnge lösennyckel" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Formulär för att namnge lösennyckel" @@ -22381,7 +22450,7 @@ msgstr "Inga ändringar att spara" msgid "No channels available" msgstr "Inga kanaler tillgängliga" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Inga kanaler hittades" @@ -22824,7 +22893,7 @@ msgstr "Inga användare hittades" msgid "No verification is required." msgstr "Ingen verifiering krävs." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Inga verifieringsmetoder tillgängliga" @@ -23417,6 +23486,16 @@ msgstr "Öppna {contextName}" msgid "Open {displayName}" msgstr "Öppna {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Öppna {host} i Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Öppna {host} i webbläsaren." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Öppna {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsNam msgid "Open {productName}" msgstr "Öppna {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Öppna {productName} i webbläsaren" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Öppna banneralternativ" msgid "Open bookmarks" msgstr "Öppna bokmärken" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Öppna webbläsaren" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Öppna emoji i webbläsaren" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Öppna emojiväljaren" @@ -23809,6 +23893,16 @@ msgstr "Öppna länken nedan i din webbläsare, kopiera sedan hela JSON-svaret o msgid "Open the menu and choose an option." msgstr "Öppna menyn och välj ett alternativ." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Öppna den nya appen och välj Logga in med din gamla {productName}-app. Välj sedan Länka en ny enhet här och ange koden som visas." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Öppna den nya appen. Du är redan inloggad." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Öppna röstchatt" msgid "Open voice & video settings" msgstr "Öppna inställningar för ljud och video" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Öppna din webbläsare, logga in och ange sedan koden nedan för att länka ditt konto." @@ -23868,6 +23962,10 @@ msgstr "Öppna din webbläsare, logga in och ange sedan koden nedan för att lä msgid "Open your messages" msgstr "Öppna dina meddelanden" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Öppna din gamla {PRODUCT_NAME}-app och välj \"Länka en ny enhet\" och ange sedan koden nedan." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Öppna telefonens meddelandeapp och skapa ett nytt SMS." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Lösennyckel" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Namn på lösennyckel" @@ -28138,7 +28236,7 @@ msgstr "Mättnad" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Förhandsvisning vid spolning" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Markera text" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Välj kontot du vill logga in med i datorappen." +msgid "Select the account you want to sign in with on your new device." +msgstr "Välj det konto du vill logga in med på din nya enhet." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Skicka" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Skicka ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Session" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Sessionen har gått ut för {identifier}. Logga in igen." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Kortkommandon pausas under inspelningen." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Korta ner meddelandet och försök igen." @@ -30217,6 +30315,11 @@ msgstr "Visa högst 3 (märke med +5)" msgid "Show max 5 (+5 badge)" msgstr "Visa högst 5 (märke med +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Visa mig hur" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Logga in för att lösa in din present" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Logga in via webbläsaren" @@ -30496,6 +30599,11 @@ msgstr "Logga in via webbläsaren" msgid "Sign in with a passkey" msgstr "Logga in med en lösennyckel" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Logga in med din gamla {productName}-app" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Inloggningsuppgifter" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Inloggningsflöde" @@ -31030,7 +31138,7 @@ msgstr "Vissa användare kan inte läggas till" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Prata i den här röstkanalen. Utan den här behörigheten förblir medl msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Prata i röstkanaler. Utan den här behörigheten förblir medlemmar tystade tills någon med behörigheten ”Tysta medlemmar” slår på deras mikrofoner." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Tryck på Dela, sedan Lägg till på hemskärmen." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Telefonkontrollen har skjutits upp. Ett e-poststeg återstår." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Den angivna redirect_uri är inte registrerad för den här appen." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Den angivna redirect_uri måste använda HTTP eller HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Den här animeringen är för lång eller för stor för att beskära. Prova en kortare eller mindre fil." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Den här appen" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Den här bilagan har upphört att gälla" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Den här botten är inte offentlig och kan inte läggas till av andra användare." @@ -33757,7 +33870,7 @@ msgstr "Den här gruppen har för många medlemmar" msgid "This group is full. Remove someone before adding more people." msgstr "Den här gruppen är full. Ta bort någon innan du lägger till fler personer." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Den här överlämningskoden har gått ut. Skapa en ny för att fortsätta." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Okänt" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Okänd app, utgången session eller ogiltig auktoriseringsförfrågan." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Användningar:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Använder faktisk kontostatus" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Använder presentlagrets faktiska status" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Använder faktisk premiumstatus" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Använder faktiskt tillstånd" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Använder faktisk verifieringsstatus" @@ -37947,17 +38060,17 @@ msgstr "Vänta här" msgid "Waiting for connection..." msgstr "Väntar på anslutning…" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Väntar på lösennyckel…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Väntar på att långsamt läge i {forwardChannelDisplayName} ska upphöra." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Väntar på att väntetiden för långsamt läge ska löpa ut i en eller flera valda kanaler." @@ -38156,7 +38269,7 @@ msgstr "Vi skalade och komprimerade klistermärket till 320x320 pixlar, men det msgid "We sent a code to {email}." msgstr "Vi skickade en kod till {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Vi fyller i {prefillEmail} automatiskt när inloggningen öppnas i webbläsaren." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Förhandsvisningar av webbplatser" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Välkommen tillbaka" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Ditt {premiumProductName}-abonnemang har gått ut. Du har förlorat alla {premiumProductName}-förmåner. Återaktivera abonnemanget för att få åtkomst igen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Din {productName}-session har upphört. Logga in igen för att fortsätta auktorisera den här appen." @@ -39734,7 +39847,7 @@ msgstr "Ditt konto är för nytt för att skicka meddelanden i den här communit msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Din kontobegäran väntar på godkännande. Du kan logga in efter att en administratör har godkänt den." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Ditt konto kräver multifaktorautentisering, men ingen metod som stöds är konfigurerad på den här enheten. Logga in på en enhet med din autentiseringsapp eller säkerhetsnyckel eller kontakta supporten." @@ -39757,6 +39870,11 @@ msgstr "Din åtgärd kunde inte slutföras eftersom den flaggades av våra säke msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Din ansökan väntar på granskning. Du kan fortfarande uppdatera listningens uppgifter eller dra tillbaka ansökan." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Webbläsaren stöder inte passkeys på den här domänen. Uppdatera webbläsaren, eller logga in med ditt lösenord och tvåfaktorskod." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Ditt kort är godkänt. Du omdirigeras nu till betalningssidan." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Din mobiloperatör säger att numret inte är i bruk. Kontrollera det och försök igen, eller kontakta support om det stämmer." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Din kod" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Din kod är redo" diff --git a/fluxer_app/src/features/i18n/locales/th/messages.po b/fluxer_app/src/features/i18n/locales/th/messages.po index c0eec15dd..d6728c9c5 100644 --- a/fluxer_app/src/features/i18n/locales/th/messages.po +++ b/fluxer_app/src/features/i18n/locales/th/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {ข้อความที่ถูกบล็อ msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {พบ # คอมมูนิตี้} other {พบ # คอมมูนิตี้}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# คอมมูนิตี้ไม่พร้อมใช้งานชั่วคราว เนื่องจากเครื่องฟลักซ์คาปาซิเตอร์ทำงานผิดปกติ} other {# คอมมูนิตี้ไม่พร้อมใช้งานชั่วคราว เนื่องจากเครื่องฟลักซ์คาปาซิเตอร์ทำงานผิดปกติ}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} ตรวจพบการโจมตีที่อา msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} ตรวจพบการโจมตีที่อาจเกิดขึ้น ผู้ใช้ใหม่จึงไม่สามารถเข้าร่วมได้ในขณะนี้" +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} ย้ายไปที่ {host} แล้ว เพิ่มไปยังหน้าจอโฮมหรือ Dock ของคุณ จากนั้นลงชื่อเข้าใช้ด้วยแอปนี้" + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} ย้ายไปที่ {host} แล้ว ติดตั้งจากเบราว์เซอร์ของคุณ จากนั้นลงชื่อเข้าใช้ด้วยแอปนี้" + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} ได้ย้ายไปยัง {host} แล้ว ติดตั้งแอปใหม่ แล้วคุณจะเข้าสู่ระบบโดยอัตโนมัติ" + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} ย้ายไปที่ {host} แล้ว เปิดในเบราว์เซอร์ของคุณเพื่อติดตั้งแอปใหม่" + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} ต้องการสิทธิ์เข้าถึ msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} ต้องการสิทธิ์บันทึกหน้าจอ โปรดอนุญาตการบันทึกหน้าจอในการตั้งค่าความเป็นส่วนตัวของระบบปฏิบัติการ แล้วรีสตาร์ทแอป" +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} ตอนนี้อยู่ที่ {host} แล้ว ติดตั้งจากที่นั่น จากนั้นลงชื่อเข้าใช้ด้วยแอปนี้" + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} ตอนนี้อยู่ที่ {host} แล้ว ติดตั้งจากที่นั่นได้เลย" + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "โพล" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "ต้องระบุ redirect_uri เมื่อขอบเขต bot ไม่ใช่ขอบเขตเดียว" @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "เพิ่มบอทเข้าสู่คอมมูนิตี้พร้อมสิทธิ์ที่ร้องขอ" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "เพิ่มความคิดเห็น (ไม่บังคับ)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "เพิ่มคีย์ YouTube Data API เพื่อปรับปรุงลิงก์ YouTube" #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "เพิ่มบัญชี" @@ -4379,6 +4409,11 @@ msgstr "ต้องได้รับการอนุมัติ" msgid "Approve" msgstr "อนุมัติ" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "อนุมัติแอปนี้จากแอป {productName} ที่คุณใช้อยู่แล้ว ไม่ต้องใช้รหัสผ่าน" + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "การอนุญาตล้มเหลว" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "การอนุญาตล้มเหลว ลองอีกครั้ง" @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "สิทธิ์ของบอท" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "สิทธิ์ของบอทต้องไม่เป็นค่าลบ" @@ -6329,10 +6364,10 @@ msgstr "อัปโหลดไฟล์ที่นี่ไม่ได้" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "อัปโหลดไฟล์ที่นี่ไม่ได้" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "เลือกพื้นหลัง" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "เลือก guild_id หรือ channel_id อย่างใดอย่างหนึ่ง ไม่ใช่ทั้งสองอย่าง" +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "เลือก ไฟล์ แล้วเลือก เพิ่มไปยัง Dock" + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "บล็อกโค้ด" msgid "Code blocks" msgstr "บล็อกโค้ด" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "รหัสหมดอายุแล้ว" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "คำสั่ง" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "ปิดใช้งานความคิดเห็นเนื่องจากมีช่องที่เลือกไว้อย่างน้อยหนึ่งช่องเปิดโหมดจำกัดเวลาอยู่ ความคิดเห็นจะถูกบล็อกด้วยโหมดจำกัดเวลาทันทีหลังส่งต่อ" #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "ปิดใช้งานความคิดเห็นเนื่องจาก {forwardChannelDisplayName} เปิดโหมดจำกัดเวลาอยู่ ความคิดเห็นจะถูกบล็อกด้วยโหมดจำกัดเวลาทันทีหลังส่งต่อ" #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "ไม่สามารถแสดงความคิดเห็นได้เมื่อเปิดโหมดจำกัดเวลา" @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "การแจ้งเตือนข้อความของคอมมูนิตี้" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "แถบแจ้งเตือนข้อกำหนด MFA ของคอมมูนิตี้" @@ -9182,7 +9222,7 @@ msgstr "ดำเนินการรายงานคอมมูนิตี msgid "Continue to report user" msgstr "ดำเนินการรายงานผู้ใช้ต่อ" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "ดำเนินการต่อด้วยพาสคีย์" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "ประสานงานการโจมตี การรุมถล่ม หรือการคุกคามต่อบุคคลหรือคอมมูนิตี้" #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "คัดลอกแล้ว" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "คัดลอกลิงก์ช่อง" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "คัดลอกรหัส" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "จำนวนครั้งที่ใช้ไปแล้ว:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "ปิดอยู่" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "กำลังแสดง" @@ -12555,6 +12595,11 @@ msgstr "ไม่บล็อก คุณสามารถเปิดให msgid "Domain" msgstr "โดเมน" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "ย้ายโดเมนแล้ว" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "ดัตช์" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "เช่น YubiKey, iPhone, คอมพิวเตอร์ที่ทำงาน" @@ -14056,10 +14101,14 @@ msgstr "ป้อนรหัส PIN ของคีย์ความปลอ msgid "Enter the SMS code" msgstr "ป้อนรหัส SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "ป้อนรหัสนี้ในเบราว์เซอร์เพื่อลงชื่อเข้าใช้ให้เสร็จสมบูรณ์" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "กรอกรหัสนี้ในแอป {PRODUCT_NAME} เดิมของคุณเพื่อดำเนินการลงชื่อเข้าใช้ให้เสร็จสมบูรณ์" + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "กด Enter เพื่อ<0><1>บันทึก" @@ -14441,7 +14490,7 @@ msgstr "หมดอายุ {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "หมดอายุระหว่าง {earliest} ถึง {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "หมดอายุใน {remaining} วินาที" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "เริ่ม SSO ไม่สำเร็จ" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "เปลี่ยนบัญชีไม่สำเร็จ" @@ -15413,17 +15462,17 @@ msgstr "สำหรับฉัน" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "สำหรับการตรวจสอบนี้ คุณจะต้องส่งข้อความถึงเราแทนการรับข้อความจากเรา" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "เพื่อความปลอดภัย โปรดยืนยันตัวตนเพื่อดำเนินการต่อ" #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "บังคับปิดใช้งาน" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "บังคับเปิดใช้งาน" @@ -15540,7 +15589,7 @@ msgstr "ลืมรหัสผ่าน" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "ลืมรหัสผ่านใช่ไหม" @@ -15571,7 +15620,7 @@ msgstr "ส่งต่อสื่อ" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "ส่งต่อข้อความ" @@ -15965,11 +16014,11 @@ msgstr "สร้างลิงก์ที่แชร์ได้ หรื msgid "Generate invite link" msgstr "สร้างลิงก์เชิญ" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "สร้างรหัสใหม่" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "กำลังสร้างรหัส…" @@ -16045,6 +16094,11 @@ msgstr "รับแอปเดสก์ท็อป {productName} เพื่ msgid "Get the desktop app" msgstr "รับแอปเดสก์ท็อป" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "รับแอป {productName} ใหม่" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "แทรกอิโมจิ" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "ติดตั้ง {productName} เพื่อใช้ตัวเลือกแหล่งที่มาแบบเต็มและควบคุมการแชร์หน้าจอได้ราบรื่นยิ่งขึ้น" +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "ติดตั้งจากเมนูเบราว์เซอร์ของคุณ" + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "ติดตั้งแอปใหม่" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "คำขออนุญาตไม่ถูกต้อง" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "สิทธิ์ของบอทไม่ถูกต้อง" @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "รหัสไม่ถูกต้องหรือหมดอายุแล้ว ลองอีกครั้ง" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "redirect_uri ไม่ถูกต้อง" @@ -19332,6 +19396,11 @@ msgstr "ลิงก์" msgid "Link (mature)" msgstr "ลิงก์ (เนื้อหาสำหรับผู้ใหญ่)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "เชื่อมโยงอุปกรณ์ใหม่" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "การตั้งค่าการแสดงข้อความ msgid "Message editing" msgstr "แก้ไขข้อความ" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "ส่งต่อข้อความแล้ว" @@ -20746,7 +20815,7 @@ msgstr "ปุ่มป้อนข้อความ" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "ข้อความยาวเกินไป" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "เบ็ดเตล็ด" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "ไม่มี client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "ชื่อต้องมีอย่างน้อย 2 ตัวอักษร" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "ตั้งชื่อพาสคีย์" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "แบบฟอร์มตั้งชื่อพาสคีย์" @@ -22381,7 +22450,7 @@ msgstr "ไม่มีการเปลี่ยนแปลงที่ต้ msgid "No channels available" msgstr "ไม่มีช่องให้เลือก" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "ไม่พบช่อง" @@ -22824,7 +22893,7 @@ msgstr "ไม่พบผู้ใช้" msgid "No verification is required." msgstr "ไม่ต้องยืนยันตัวตน" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "ไม่มีวิธีการยืนยันตัวตน" @@ -23417,6 +23486,16 @@ msgstr "เปิด {contextName}" msgid "Open {displayName}" msgstr "เปิด {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "เปิด {host} ใน Safari" + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "เปิด {host} ในเบราว์เซอร์ของคุณ" + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "เปิด {macosSystemSettingsName} → {macosPrivacyAndSecuritySetti msgid "Open {productName}" msgstr "เปิด {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "เปิด {productName} ในเบราว์เซอร์ของคุณ" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "เปิดตัวเลือกแบนเนอร์" msgid "Open bookmarks" msgstr "เปิดบุ๊กมาร์ก" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "เปิดเบราว์เซอร์" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "เปิดอิโมจิในเบราว์เซอร์" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "เปิดตัวเลือกอิโมจิ" @@ -23809,6 +23893,16 @@ msgstr "เปิดลิงก์ด้านล่างในเบราว msgid "Open the menu and choose an option." msgstr "เปิดเมนูแล้วเลือกตัวเลือก" +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "เปิดแอปใหม่แล้วเลือก \"ลงชื่อเข้าใช้ด้วยแอป {productName} เดิมของคุณ\" จากนั้นเลือก \"เชื่อมโยงอุปกรณ์ใหม่ที่นี่\" แล้วป้อนรหัสที่แสดง" + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "เปิดแอปใหม่ คุณลงชื่อเข้าใช้แล้ว" + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "เปิดการโทรด้วยเสียง" msgid "Open voice & video settings" msgstr "เปิดการตั้งค่าเสียงและวิดีโอ" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "เปิดเบราว์เซอร์ของคุณ ลงชื่อเข้าใช้ แล้วป้อนรหัสข้างล่างเพื่อเชื่อมโยงบัญชีของคุณ" @@ -23868,6 +23962,10 @@ msgstr "เปิดเบราว์เซอร์ของคุณ ลง msgid "Open your messages" msgstr "เปิดข้อความของคุณ" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "เปิดแอป {PRODUCT_NAME} เดิมของคุณ แล้วเลือก \"เชื่อมโยงอุปกรณ์ใหม่\" จากนั้นป้อนรหัสโค้ดด้านล่าง" + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "เปิดแอปข้อความในโทรศัพท์ของคุณ แล้วสร้างข้อความใหม่" @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "พาสคีย์" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "ชื่อพาสคีย์" @@ -28138,7 +28236,7 @@ msgstr "ความอิ่มตัวของสี" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "ตัวอย่างขณะเลื่อนวิดีโอ" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "เลือกข้อความ" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "เลือกบัญชีที่ต้องการลงชื่อเข้าใช้บนแอปเดสก์ท็อป" +msgid "Select the account you want to sign in with on your new device." +msgstr "เลือกบัญชีที่คุณต้องการลงชื่อเข้าใช้บนอุปกรณ์ใหม่ของคุณ" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "ส่ง" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "ส่ง ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "เซสชัน" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "เซสชันของ {identifier} หมดอายุแล้ว โปรดลงชื่อเข้าใช้อีกครั้ง" @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "ปุ่มลัดจะหยุดทำงานชั่วคราวระหว่างการบันทึก" #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "ย่อข้อความแล้วลองอีกครั้ง" @@ -30217,6 +30315,11 @@ msgstr "แสดงสูงสุด 3 (ป้าย +5)" msgid "Show max 5 (+5 badge)" msgstr "แสดงสูงสุด 5 (ป้าย +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "แสดงวิธีทำ" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "ลงชื่อเข้าใช้เพื่อรับของ #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "ลงชื่อเข้าใช้ด้วยเบราว์เซอร์" @@ -30496,6 +30599,11 @@ msgstr "ลงชื่อเข้าใช้ด้วยเบราว์เ msgid "Sign in with a passkey" msgstr "ลงชื่อเข้าใช้ด้วยพาสคีย์" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "ลงชื่อเข้าใช้ด้วยแอป {productName} เดิมของคุณ" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "รายละเอียดการลงชื่อเข้าใช้" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "ขั้นตอนการลงชื่อเข้าใช้" @@ -31030,7 +31138,7 @@ msgstr "เพิ่มผู้ใช้บางคนไม่ได้" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "พูดในช่องเสียงนี้ หากไม่ msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "พูดในช่องเสียง หากไม่มีสิทธิ์นี้ สมาชิกจะถูกปิดไมโครโฟนจนกว่าจะมีคนที่มีสิทธิ์ \"ปิดไมโครโฟนของสมาชิก\" เปิดไมโครโฟนให้" +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "แตะแชร์ แล้วแตะ เพิ่มไปยังหน้าจอโฮม" + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "การยืนยันทางโทรศัพท์ถูกพักไว้ เหลือขั้นตอนทางอีเมลอีกหนึ่งขั้นตอน" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "redirect_uri ที่ระบุไม่ได้ลงทะเบียนไว้สำหรับแอปพลิเคชันนี้" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "redirect_uri ที่ระบุต้องใช้ HTTP หรือ HTTPS" @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "ภาพเคลื่อนไหวนี้ยาวหรือใหญ่เกินไปที่จะครอบตัดได้ ลองใช้ไฟล์ที่สั้นกว่าหรือเล็กกว่า" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "แอปนี้" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "ไฟล์แนบนี้หมดอายุแล้ว" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "บอทนี้ไม่เป็นสาธารณะและผู้ใช้รายอื่นไม่สามารถเพิ่มได้" @@ -33757,7 +33870,7 @@ msgstr "กลุ่มนี้มีสมาชิกมากเกินไ msgid "This group is full. Remove someone before adding more people." msgstr "กลุ่มนี้เต็มแล้ว นำสมาชิกบางคนออกก่อนจึงจะเพิ่มคนอื่นได้" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "รหัสส่งต่อนี้หมดอายุแล้ว สร้างรหัสใหม่เพื่อดำเนินการต่อ" @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "ไม่ทราบ" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "แอปพลิเคชันที่ไม่รู้จัก เซสชันหมดอายุ หรือคำขอการอนุญาตไม่ถูกต้อง" @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "จำนวนครั้งที่ใช้:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "กำลังใช้สถานะบัญชีจริง" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "กำลังใช้สถานะคลังของขวัญจริง" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "กำลังใช้สถานะพรีเมียมจริง" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "กำลังใช้สถานะจริง" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "กำลังใช้สถานะการยืนยันจริง" @@ -37947,17 +38060,17 @@ msgstr "รอสักครู่" msgid "Waiting for connection..." msgstr "กำลังรอการเชื่อมต่อ..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "กำลังรอพาสคีย์…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "กำลังรอให้โหมดจำกัดเวลาใน {forwardChannelDisplayName} สิ้นสุด" #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "กำลังรอให้โหมดจำกัดเวลาในช่องที่เลือกอย่างน้อยหนึ่งช่องสิ้นสุด" @@ -38156,7 +38269,7 @@ msgstr "เราได้ปรับขนาดและบีบอัดส msgid "We sent a code to {email}." msgstr "เราได้ส่งรหัสไปยัง {email} แล้ว" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "เราจะกรอก {prefillEmail} ให้โดยอัตโนมัติเมื่อเปิดหน้าลงชื่อเข้าใช้ในเบราว์เซอร์" @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "แสดงตัวอย่างเว็บไซต์" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "ยินดีต้อนรับกลับมา" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "การสมัครสมาชิก {premiumProductName} ของคุณหมดอายุแล้ว คุณได้สูญเสียสิทธิประโยชน์ทั้งหมดของ {premiumProductName} ไปแล้ว เปิดใช้งานการสมัครสมาชิกของคุณอีกครั้งเพื่อเข้าถึงสิทธิประโยชน์เหล่านั้น" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "เซสชัน {productName} ของคุณหมดอายุแล้ว โปรดลงชื่อเข้าใช้อีกครั้งเพื่อดำเนินการให้สิทธิ์แอปพลิเคชันนี้ต่อ" @@ -39734,7 +39847,7 @@ msgstr "บัญชีของคุณใหม่เกินไปที่ msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "คำขอบัญชีของคุณกำลังรอการอนุมัติ คุณจะลงชื่อเข้าใช้ได้หลังจากผู้ดูแลระบบอนุมัติแล้ว" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "บัญชีของคุณต้องมีการยืนยันตัวตนแบบหลายปัจจัย แต่ยังไม่ได้ตั้งค่าวิธีการที่รองรับบนอุปกรณ์นี้ โปรดลงชื่อเข้าใช้บนอุปกรณ์ที่มีแอปยืนยันตัวตนหรือคีย์ความปลอดภัยของคุณ หรือติดต่อฝ่ายสนับสนุน" @@ -39757,6 +39870,11 @@ msgstr "ดำเนินการไม่สำเร็จเนื่อง msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "ใบสมัครของคุณอยู่ระหว่างการตรวจสอบ คุณยังสามารถอัปเดตรายละเอียดประกาศหรือถอนใบสมัครได้" +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "เบราว์เซอร์ของคุณไม่รองรับรหัสผ่านบนโดเมนนี้ โปรดอัปเดตเบราว์เซอร์ของคุณ หรือลงชื่อเข้าใช้ด้วยรหัสผ่านและรหัสยืนยันตัวตนสองชั้น" + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "บัตรของคุณใช้ได้ กำลังนำ msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "ผู้ให้บริการของคุณแจ้งว่าหมายเลขนี้ไม่ได้เปิดใช้งาน โปรดตรวจสอบและลองอีกครั้ง หรือติดต่อฝ่ายสนับสนุนหากหมายเลขถูกต้อง" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "รหัสของคุณ" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "รหัสของคุณพร้อมแล้ว" diff --git a/fluxer_app/src/features/i18n/locales/tr/messages.po b/fluxer_app/src/features/i18n/locales/tr/messages.po index 03e18f38a..5f2c9c6d0 100644 --- a/fluxer_app/src/features/i18n/locales/tr/messages.po +++ b/fluxer_app/src/features/i18n/locales/tr/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# engellenen mesaj} other {# engellenen mesaj}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# topluluk bulundu} other {# topluluk bulundu}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# topluluk, flux kapasitörü arızası nedeniyle geçici olarak kullanılamıyor.} other {# topluluk, flux kapasitörü arızası nedeniyle geçici olarak kullanılamıyor.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName}, bu toplulukta olası bir baskın algıladı. Davetler dur msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} olası bir baskın tespit ettiğinden, yeni kullanıcılar şu anda katılamaz." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName}, {host} adresine taşındı. Ana Ekranınıza veya Dock'unuza ekleyin, ardından bu uygulamayla giriş yapın." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} {host} adresine taşındı. Tarayıcınızdan yükleyin, ardından bu uygulamayla giriş yapın." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName}, {host} adresine taşındı. Yeni uygulamayı yüklediğinizde otomatik olarak oturumunuz açılmış olacaktır." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName}, {host} adresine taşındı. Yeni uygulamayı yüklemek için tarayıcınızda açın." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} uygulamasının cihazlarınızı listelemek için mikrofon msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} uygulamasının ekran kaydı erişimine ihtiyacı var. İşletim sisteminizin gizlilik ayarlarından ekran kaydına izin verin ve uygulamayı yeniden başlatın." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} artık {host} adresinde. Oradan yükleyin, ardından bu uygulamayla giriş yapın." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} artık {host} adresinde. Buradan yükleyin." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "bir anket" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Bot kapsamı tek kapsam olmadığında bir redirect_uri gereklidir." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "İstenen izinlerle bir topluluğa bot ekleme" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Yorum ekle (isteğe bağlı)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "YouTube bağlantılarını zenginleştirmek için bir YouTube Data API anahtarı ekleyin." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Hesap ekle" @@ -4379,6 +4409,11 @@ msgstr "Onay gerekli" msgid "Approve" msgstr "Onayla" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Zaten kullandığınız {productName} uygulamasından bu uygulamayı onaylayın. Parola gerekmez." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Yetkilendirme başarısız oldu" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Yetkilendirme başarısız oldu. Tekrar deneyin." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Bot izinleri" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Bot izinleri negatif olamaz." @@ -6329,10 +6364,10 @@ msgstr "Buraya dosya yüklenemiyor" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Buraya dosya yüklenemiyor" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Arka plan seçin" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "guild_id veya channel_id'den birini seçin, ikisini birden değil." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Dosya'yı seçin, ardından Dock'a Ekle'yi seçin." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "kod bloğu" msgid "Code blocks" msgstr "Kod blokları" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Kodun süresi doldu" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Komutlar" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Seçilen bir veya daha fazla kanalda yavaş mod açık olduğu için yorumlar devre dışı bırakıldı. Yorum, iletildikten hemen sonra yavaş mod tarafından engellenirdi." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "{forwardChannelDisplayName} kanalında yavaş mod açık olduğu için yorumlar devre dışı bırakıldı. Yorum, iletildikten hemen sonra yavaş mod tarafından engellenirdi." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Yavaş mod açıkken yorumlar kullanılamaz." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Topluluk mesajı bildirimleri" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Topluluk MFA gereksinimi bildirim çubuğu" @@ -9182,7 +9222,7 @@ msgstr "Topluluğu bildirmeye devam et" msgid "Continue to report user" msgstr "Kullanıcıyı bildirmeye devam et" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Geçiş anahtarıyla devam et" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Kişilere veya topluluklara karşı baskın, toplu saldırı veya taciz koordine eder." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Kopyalandı" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Kanal bağlantısını kopyala" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Kodu kopyala" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Mevcut kullanım sayısı:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Şu anda kapatıldı" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Şu anda gösteriliyor" @@ -12555,6 +12595,11 @@ msgstr "Engellemez. Daha sonra tekrar açabilirsiniz." msgid "Domain" msgstr "Alan adı" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Alan adı taşındı bildirimi" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Hollandaca" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "örn. YubiKey, iPhone, iş bilgisayarı" @@ -14056,10 +14101,14 @@ msgstr "Devam etmek için güvenlik anahtarınızın PIN'ini girin." msgid "Enter the SMS code" msgstr "SMS kodunu girin" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Girişi tamamlamak için bu kodu tarayıcınıza girin." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Oturum açmayı tamamlamak için bu kodu eski {PRODUCT_NAME} uygulamanıza girin." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "<0><1>Kaydetmek için Enter" @@ -14441,7 +14490,7 @@ msgstr "{absolute} ({relativeText}) tarihinde sona eriyor" msgid "Expires between {earliest} and {latest}" msgstr "{earliest} ile {latest} arasında sona eriyor" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "{remaining} sn içinde sona eriyor" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "SSO başlatılamadı" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Hesap değiştirilemedi" @@ -15413,17 +15462,17 @@ msgstr "Kendim için" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Bu doğrulama için bizden bir mesaj almak yerine bize bir mesaj göndereceksiniz." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Güvenliğiniz için devam etmeden önce lütfen siz olduğunuzu onaylayın." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Zorla devre dışı" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Zorla etkin" @@ -15540,7 +15589,7 @@ msgstr "Parolamı unuttum" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Parolanızı mı unuttunuz?" @@ -15571,7 +15620,7 @@ msgstr "Medyayı ilet" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Mesajı ilet" @@ -15965,11 +16014,11 @@ msgstr "Paylaşılabilir bir bağlantı oluşturun veya birleştirilmiş özel C msgid "Generate invite link" msgstr "Davet bağlantısı oluştur" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Yeni kod oluştur" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Kod oluşturuluyor…" @@ -16045,6 +16094,11 @@ msgstr "Sistem genelinde bas konuş özelliğini ve diğer güzellikleri kullanm msgid "Get the desktop app" msgstr "Masaüstü uygulamasını edinin" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Yeni {productName} uygulamasını edin" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Emoji ekle" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Eksiksiz kaynak seçici ve daha akıcı ekran paylaşımı kontrolleri için {productName} uygulamasını yükleyin." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Tarayıcı menünüzden yükleyin." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Yeni uygulamayı yükle" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Geçersiz yetkilendirme isteği" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Geçersiz bot izinleri." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Geçersiz veya süresi dolmuş kod. Tekrar deneyin." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Geçersiz redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Bağlantı" msgid "Link (mature)" msgstr "Bağlantı (yetişkin içeriği)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Yeni bir cihaz bağla" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Mesaj görüntüleme ayarları yalnızca masaüstünde kullanılabilir." msgid "Message editing" msgstr "Mesaj düzenleme" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Mesaj iletildi" @@ -20746,7 +20815,7 @@ msgstr "Mesaj giriş düğmeleri" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Mesaj çok uzun" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Çeşitli" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "client_id eksik" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Ad en az 2 karakter olmalı" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Geçiş anahtarını adlandır" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Geçiş anahtarı adlandırma formu" @@ -22381,7 +22450,7 @@ msgstr "Kaydedilecek değişiklik yok" msgid "No channels available" msgstr "Kanal yok" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Kanal bulunamadı" @@ -22824,7 +22893,7 @@ msgstr "Kullanıcı bulunamadı" msgid "No verification is required." msgstr "Doğrulama gerekmiyor." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Kullanılabilir doğrulama yöntemi yok" @@ -23417,6 +23486,16 @@ msgstr "{contextName} öğesini aç" msgid "Open {displayName}" msgstr "{displayName} adlı kişiyi aç" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "{host} adresini Safari'de açın." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "{host} adresini tarayıcınızda açın." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "{macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} → msgid "Open {productName}" msgstr "{productName} uygulamasını aç" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "{productName} tarayıcınızda açın" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Banner seçeneklerini aç" msgid "Open bookmarks" msgstr "Yer işaretlerini aç" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Tarayıcıyı aç" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Emojiyi tarayıcıda aç" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Emoji seçiciyi aç" @@ -23809,6 +23893,16 @@ msgstr "Aşağıdaki bağlantıyı tarayıcınızda açın, ardından JSON yanı msgid "Open the menu and choose an option." msgstr "Menüyü açın ve bir seçenek belirleyin." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Yeni uygulamayı açın ve eski {productName} uygulamanızla giriş yap'ı seçin. Ardından buradaki yeni bir cihaz bağla'yı seçin ve gösterilen kodu girin." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Yeni uygulamayı aç. Zaten oturumun açık." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Sesli sohbeti aç" msgid "Open voice & video settings" msgstr "Ses ve video ayarlarını aç" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Tarayıcınızı açın, oturum açın ve hesabınızı bağlamak için aşağıdaki kodu girin." @@ -23868,6 +23962,10 @@ msgstr "Tarayıcınızı açın, oturum açın ve hesabınızı bağlamak için msgid "Open your messages" msgstr "Mesajlarınızı açın" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Eski {PRODUCT_NAME} uygulamanızı açın ve Yeni bir cihaz bağla seçeneğini belirleyip aşağıdaki kodu girin." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Telefonunuzun mesajlaşma uygulamasını açın ve yeni bir kısa mesaj oluşturun." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Geçiş anahtarı" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Geçiş anahtarı adı" @@ -28138,7 +28236,7 @@ msgstr "Doygunluk" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Sarma önizlemesi" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Metin seç" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Masaüstü uygulamasında oturum açmak istediğiniz hesabı seçin." +msgid "Select the account you want to sign in with on your new device." +msgstr "Yeni cihazınızda oturum açmak istediğiniz hesabı seçin." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Gönder" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Gönder ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Oturum" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "{identifier} için oturumunuzun süresi doldu. Tekrar oturum açın." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Kayıt sırasında klavye kısayolları duraklatılır." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Mesajı kısaltıp tekrar deneyin." @@ -30217,6 +30315,11 @@ msgstr "En fazla 3 göster (+5 rozet)" msgid "Show max 5 (+5 badge)" msgstr "En fazla 5 göster (+5 rozet)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Nasıl yapıldığını göster" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Hediyenizi almak için giriş yapın" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Tarayıcıyla giriş yap" @@ -30496,6 +30599,11 @@ msgstr "Tarayıcıyla giriş yap" msgid "Sign in with a passkey" msgstr "Geçiş anahtarıyla giriş yap" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Eski {productName} uygulamanla oturum aç" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Oturum açma ayrıntıları" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Oturum açma akışı" @@ -31030,7 +31138,7 @@ msgstr "Bazı kullanıcılar eklenemiyor" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Bu sesli kanalda konuşun. Bu izin olmadan üyeler, \"Üyelerin sesini k msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Sesli kanallarda konuşun. Bu izin olmadan üyeler, \"Üyelerin sesini kapat\" iznine sahip biri seslerini açana kadar sessiz kalır." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Paylaş'a dokunun, ardından Ana Ekrana Ekle'yi seçin." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Telefon kontrolü beklemeye alındı. Bir e-posta adımı kaldı." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Sağlanan redirect_uri bu uygulama için kayıtlı değil." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Sağlanan redirect_uri HTTP veya HTTPS kullanmalıdır." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Bu animasyon kırpılamayacak kadar uzun veya büyük. Daha kısa veya daha küçük bir dosya deneyin." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Bu uygulama" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Bu ekin süresi doldu" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Bu bot herkese açık değil ve diğer kullanıcılar tarafından eklenemez." @@ -33757,7 +33870,7 @@ msgstr "Bu grupta çok fazla kişi var" msgid "This group is full. Remove someone before adding more people." msgstr "Bu grup dolu. Daha fazla kişi eklemeden önce birini çıkarın." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Bu aktarım kodunun süresi doldu. Devam etmek için yeni bir tane oluşturun." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Bilinmiyor" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Bilinmeyen uygulama, süresi dolmuş oturum veya geçersiz yetkilendirme isteği." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Kullanım:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Gerçek hesap durumu kullanılıyor" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Gerçek hediye envanteri durumu kullanılıyor" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Gerçek premium durumu kullanılıyor" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Gerçek durum kullanılıyor" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Gerçek doğrulama durumu kullanılıyor" @@ -37947,17 +38060,17 @@ msgstr "Burada bekleyin" msgid "Waiting for connection..." msgstr "Bağlantı bekleniyor..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Geçiş anahtarı bekleniyor…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "{forwardChannelDisplayName} kanalındaki yavaş modun süresinin dolması bekleniyor." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Seçilen bir veya daha fazla kanalda yavaş modun süresinin dolması bekleniyor." @@ -38156,7 +38269,7 @@ msgstr "Bu çıkartmayı 320x320 piksele yeniden boyutlandırıp sıkıştırdı msgid "We sent a code to {email}." msgstr "{email} adresine bir kod gönderdik." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Tarayıcıda oturum açma ekranı açıldığında {prefillEmail} adresini önceden dolduracağız." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Web sitesi önizlemeleri" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Tekrar hoş geldiniz" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "{premiumProductName} aboneliğinizin süresi doldu. Tüm {premiumProductName} avantajlarını kaybettiniz. Erişimi yeniden kazanmak için aboneliğinizi yeniden etkinleştirin." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "{productName} oturumunuzun süresi doldu. Bu uygulamaya yetki vermeye devam etmek için tekrar oturum açın." @@ -39734,7 +39847,7 @@ msgstr "Bu toplulukta mesaj göndermek için hesabınız çok yeni." msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Hesap isteğiniz onay bekliyor. Bir yönetici onayladıktan sonra oturum açabilirsiniz." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Hesabınız çok faktörlü kimlik doğrulama gerektiriyor ancak bu cihazda desteklenen bir yöntem ayarlanmamış. Kimlik doğrulayıcı uygulamanızın veya güvenlik anahtarınızın olduğu bir cihazdan oturum açın ya da destek ekibiyle iletişime geçin." @@ -39757,6 +39870,11 @@ msgstr "İşleminiz güvenlik sistemlerimiz tarafından işaretlendiği için ta msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Başvurunuz incelenmeyi bekliyor. İlan ayrıntılarınızı yine de güncelleyebilir veya başvuruyu geri çekebilirsiniz." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Tarayıcınız bu alan adında kimlik anahtarlarını desteklemiyor. Tarayıcınızı güncelleyin veya parolanız ve iki faktör kodunuzla giriş yapın." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Kartınız uygun. Şimdi ödeme sayfasına yönlendiriliyorsunuz." msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Operatörünüz bu numaranın kullanımda olmadığını belirtiyor. Kontrol edip tekrar deneyin veya doğruysa destek ekibimizle iletişime geçin." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Kodunuz" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Kodunuz hazır" diff --git a/fluxer_app/src/features/i18n/locales/uk/messages.po b/fluxer_app/src/features/i18n/locales/uk/messages.po index d269ef7af..0c6589acb 100644 --- a/fluxer_app/src/features/i18n/locales/uk/messages.po +++ b/fluxer_app/src/features/i18n/locales/uk/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# заблоковане повідомлення} msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Знайдено # спільноту} few {Знайдено # спільноти} many {Знайдено # спільнот} other {Знайдено # спільноти}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# спільнота тимчасово недоступна через несправність потокового конденсатора.} few {# спільноти тимчасово недоступні через несправність потокового конденсатора.} many {# спільнот тимчасово недоступні через несправність потокового конденсатора.} other {# спільнот тимчасово недоступні через несправність потокового конденсатора.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} виявив потенційний рейд у цій с msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} виявив потенційний рейд, тому нові користувачі наразі не можуть приєднатися." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} переїхав на {host}. Додайте його на головний екран або в Dock, а потім увійдіть за допомогою цієї програми." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} переїхав на {host}. Встановіть його з браузера, а потім увійдіть за допомогою цієї програми." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} переїхав на {host}. Встановіть новий додаток, і ви вже будете ввійшли." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} переїхав на {host}. Відкрийте його у браузері, щоб установити новий додаток." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "Щоб переглянути список пристроїв, заст msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} потрібен доступ до запису екрана. Увімкніть запис екрана в налаштуваннях конфіденційності вашої операційної системи та перезапустіть застосунок." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} тепер знаходиться за адресою {host}. Встановіть його звідти, а потім увійдіть за допомогою цієї програми." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} тепер знаходиться за адресою {host}. Встановіть його звідти." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "опитування" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Потрібен redirect_uri, якщо область дії bot не єдина." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Додати бота до спільноти із запитаними дозволами" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Додати коментар (необов'язково)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Додайте ключ API YouTube Data, щоб доповнювати посилання на YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Додати обліковий запис" @@ -4379,6 +4409,11 @@ msgstr "Потрібне схвалення" msgid "Approve" msgstr "Схвалити" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Підтвердьте цей додаток у програмі {productName}, яку ви вже використовуєте. Пароль не потрібен." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Не вдалося авторизуватися" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Не вдалося авторизуватися. Спробуйте ще раз." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Дозволи бота" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Дозволи бота не можуть бути від'ємними." @@ -6329,10 +6364,10 @@ msgstr "Сюди не можна завантажити файли" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Сюди не можна завантажити файли" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Виберіть фон" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Виберіть guild_id або channel_id, але не обидва." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Виберіть Файл, а потім Додати в Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "блок коду" msgid "Code blocks" msgstr "Блоки коду" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Термін дії коду закінчився" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Команди" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Коментарі вимкнено, оскільки в одному або кількох вибраних каналах увімкнено повільний режим. Коментар буде заблоковано повільним режимом одразу після пересилання." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Коментарі вимкнено, оскільки в каналі «{forwardChannelDisplayName}» увімкнено повільний режим. Коментар буде заблоковано повільним режимом одразу після пересилання." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Коментарі недоступні, коли ввімкнено повільний режим." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Сповіщення про повідомлення спільноти" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Панель нагадування про вимогу MFA для спільноти" @@ -9182,7 +9222,7 @@ msgstr "Продовжити скаргу на спільноту" msgid "Continue to report user" msgstr "Продовжити скаргу на користувача" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Продовжити з ключем доступу" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Координує рейди, масові нападки або переслідування людей чи спільнот." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Скопійовано" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Копіювати посилання на канал" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Копіювати код" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Поточна кількість використань:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Наразі відхилено" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Зараз показується" @@ -12555,6 +12595,11 @@ msgstr "Не блокує. Можна відкрити знову пізніше msgid "Domain" msgstr "Домен" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Банер: домен перенесено" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Нідерландська" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "наприклад, YubiKey, iPhone, робочий комп'ютер" @@ -14056,10 +14101,14 @@ msgstr "Введіть PIN-код вашого ключа безпеки, щоб msgid "Enter the SMS code" msgstr "Введіть SMS-код" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Введіть цей код у своєму браузері, щоб завершити вхід." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Введіть цей код у своєму старому додатку {PRODUCT_NAME}, щоб завершити вхід." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "натисніть Enter, щоб <0><1>зберегти" @@ -14441,7 +14490,7 @@ msgstr "Закінчується {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Термін дії закінчується між {earliest} та {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Закінчується через {remaining} с" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Не вдалося запустити SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Не вдалося перемкнути обліковий запис" @@ -15413,17 +15462,17 @@ msgstr "Для мене" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Для цієї перевірки ви надсилаєте нам текстове повідомлення, а не отримуєте його від нас." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Для вашої безпеки підтвердьте, що це ви, щоб продовжити." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Примусово вимкнено" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Примусово ввімкнено" @@ -15540,7 +15589,7 @@ msgstr "Забули пароль" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Забули пароль?" @@ -15571,7 +15620,7 @@ msgstr "Переслати медіа" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Переслати повідомлення" @@ -15965,11 +16014,11 @@ msgstr "Згенеруйте посилання для поширення або msgid "Generate invite link" msgstr "Згенерувати посилання-запрошення" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Згенерувати новий код" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Генерування коду…" @@ -16045,6 +16094,11 @@ msgstr "Завантажте десктопний застосунок {productN msgid "Get the desktop app" msgstr "Завантажити десктопний застосунок" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Завантажте новий додаток {productName}" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Вставити емоджі" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Установіть {productName}, щоб отримати повний вибір джерел і зручніші елементи керування демонстрацією екрана." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Встановіть його з меню браузера." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Встановити новий додаток" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Недійсний запит авторизації" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Недійсні дозволи бота." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Недійсний або прострочений код. Спробуйте ще раз." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "Недійсний redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "Посилання" msgid "Link (mature)" msgstr "Посилання (для дорослих)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Підключити новий пристрій" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Налаштування відображення повідомлень msgid "Message editing" msgstr "Редагування повідомлень" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Повідомлення переслано" @@ -20746,7 +20815,7 @@ msgstr "Кнопки введення повідомлень" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Повідомлення задовге" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Різне" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Відсутній client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Назва має містити щонайменше 2 символи" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Назвати ключ доступу" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Форма назви ключа доступу" @@ -22381,7 +22450,7 @@ msgstr "Немає змін для збереження" msgid "No channels available" msgstr "Немає доступних каналів" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Канали не знайдено" @@ -22824,7 +22893,7 @@ msgstr "Користувачів не знайдено" msgid "No verification is required." msgstr "Перевірка не потрібна." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Немає доступних методів перевірки" @@ -23417,6 +23486,16 @@ msgstr "Відкрити {contextName}" msgid "Open {displayName}" msgstr "Відкрити {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Відкрийте {host} у Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Відкрийте {host} у браузері." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Відкрийте {macosSystemSettingsName} → {macosPrivacyAndSecurit msgid "Open {productName}" msgstr "Відкрити {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Відкрити {productName} у браузері" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Відкрити параметри банера" msgid "Open bookmarks" msgstr "Відкрити закладки" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Відкрити браузер" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Відкрити емоджі в браузері" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Відкрити панель емоджі" @@ -23809,6 +23893,16 @@ msgstr "Відкрийте посилання нижче у своєму бра msgid "Open the menu and choose an option." msgstr "Відкрийте меню та виберіть варіант." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Відкрийте новий додаток і виберіть «Увійти за допомогою старого додатка {productName}». Потім виберіть «Зв’язати новий пристрій тут» і введіть код, який там з’явиться." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Відкрийте новий застосунок. Ви вже ввійшли в систему." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Відкрити голосовий чат" msgid "Open voice & video settings" msgstr "Відкрити налаштування аудіо та відео" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Відкрийте браузер, увійдіть, а потім введіть код нижче, щоб зв'язати свій обліковий запис." @@ -23868,6 +23962,10 @@ msgstr "Відкрийте браузер, увійдіть, а потім вв msgid "Open your messages" msgstr "Відкрийте повідомлення" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Відкрийте свій старий застосунок {PRODUCT_NAME} і виберіть «Прив'язати новий пристрій», а потім введіть код нижче." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Відкрийте застосунок для обміну повідомленнями на телефоні та створіть нове текстове повідомлення." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Ключ доступу" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Назва ключа доступу" @@ -28138,7 +28236,7 @@ msgstr "Насиченість" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Перегляд під час перемотування" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Виділити текст" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Виберіть обліковий запис, з яким ви хочете ввійти в десктопний застосунок." +msgid "Select the account you want to sign in with on your new device." +msgstr "Виберіть обліковий запис, за допомогою якого ви хочете увійти на новому пристрої." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Надіслати" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Надіслати ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Сеанс" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Термін дії сеансу для {identifier} закінчився. Увійдіть знову." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Під час запису комбінації клавіш призупинено." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Скоротіть повідомлення та спробуйте ще раз." @@ -30217,6 +30315,11 @@ msgstr "Показувати щонайбільше 3 (значок +5)" msgid "Show max 5 (+5 badge)" msgstr "Показувати щонайбільше 5 (значок +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Показати інструкції" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Увійдіть, щоб отримати подарунок" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Увійти через браузер" @@ -30496,6 +30599,11 @@ msgstr "Увійти через браузер" msgid "Sign in with a passkey" msgstr "Увійти за допомогою ключа доступу" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Увійдіть за допомогою старого застосунку {productName}" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Дані для входу" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Вхід" @@ -31030,7 +31138,7 @@ msgstr "Деяких користувачів не можна додати" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Розмовляти в цьому голосовому каналі. Б msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Розмовляти в голосових каналах. Без цього дозволу учасники залишатимуться з вимкненим мікрофоном, доки хтось із дозволом «Вимикати мікрофон учасникам» не увімкне їх." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Натисніть «Поділитися», а потім «Додати на головний екран»." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Перевірку телефону відкладено. Залишився один крок з електронною поштою." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "Наданий redirect_uri не зареєстровано для цього застосунку." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Наданий redirect_uri має використовувати HTTP або HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ця анімація занадто довга або занадто велика для обрізання. Спробуйте коротший або менший файл." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Цей застосунок" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Термін дії цього вкладення закінчився" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Цей бот не є публічним, і його не можуть додати інші користувачі." @@ -33757,7 +33870,7 @@ msgstr "У цій групі забагато людей" msgid "This group is full. Remove someone before adding more people." msgstr "Ця група заповнена. Видаліть когось, перш ніж додавати інших." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Термін дії цього коду передачі закінчився. Створіть новий, щоб продовжити." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Невідомо" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Невідомий застосунок, термін дії сеансу закінчився або недійсний запит авторизації." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Кількість використань:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Використовується фактичний стан облікового запису" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Використовується фактичний стан інвентарю подарунків" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Використовується фактичний стан преміуму" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Використовується фактичний стан" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Використовується фактичний стан перевірки" @@ -37947,17 +38060,17 @@ msgstr "Зачекайте тут" msgid "Waiting for connection..." msgstr "Очікування з'єднання…" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Очікування ключа доступу…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Очікування завершення повільного режиму в каналі «{forwardChannelDisplayName}»." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Очікування завершення повільного режиму в одному або кількох вибраних каналах." @@ -38156,7 +38269,7 @@ msgstr "Ми змінили розмір і стиснули цей стікер msgid "We sent a code to {email}." msgstr "Ми надіслали код на {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Ми попередньо заповнимо {prefillEmail}, щойно відкриється вхід у браузері." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Попередній перегляд вебсайтів" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "З поверненням" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Термін дії вашої підписки {premiumProductName} закінчився. Ви втратили всі переваги {premiumProductName}. Поновіть підписку, щоб відновити доступ." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Термін дії вашої сесії {productName} закінчився. Увійдіть знову, щоб продовжити авторизацію цього застосунку." @@ -39734,7 +39847,7 @@ msgstr "Ваш обліковий запис занадто новий, щоб msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Ваш запит на обліковий запис очікує схвалення. Ви зможете ввійти після схвалення адміністратором." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Ваш обліковий запис вимагає багатофакторної автентифікації, але на цьому пристрої не налаштовано жодного підтримуваного методу. Увійдіть на пристрої із застосунком автентифікації або ключем безпеки чи зверніться до служби підтримки." @@ -39757,6 +39870,11 @@ msgstr "Вашу дію не вдалося виконати, оскільки msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Ваша заявка очікує на розгляд. Ви все ще можете оновити деталі оголошення або відкликати заявку." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Ваш браузер не підтримує ключі доступу для цього домену. Оновіть браузер або увійдіть за допомогою пароля та двофакторного коду." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Ваша картка підходить. Перенаправляємо msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Ваш оператор повідомив, що цей номер не обслуговується. Перевірте його та спробуйте ще раз, або зверніться до служби підтримки, якщо номер правильний." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Ваш код" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Ваш код готовий" diff --git a/fluxer_app/src/features/i18n/locales/vi/messages.po b/fluxer_app/src/features/i18n/locales/vi/messages.po index 78477e496..d65894809 100644 --- a/fluxer_app/src/features/i18n/locales/vi/messages.po +++ b/fluxer_app/src/features/i18n/locales/vi/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# tin nhắn bị chặn} other {# tin nhắn bị msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# cộng đồng được tìm thấy} other {# cộng đồng được tìm thấy}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# cộng đồng hiện không khả dụng do lỗi bộ biến đổi dòng chảy.} other {# cộng đồng hiện không khả dụng do lỗi bộ biến đổi dòng chảy.}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} đã phát hiện một cuộc tấn công tiềm ẩn tro msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} đã phát hiện một cuộc tấn công tiềm ẩn, vì vậy người dùng mới không thể tham gia ngay bây giờ." +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} đã chuyển sang {host}. Hãy thêm ứng dụng vào Màn hình chính hoặc Dock, sau đó đăng nhập bằng ứng dụng này." + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} đã chuyển sang {host}. Hãy cài đặt từ trình duyệt của bạn, sau đó đăng nhập bằng ứng dụng này." + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} đã chuyển sang {host}. Hãy cài đặt ứng dụng mới và bạn sẽ được đăng nhập sẵn." + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} đã chuyển sang {host}. Mở trên trình duyệt để cài đặt ứng dụng mới." + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} cần quyền truy cập micrô để liệt kê thiết b msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} cần quyền truy cập ghi màn hình. Hãy cho phép ghi màn hình trong cài đặt quyền riêng tư của hệ điều hành và khởi động lại ứng dụng." +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} hiện đã có tại {host}. Hãy cài đặt từ đó, sau đó đăng nhập bằng ứng dụng này." + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} giờ đã có tại {host}. Hãy cài đặt từ đó." + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "một cuộc thăm dò" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Cần có redirect_uri khi phạm vi bot không phải là phạm vi duy nhất." @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "Thêm bot vào cộng đồng với các quyền được yêu cầu" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "Thêm bình luận (tùy chọn)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "Thêm khóa YouTube Data API để bổ sung thông tin cho các liên kết YouTube." #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "Thêm tài khoản" @@ -4379,6 +4409,11 @@ msgstr "Cần phê duyệt" msgid "Approve" msgstr "Duyệt" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "Phê duyệt ứng dụng này từ ứng dụng {productName} mà bạn đã sử dụng. Không cần mật khẩu." + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "Ủy quyền thất bại" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "Ủy quyền thất bại. Vui lòng thử lại." @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "Quyền của bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "Quyền của bot không thể là số âm." @@ -6329,10 +6364,10 @@ msgstr "Không thể tải tệp lên ở đây" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "Không thể tải tệp lên ở đây" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "Chọn hình nền" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "Chọn guild_id hoặc channel_id, không chọn cả hai." +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "Chọn Tệp, sau đó Thêm vào Dock." + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "khối mã" msgid "Code blocks" msgstr "Khối mã" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "Mã đã hết hạn" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "Lệnh" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "Không thể bình luận vì một hoặc nhiều kênh đã chọn đang bật chế độ chậm. Bình luận sẽ bị chặn ngay sau khi chuyển tiếp." #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "Không thể bình luận vì kênh {forwardChannelDisplayName} đang bật chế độ chậm. Bình luận sẽ bị chặn ngay sau khi chuyển tiếp." #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "Không thể bình luận khi đang bật chế độ chậm." @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "Thông báo tin nhắn cộng đồng" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "Thanh thông báo yêu cầu MFA của cộng đồng" @@ -9182,7 +9222,7 @@ msgstr "Tiếp tục báo cáo cộng đồng" msgid "Continue to report user" msgstr "Tiếp tục báo cáo người dùng" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "Tiếp tục bằng khóa truy cập" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "Điều phối các cuộc tấn công, quấy rối hoặc gây rối có tổ chức nhắm vào người hoặc cộng đồng." #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "Đã sao chép" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "Sao chép liên kết kênh" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "Sao chép mã" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "Lượt dùng hiện tại:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "Đang ẩn" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "Đang hiển thị" @@ -12555,6 +12595,11 @@ msgstr "Không chặn. Bạn có thể mở lại sau." msgid "Domain" msgstr "Tên miền" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "Chuyển miền" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "Tiếng Hà Lan" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "Ví dụ: YubiKey, iPhone, máy tính làm việc" @@ -14056,10 +14101,14 @@ msgstr "Nhập mã PIN cho khóa bảo mật của bạn để tiếp tục." msgid "Enter the SMS code" msgstr "Nhập mã SMS" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "Nhập mã này vào trình duyệt để hoàn tất đăng nhập." +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "Nhập mã này vào ứng dụng {PRODUCT_NAME} cũ của bạn để hoàn tất đăng nhập." + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "Nhấn Enter để <0><1>lưu" @@ -14441,7 +14490,7 @@ msgstr "Hết hạn {absolute} ({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "Hết hạn từ {earliest} đến {latest}" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "Hết hạn sau {remaining} giây" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "Không thể bắt đầu SSO" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "Không thể chuyển tài khoản" @@ -15413,17 +15462,17 @@ msgstr "Dành cho tôi" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "Để thực hiện bước kiểm tra này, bạn sẽ gửi tin nhắn cho chúng tôi thay vì nhận tin nhắn từ chúng tôi." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "Để bảo mật, vui lòng xác nhận danh tính của bạn để tiếp tục." #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "Buộc tắt" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "Buộc bật" @@ -15540,7 +15589,7 @@ msgstr "Quên mật khẩu" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "Quên mật khẩu?" @@ -15571,7 +15620,7 @@ msgstr "Chuyển tiếp phương tiện" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "Chuyển tiếp tin nhắn" @@ -15965,11 +16014,11 @@ msgstr "Tạo liên kết có thể chia sẻ hoặc sao chép CSS tùy chỉnh msgid "Generate invite link" msgstr "Tạo liên kết mời" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "Tạo mã mới" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "Đang tạo mã…" @@ -16045,6 +16094,11 @@ msgstr "Tải ứng dụng {productName} cho máy tính để bàn để sử d msgid "Get the desktop app" msgstr "Tải ứng dụng cho máy tính" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "Tải ứng dụng {productName} mới" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "Chèn biểu tượng cảm xúc" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "Cài đặt {productName} để có bộ chọn nguồn đầy đủ và điều khiển chia sẻ màn hình mượt mà hơn." +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "Cài đặt từ menu trình duyệt của bạn." + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "Cài đặt ứng dụng mới" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "Yêu cầu ủy quyền không hợp lệ" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "Quyền của bot không hợp lệ." @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "Mã không hợp lệ hoặc đã hết hạn. Hãy thử lại." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "redirect_uri không hợp lệ" @@ -19332,6 +19396,11 @@ msgstr "Liên kết" msgid "Link (mature)" msgstr "Liên kết (người lớn)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "Liên kết một thiết bị mới" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "Cài đặt hiển thị tin nhắn chỉ có trên máy tính." msgid "Message editing" msgstr "Chỉnh sửa tin nhắn" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "Đã chuyển tiếp tin nhắn" @@ -20746,7 +20815,7 @@ msgstr "Nút nhập tin nhắn" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "Tin nhắn quá dài" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "Khác" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "Thiếu client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "Tên phải có ít nhất 2 ký tự" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "Đặt tên khóa truy cập" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "Biểu mẫu đặt tên khóa truy cập" @@ -22381,7 +22450,7 @@ msgstr "Không có thay đổi nào để lưu" msgid "No channels available" msgstr "Không có kênh nào" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "Không tìm thấy kênh nào" @@ -22824,7 +22893,7 @@ msgstr "Không tìm thấy người dùng nào" msgid "No verification is required." msgstr "Không yêu cầu xác minh." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "Không có phương thức xác minh nào" @@ -23417,6 +23486,16 @@ msgstr "Mở {contextName}" msgid "Open {displayName}" msgstr "Mở {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "Mở {host} trên Safari." + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "Mở {host} trên trình duyệt của bạn." + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "Mở {macosSystemSettingsName} → {macosPrivacyAndSecuritySettingsName} msgid "Open {productName}" msgstr "Mở {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "Mở {productName} trên trình duyệt của bạn" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "Mở tùy chọn ảnh bìa" msgid "Open bookmarks" msgstr "Mở tin nhắn đã lưu" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "Mở trình duyệt" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "Mở biểu tượng cảm xúc trong trình duyệt" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "Mở bảng chọn biểu tượng cảm xúc" @@ -23809,6 +23893,16 @@ msgstr "Mở liên kết bên dưới trong trình duyệt, sau đó sao chép t msgid "Open the menu and choose an option." msgstr "Mở menu và chọn một tùy chọn." +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "Mở ứng dụng mới và chọn Đăng nhập bằng ứng dụng {productName} cũ của bạn. Sau đó chọn Liên kết thiết bị mới tại đây và nhập mã hiển thị." + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "Mở ứng dụng mới. Bạn đã đăng nhập." + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "Mở trò chuyện thoại" msgid "Open voice & video settings" msgstr "Mở cài đặt âm thanh & video" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "Mở trình duyệt, đăng nhập, sau đó nhập mã bên dưới để liên kết tài khoản của bạn." @@ -23868,6 +23962,10 @@ msgstr "Mở trình duyệt, đăng nhập, sau đó nhập mã bên dưới đ msgid "Open your messages" msgstr "Mở tin nhắn của bạn" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "Mở ứng dụng {PRODUCT_NAME} cũ của bạn và chọn Liên kết thiết bị mới, sau đó nhập mã bên dưới." + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "Mở ứng dụng nhắn tin trên điện thoại của bạn và tạo tin nhắn văn bản mới." @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "Khóa truy cập" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "Tên khóa truy cập" @@ -28138,7 +28236,7 @@ msgstr "Độ bão hòa" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "Xem trước khi tua" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "Chọn văn bản" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "Chọn tài khoản bạn muốn đăng nhập trên ứng dụng máy tính." +msgid "Select the account you want to sign in with on your new device." +msgstr "Chọn tài khoản bạn muốn đăng nhập trên thiết bị mới của mình." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "Gửi" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "Gửi ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "Phiên" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "Phiên đăng nhập của {identifier} đã hết hạn. Hãy đăng nhập lại." @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "Các phím tắt tạm ngừng hoạt động trong khi ghi lại tổ hợp phím." #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "Rút gọn tin nhắn và thử lại." @@ -30217,6 +30315,11 @@ msgstr "Hiển thị tối đa 3 (huy hiệu +5)" msgid "Show max 5 (+5 badge)" msgstr "Hiển thị tối đa 5 (huy hiệu +5)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "Chỉ cho tôi cách" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "Đăng nhập để nhận quà" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "Đăng nhập qua trình duyệt" @@ -30496,6 +30599,11 @@ msgstr "Đăng nhập qua trình duyệt" msgid "Sign in with a passkey" msgstr "Đăng nhập bằng khóa truy cập" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "Đăng nhập bằng ứng dụng {productName} cũ của bạn" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "Chi tiết đăng nhập" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "Quy trình đăng nhập" @@ -31030,7 +31138,7 @@ msgstr "Không thể thêm một số người dùng" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "Nói trong kênh thoại này. Nếu không có quyền này, thành vi msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "Nói trong các kênh thoại. Nếu không có quyền này, thành viên sẽ bị tắt tiếng cho đến khi ai đó có quyền Tắt tiếng thành viên bật tiếng cho họ." +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "Chạm vào Chia sẻ, sau đó chọn Thêm vào Màn hình chính." + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "Đã bỏ qua kiểm tra điện thoại. Còn một bước email." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "redirect_uri được cung cấp chưa đăng ký với ứng dụng này." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "redirect_uri được cung cấp phải dùng HTTP hoặc HTTPS." @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ảnh động này quá dài hoặc quá lớn để cắt. Hãy thử một tệp ngắn hơn hoặc nhỏ hơn." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "Ứng dụng này" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "Tệp đính kèm này đã hết hạn" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "Bot này không công khai nên người dùng khác không thể thêm." @@ -33757,7 +33870,7 @@ msgstr "Nhóm này có quá nhiều người" msgid "This group is full. Remove someone before adding more people." msgstr "Nhóm này đã đầy. Hãy xóa bớt một người trước khi thêm người khác." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "Mã chuyển giao này đã hết hạn. Tạo mã mới để tiếp tục." @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "Không rõ" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Ứng dụng không xác định, phiên đã hết hạn hoặc yêu cầu ủy quyền không hợp lệ." @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "Lượt dùng:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "Đang sử dụng trạng thái tài khoản thực tế" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "Đang sử dụng trạng thái kho quà tặng thực tế" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "Đang sử dụng trạng thái premium thực tế" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "Đang sử dụng trạng thái thực tế" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "Đang sử dụng trạng thái xác minh thực tế" @@ -37947,17 +38060,17 @@ msgstr "Đợi ở đây" msgid "Waiting for connection..." msgstr "Đang chờ kết nối..." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "Đang chờ khóa truy cập…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "Đang chờ chế độ chậm trong {forwardChannelDisplayName} hết hạn." #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "Đang chờ chế độ chậm trong một hoặc nhiều kênh đã chọn hết hạn." @@ -38156,7 +38269,7 @@ msgstr "Chúng tôi đã đổi kích thước và nén nhãn dán này thành 3 msgid "We sent a code to {email}." msgstr "Chúng tôi đã gửi mã đến {email}." -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "Chúng tôi sẽ điền sẵn {prefillEmail} khi trình duyệt mở trang đăng nhập." @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "Xem trước trang web" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "Chào mừng bạn trở lại" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Gói đăng ký {premiumProductName} của bạn đã hết hạn. Bạn đã mất tất cả các đặc quyền của {premiumProductName}. Vui lòng kích hoạt lại gói đăng ký để lấy lại quyền truy cập." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Phiên {productName} của bạn đã hết hạn. Vui lòng đăng nhập lại để tiếp tục ủy quyền cho ứng dụng này." @@ -39734,7 +39847,7 @@ msgstr "Tài khoản của bạn còn quá mới để gửi tin nhắn trong c msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "Yêu cầu tài khoản của bạn đang chờ phê duyệt. Bạn có thể đăng nhập sau khi quản trị viên phê duyệt." -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "Tài khoản của bạn yêu cầu xác thực đa yếu tố, nhưng chưa có phương thức nào được thiết lập trên thiết bị này. Vui lòng đăng nhập trên thiết bị có ứng dụng xác thực hoặc khóa bảo mật của bạn, hoặc liên hệ bộ phận hỗ trợ." @@ -39757,6 +39870,11 @@ msgstr "Hành động của bạn không thể hoàn tất vì đã bị hệ th msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "Đơn đăng ký của bạn đang chờ xét duyệt. Bạn vẫn có thể cập nhật chi tiết hoặc rút lại đơn đăng ký." +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "Trình duyệt của bạn không hỗ trợ khóa truy cập trên miền này. Hãy cập nhật trình duyệt hoặc đăng nhập bằng mật khẩu và mã hai yếu tố của bạn." + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "Thẻ của bạn đủ điều kiện. Đang chuyển bạn đến tran msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "Nhà mạng cho biết số này không còn được sử dụng. Vui lòng kiểm tra lại rồi thử lại, hoặc liên hệ bộ phận hỗ trợ nếu số này là chính xác." -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "Mã của bạn" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "Mã của bạn đã sẵn sàng" diff --git a/fluxer_app/src/features/i18n/locales/zh-CN/messages.po b/fluxer_app/src/features/i18n/locales/zh-CN/messages.po index bda56a93b..b657dd705 100644 --- a/fluxer_app/src/features/i18n/locales/zh-CN/messages.po +++ b/fluxer_app/src/features/i18n/locales/zh-CN/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, other {# 条被屏蔽的消息}}" msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, other {# 个社区}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, other {# 个社区因时空穿梭器故障暂时不可用。}}" @@ -1692,6 +1692,26 @@ msgstr "{productName} 在此社区中检测到潜在的突袭。邀请已暂停 msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} 检测到潜在的突袭,因此新用户暂时无法加入。" +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} 已迁移到 {host}。请将其添加到主屏幕或程序坞,然后使用此应用登录。" + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} 已迁移到 {host}。请在浏览器中安装,然后使用此应用登录。" + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} 已迁移到 {host}。安装新应用后,您将自动登录。" + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} 已移至 {host}。请在浏览器中打开以安装新应用。" + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} 需要麦克风权限才能列出你的设备。" msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} 需要屏幕录制权限。请在操作系统的隐私设置中允许屏幕录制,然后重启应用。" +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} 现已迁移至 {host}。请从新地址安装,然后使用此应用登录。" + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} 现已迁移至 {host}。请从新地址安装。" + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "投票" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "当 bot 范围不是唯一范围时,需要一个 redirect_uri。" @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "将机器人添加到社区并授予其请求的权限" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "添加评论(可选)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "添加 YouTube Data API 密钥,以丰富 YouTube 链接的信息。" #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "添加账号" @@ -4379,6 +4409,11 @@ msgstr "需要批准" msgid "Approve" msgstr "批准" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "从您已安装的 {productName} 应用批准此应用" + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "授权失败" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "授权失败。请重试。" @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "机器人权限" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "机器人权限不能为负数。" @@ -6329,10 +6364,10 @@ msgstr "无法在此上传文件" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "无法在此上传文件" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "选择背景" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "请选择 guild_id 或 channel_id,不能同时选择两者。" +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "选择“文件”,然后选择“添加到 Dock”。" + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "代码块" msgid "Code blocks" msgstr "代码块" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "验证码已过期" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "命令" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "评论已禁用,因为你选择的一个或多个频道开启了慢速模式。转发后,评论会立即被慢速模式拦截。" #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "由于“{forwardChannelDisplayName}”中开启了慢速模式,评论已禁用。转发后,评论会立即被慢速模式拦截。" #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "慢速模式开启时无法评论。" @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "社区消息通知" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "社区 MFA 要求提示栏" @@ -9182,7 +9222,7 @@ msgstr "继续举报社区" msgid "Continue to report user" msgstr "继续举报此用户" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "使用通行密钥继续" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "协调针对个人或社区的突袭、围攻或骚扰行为。" #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "已复制" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "复制频道链接" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "复制代码" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "当前使用次数:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "当前已忽略" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "当前显示" @@ -12555,6 +12595,11 @@ msgstr "不会屏蔽对方。你可以稍后重新打开。" msgid "Domain" msgstr "域名" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "域名已移动通知栏" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "荷兰语" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "例如:YubiKey、iPhone、工作电脑" @@ -14056,10 +14101,14 @@ msgstr "输入你的安全密钥 PIN 码以继续。" msgid "Enter the SMS code" msgstr "输入短信验证码" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "在浏览器中输入此代码以完成登录。" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "在你的旧版 {PRODUCT_NAME} 应用中输入此代码以完成登录。" + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "按 Enter 键<0><1>保存" @@ -14441,7 +14490,7 @@ msgstr "{absolute} 到期({relativeText})" msgid "Expires between {earliest} and {latest}" msgstr "将于 {earliest} 至 {latest} 之间过期" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "{remaining} 秒后过期" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "启动单点登录失败" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "切换账号失败" @@ -15413,17 +15462,17 @@ msgstr "为自己" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "本次验证,你需要向我们发送短信,而不是接收我们发来的短信。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "为保障你的账号安全,请先验证身份。" #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "强制禁用" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "强制启用" @@ -15540,7 +15589,7 @@ msgstr "忘记密码" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "忘记密码?" @@ -15571,7 +15620,7 @@ msgstr "转发媒体" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "转发消息" @@ -15965,11 +16014,11 @@ msgstr "生成可分享的链接,或复制合并后的自定义 CSS。" msgid "Generate invite link" msgstr "生成邀请链接" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "生成新验证码" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "正在生成验证码…" @@ -16045,6 +16094,11 @@ msgstr "获取 {productName} 桌面应用,即可全局使用按住说话,还 msgid "Get the desktop app" msgstr "获取桌面应用" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "获取新的{productName}应用" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "插入表情" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "安装 {productName},获得完整的共享源选择器和更顺畅的屏幕共享控制。" +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "从浏览器菜单中安装。" + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "安装新应用" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "授权请求无效" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "机器人权限无效。" @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "配对码无效或已过期。请重试。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "无效的 redirect_uri" @@ -19332,6 +19396,11 @@ msgstr "链接" msgid "Link (mature)" msgstr "链接(成人内容)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "关联新设备" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "消息显示设置仅在桌面端可用。" msgid "Message editing" msgstr "编辑消息" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "消息已转发" @@ -20746,7 +20815,7 @@ msgstr "消息输入按钮" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "消息太长" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "其他" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "缺少 client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "名称至少需要 2 个字符" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "命名通行密钥" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "命名通行密钥表单" @@ -22381,7 +22450,7 @@ msgstr "没有要保存的更改" msgid "No channels available" msgstr "没有可用频道" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "未找到频道" @@ -22824,7 +22893,7 @@ msgstr "未找到用户" msgid "No verification is required." msgstr "无需验证。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "没有可用的验证方式" @@ -23417,6 +23486,16 @@ msgstr "打开 {contextName}" msgid "Open {displayName}" msgstr "打开 {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "在 Safari 中打开 {host}。" + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "在浏览器中打开 {host}。" + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "打开“{macosSystemSettingsName}”→“{macosPrivacyAndSecuritySetti msgid "Open {productName}" msgstr "打开 {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "在浏览器中打开 {productName}" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "打开横幅选项" msgid "Open bookmarks" msgstr "打开书签" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "打开浏览器" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "在浏览器中打开表情" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "打开表情选择器" @@ -23809,6 +23893,16 @@ msgstr "在浏览器中打开下方链接,然后复制完整的 JSON 响应并 msgid "Open the menu and choose an option." msgstr "打开菜单并选择一个选项。" +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "打开新应用,然后选择“使用旧版 {productName} 应用登录”。接着选择“在此处关联新设备”,并输入它显示的验证码。" + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "打开新应用。您已登录。" + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "打开语音" msgid "Open voice & video settings" msgstr "打开语音和视频设置" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "打开浏览器,登录后输入以下代码以关联你的账号。" @@ -23868,6 +23962,10 @@ msgstr "打开浏览器,登录后输入以下代码以关联你的账号。" msgid "Open your messages" msgstr "打开短信应用" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "打开你的旧 {PRODUCT_NAME} 应用,选择“关联新设备”,然后输入下面的代码。" + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "打开手机短信应用,创建一条新短信。" @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "通行密钥" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "通行密钥名称" @@ -28138,7 +28236,7 @@ msgstr "饱和度" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "拖动预览" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "选择文本" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "选择要在桌面应用上登录的账号。" +msgid "Select the account you want to sign in with on your new device." +msgstr "选择您想在新设备上登录的账号。" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "发送" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "发送({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "会话" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "“{identifier}”的会话已过期。请重新登录。" @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "录制快捷键时,快捷键会暂停生效。" #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "请缩短消息后重试。" @@ -30217,6 +30315,11 @@ msgstr "显示最多 3 个(+5 徽章)" msgid "Show max 5 (+5 badge)" msgstr "显示最多 5 个(+5 徽章)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "给我看看" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "登录以领取你的礼物" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "通过浏览器登录" @@ -30496,6 +30599,11 @@ msgstr "通过浏览器登录" msgid "Sign in with a passkey" msgstr "使用通行密钥登录" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "使用你的旧版 {productName} 应用登录" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "登录信息" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "登录流程" @@ -31030,7 +31138,7 @@ msgstr "部分用户无法添加" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "在此语音频道中发言。没有此权限,成员将保持静音状 msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "在语音频道中发言。没有此权限,成员将保持静音状态,直到拥有“静音成员”权限的人解除静音。" +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "轻点“共享”,然后轻点“添加到主屏幕”。" + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "手机验证已暂缓。还剩一个邮箱验证步骤。" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "提供的 redirect_uri 未在此应用中注册。" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "提供的 redirect_uri 必须使用 HTTP 或 HTTPS。" @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "此动画过长或过大,无法裁剪。请尝试使用更短或更小的文件。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "此应用" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "此附件已过期" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "此机器人未公开,其他用户无法添加。" @@ -33757,7 +33870,7 @@ msgstr "群聊人数过多" msgid "This group is full. Remove someone before adding more people." msgstr "群聊已满。请先移除一些成员,再添加其他人。" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "此切换码已过期。请生成新切换码以继续。" @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "未知" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "未知应用、会话过期或授权请求无效。" @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "使用次数:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "正在使用实际账号状态" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "正在使用实际礼物库存状态" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "正在使用实际会员状态" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "正在使用实际状态" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "正在使用实际验证状态" @@ -37947,17 +38060,17 @@ msgstr "请稍候" msgid "Waiting for connection..." msgstr "正在等待连接…" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "正在等待通行密钥…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "正在等待 {forwardChannelDisplayName} 的慢速模式结束。" #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "正在等待一个或多个所选频道的慢速模式结束。" @@ -38156,7 +38269,7 @@ msgstr "我们已将此贴纸的尺寸调整并压缩到 320x320 像素,但它 msgid "We sent a code to {email}." msgstr "验证码已发送至 {email}。" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "浏览器登录页面打开后,我们将预填 {prefillEmail}。" @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "网页预览" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "欢迎回来" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "你的 {premiumProductName} 订阅已过期。你已失去所有 {premiumProductName} 福利。请重新激活订阅以恢复访问权限。" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "你的 {productName} 会话已过期。请重新登录以继续授权此应用。" @@ -39734,7 +39847,7 @@ msgstr "你的账号注册时间太短,无法在此社区中发送消息。" msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "你的账号申请正在等待审核。管理员批准后即可登录。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "你的账号需要多重身份验证,但此设备上未设置任何受支持的方式。请在已设置身份验证器应用或安全密钥的设备上登录,或联系支持团队。" @@ -39757,6 +39870,11 @@ msgstr "你的操作未能完成,因为它被我们的安全系统标记了。 msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "你的申请正在审核中。你仍可以更新展示信息或撤回申请。" +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "您的浏览器不支持此域名下的凭证。请更新您的浏览器,或使用密码和双因素代码登录。" + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "你的银行卡符合条件。正在将你跳转到付款页面。" msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "你的运营商称此号码未开通。请检查后重试,如果号码无误,请联系支持团队。" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "你的代码" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "你的代码已就绪" diff --git a/fluxer_app/src/features/i18n/locales/zh-TW/messages.po b/fluxer_app/src/features/i18n/locales/zh-TW/messages.po index 4ce8c9a54..3f583399b 100644 --- a/fluxer_app/src/features/i18n/locales/zh-TW/messages.po +++ b/fluxer_app/src/features/i18n/locales/zh-TW/messages.po @@ -938,7 +938,7 @@ msgstr "{count, plural, one {# 則被封鎖的訊息} other {# 則被封鎖的 msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {找到 # 個社群} other {找到 # 個社群}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1953 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# 個社群} other {# 個社群}}因時空電容器故障,暫時無法使用。" @@ -1692,6 +1692,26 @@ msgstr "{productName} 偵測到此社群可能遭到突襲。邀請功能已暫 msgid "{productName} detected a potential raid, so new users can't join right now." msgstr "{productName} 偵測到可能的突襲,因此新使用者目前無法加入。" +#. Banner in a web app installed on iPhone, iPad or Mac. productName is the app name. host is the new domain, for example fluxer.com. Home Screen and Dock are Apple names. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:38 +msgid "{productName} has moved to {host}. Add it to your Home Screen or Dock, then sign in with this app." +msgstr "{productName} 已移至 {host}。將它加入主畫面或 Dock,然後使用此應用程式登入。" + +#. Banner in an installed web app on other browsers. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:43 +msgid "{productName} has moved to {host}. Install it from your browser, then sign in with this app." +msgstr "{productName} 已移至 {host}。請從您的瀏覽器安裝,然後使用此應用程式登入。" + +#. Banner in an installed desktop web app after the account moved to the new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:28 +msgid "{productName} has moved to {host}. Install the new app and you will already be signed in." +msgstr "{productName} 已移至 {host}。安裝新應用程式後,您將自動登入。" + +#. Banner in an installed Android web app. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:33 +msgid "{productName} has moved to {host}. Open it in your browser to install the new app." +msgstr "{productName} 已移至 {host}。請在瀏覽器中開啟以安裝新應用程式。" + #. Profile membership label showing when the user account was created. #: src/features/user/components/popouts/UserProfileShared.tsx:53 msgid "{productName} member since" @@ -1732,6 +1752,16 @@ msgstr "{productName} 需要麥克風存取權限才能列出您的裝置。" msgid "{productName} needs screen recording access. Allow screen recording in your operating system privacy settings and restart the app." msgstr "{productName} 需要螢幕錄影權限。請在您的作業系統隱私設定中允許螢幕錄影,然後重新啟動應用程式。" +#. Intro in the modal that explains how to install the app from its new domain. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:22 +msgid "{productName} now lives at {host}. Install it from there, then sign in with this app." +msgstr "{productName} 現在已移至 {host}。請從該處安裝,然後使用此應用程式登入。" + +#. Intro in the modal that explains how to install the app from its new domain when the account already moved. productName is the app name. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:27 +msgid "{productName} now lives at {host}. Install it from there." +msgstr "{productName} 現在已移至 {host}。請從該處安裝。" + #. Short badge title in the user profile badges popout. Preserve {productName}; it is inserted by code. English locales use Title Case for official badge titles; other locales should use natural local capitalization. #: src/features/user/components/popouts/UserProfileBadges.tsx:31 msgid "{productName} Partner" @@ -2461,7 +2491,7 @@ msgid "a poll" msgstr "投票" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:53 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "當「bot」範圍不是唯一的範圍時,必須提供 redirect_uri。" @@ -2859,7 +2889,7 @@ msgid "Add a bot to a community with requested permissions" msgstr "將機器人新增至社群並授予要求的權限" #. Placeholder text in the optional comment field of the forward modal. -#: src/features/messaging/components/modals/ForwardModal.tsx:99 +#: src/features/messaging/components/modals/ForwardModal.tsx:96 msgid "Add a comment (optional)" msgstr "新增留言(選填)" @@ -2920,7 +2950,7 @@ msgid "Add a YouTube Data API key to enrich YouTube links." msgstr "新增 YouTube Data API 金鑰以豐富 YouTube 連結。" #. Short label in the authentication browser login handoff modal. Keep the tone plain and specific. -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:21 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:22 msgid "Add account" msgstr "新增帳號" @@ -4379,6 +4409,11 @@ msgstr "需要核准" msgid "Approve" msgstr "核准" +#. Hint under the sign-in option on fluxer.com that pairs a newly installed app with the old installed app. productName is the app name. +#: src/features/auth/flow/AuthLoginLayout.tsx:74 +msgid "Approve this app from the {productName} app you already use. No password needed." +msgstr "從您已安裝的 {productName} App 批准此 App。無需密碼。" + #. Discovery application status badge in the community Discovery settings tab. The community has been approved and is listed. #: src/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.tsx:45 msgid "Approved" @@ -4848,7 +4883,7 @@ msgid "Authorization failed" msgstr "授權失敗" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:74 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 msgid "Authorization failed. Try again." msgstr "授權失敗。請再試一次。" @@ -5826,7 +5861,7 @@ msgid "Bot permissions" msgstr "機器人權限" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:66 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 msgid "Bot permissions cannot be negative." msgstr "機器人權限不能是負數。" @@ -6329,10 +6364,10 @@ msgstr "無法在這裡上傳檔案" #: src/features/auth/components/modals/ClaimAccountModal.tsx:216 #: src/features/auth/components/modals/ClaimAccountModal.tsx:232 #: src/features/auth/components/modals/DeviceRevokeModal.tsx:72 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:80 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:86 #: src/features/auth/components/modals/PasskeyPinModal.tsx:104 -#: src/features/auth/components/modals/SudoVerificationModal.tsx:278 -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:282 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:164 #: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137 #: src/features/channel/components/MentionEveryonePopout.tsx:177 #: src/features/channel/components/modals/ChannelDeleteModal.tsx:53 @@ -6351,7 +6386,7 @@ msgstr "無法在這裡上傳檔案" #: src/features/messaging/components/modals/AttachmentEditModal.tsx:133 #: src/features/messaging/components/modals/EditAltTextModal.tsx:190 #: src/features/messaging/components/modals/ExternalLinkWarningModal.tsx:111 -#: src/features/messaging/components/modals/ForwardModal.tsx:650 +#: src/features/messaging/components/modals/ForwardModal.tsx:591 #: src/features/messaging/components/modals/ImageCropModal.tsx:1025 #: src/features/messaging/components/modals/MessageHistoryThresholdModal.tsx:107 #: src/features/moderation/components/modals/BanMemberModal.tsx:262 @@ -7462,10 +7497,15 @@ msgid "Choose background" msgstr "選擇背景" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:86 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 msgid "Choose either guild_id or channel_id, not both." msgstr "請選擇 guild_id 或 channel_id,不可兩者都選。" +#. Second install step on a Mac. File and Add to Dock are the names of the Safari menu items, use the names macOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:46 +msgid "Choose File, then Add to Dock." +msgstr "選擇「檔案」,然後選擇「加入 Dock」。" + #. Subtitle in the create-DM modal. Keep it short and calm. #: src/features/channel/utils/CreateDMModalUtils.tsx:39 msgid "Choose friends to message." @@ -8215,7 +8255,7 @@ msgstr "程式碼區塊" msgid "Code blocks" msgstr "程式碼區塊" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:102 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:110 msgid "Code expired" msgstr "驗證碼已過期" @@ -8385,17 +8425,17 @@ msgid "Commands" msgstr "指令" #. Helper text in the forward modal explaining why the optional comment field is disabled when at least one selected target has slowmode active. -#: src/features/messaging/components/modals/ForwardModal.tsx:71 +#: src/features/messaging/components/modals/ForwardModal.tsx:68 msgid "Comments are disabled because one or more selected channels have slowmode on. The comment would be blocked by slowmode right after the forward." msgstr "由於所選頻道中至少有一個已開啟慢速模式,留言功能已停用。轉傳後,留言會立即被慢速模式阻擋。" #. Helper text in the forward modal explaining why the optional comment field is disabled for a single selected channel with slowmode. -#: src/features/messaging/components/modals/ForwardModal.tsx:65 +#: src/features/messaging/components/modals/ForwardModal.tsx:62 msgid "Comments are disabled because slowmode is on in {forwardChannelDisplayName}. The comment would be blocked by slowmode right after the forward." msgstr "由於「{forwardChannelDisplayName}」已開啟慢速模式,留言功能已停用。轉傳後,留言會立即被慢速模式阻擋。" #. Tooltip on the disabled comment field in the forward modal when slowmode blocks comments. -#: src/features/messaging/components/modals/ForwardModal.tsx:95 +#: src/features/messaging/components/modals/ForwardModal.tsx:92 msgid "Comments are unavailable while slowmode is on." msgstr "慢速模式開啟時無法留言。" @@ -8551,7 +8591,7 @@ msgid "Community message notifications" msgstr "社群訊息通知" #. Developer control label for the community MFA requirement banner. -#: src/features/devtools/components/NagbarControls.ts:76 +#: src/features/devtools/components/NagbarControls.ts:81 msgid "Community MFA requirement nagbar" msgstr "社群 MFA 要求提示列" @@ -9182,7 +9222,7 @@ msgstr "繼續檢舉社群" msgid "Continue to report user" msgstr "繼續檢舉使用者" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:245 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:249 msgid "Continue with passkey" msgstr "使用通行金鑰繼續" @@ -9266,7 +9306,7 @@ msgid "Coordinates raids, brigading, or harassment against people or communities msgstr "協調對個人或社群發動突襲、集體圍攻或騷擾。" #. Generic short status label confirming a value was copied to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:381 msgid "Copied" msgstr "已複製" @@ -9368,7 +9408,7 @@ msgid "Copy channel link" msgstr "複製頻道連結" #. Action label that copies a code block to the clipboard. -#: src/features/auth/flow/HandoffCodeDisplay.tsx:157 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:165 #: src/features/i18n/utils/CommonMessageDescriptors.ts:393 msgid "Copy code" msgstr "複製代碼" @@ -10559,12 +10599,12 @@ msgid "Current uses:" msgstr "目前使用次數:" #. Developer nagbar status: the banner is dismissed in the current real state. -#: src/features/devtools/components/NagbarControls.ts:109 +#: src/features/devtools/components/NagbarControls.ts:114 msgid "Currently dismissed" msgstr "目前已關閉" #. Developer nagbar status: the banner is showing in the current real state. -#: src/features/devtools/components/NagbarControls.ts:113 +#: src/features/devtools/components/NagbarControls.ts:118 msgid "Currently showing" msgstr "目前顯示" @@ -12555,6 +12595,11 @@ msgstr "不會封鎖。您可以稍後再重新開啟。" msgid "Domain" msgstr "網域" +#. Developer or debug surface, keep terse and technical. Label in the developer Nagbar controls panel for the banner telling installed web apps that the app has moved to a new domain. +#: src/features/devtools/components/NagbarControls.ts:76 +msgid "Domain moved nagbar" +msgstr "網域已轉移通知列" + #. Label in the inbox commands. #: src/features/inbox/commands/InboxCommands.tsx:27 #: src/features/voice/components/modals/HideOwnCameraConfirmModal.tsx:59 @@ -12912,7 +12957,7 @@ msgid "Dutch" msgstr "荷蘭文" #. Placeholder or example text in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:31 msgid "e.g., YubiKey, iPhone, work computer" msgstr "例如:YubiKey、iPhone、公司電腦" @@ -14056,10 +14101,14 @@ msgstr "請輸入安全金鑰的 PIN 以繼續。" msgid "Enter the SMS code" msgstr "輸入簡訊驗證碼" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:124 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:132 msgid "Enter this code in your browser to complete sign-in." msgstr "請在瀏覽器中輸入此代碼以完成登入。" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:142 +msgid "Enter this code in your old {PRODUCT_NAME} app to complete sign-in." +msgstr "請在你的舊版 {PRODUCT_NAME} App 中輸入此代碼以完成登入。" + #: src/features/channel/components/EditingMessageInput.tsx:358 msgid "Enter to <0><1>save" msgstr "按 Enter 鍵<0><1>儲存" @@ -14441,7 +14490,7 @@ msgstr "將於 {absolute}({relativeText})到期" msgid "Expires between {earliest} and {latest}" msgstr "將於 {earliest} 到 {latest} 之間到期" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:142 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:150 msgid "Expires in {remaining}s" msgstr "{remaining} 秒後到期" @@ -14835,7 +14884,7 @@ msgid "Failed to start SSO" msgstr "無法啟動單一登入" #. Login flow error shown when switching to a saved account fails. -#: src/features/auth/flow/AuthLoginLayout.tsx:51 +#: src/features/auth/flow/AuthLoginLayout.tsx:58 msgid "Failed to switch account" msgstr "無法切換帳號" @@ -15413,17 +15462,17 @@ msgstr "自己使用" msgid "For this check, you send us a text message instead of receiving one from us." msgstr "這次驗證,您需要傳送簡訊給我們,而不是接收我們的簡訊。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:183 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:187 msgid "For your security, please confirm it's you to continue." msgstr "為了您的帳戶安全,請驗證是您本人操作,才能繼續。" #. Developer nagbar override status: the banner is forced to hide. -#: src/features/devtools/components/NagbarControls.ts:97 +#: src/features/devtools/components/NagbarControls.ts:102 msgid "Force disabled" msgstr "強制停用" #. Developer nagbar override status: the banner is forced to show. -#: src/features/devtools/components/NagbarControls.ts:93 +#: src/features/devtools/components/NagbarControls.ts:98 msgid "Force enabled" msgstr "強制啟用" @@ -15540,7 +15589,7 @@ msgstr "忘記密碼" #. Authentication link label that opens password recovery. #: src/features/auth/components/pages/ForgotPasswordPage.tsx:71 -#: src/features/auth/flow/AuthLoginLayout.tsx:63 +#: src/features/auth/flow/AuthLoginLayout.tsx:70 msgid "Forgot your password?" msgstr "忘記密碼?" @@ -15571,7 +15620,7 @@ msgstr "轉傳媒體" #. Keyboard shortcut action label shown in the shortcuts list and keybind editor. Keep short, sentence case. #. Title of the forward message modal. #: src/features/input/state/input_keybind/shared.ts:25 -#: src/features/messaging/components/modals/ForwardModal.tsx:86 +#: src/features/messaging/components/modals/ForwardModal.tsx:83 msgid "Forward message" msgstr "轉傳訊息" @@ -15965,11 +16014,11 @@ msgstr "產生可分享的連結,或複製合併後的自訂 CSS。" msgid "Generate invite link" msgstr "產生邀請連結" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:109 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:117 msgid "Generate new code" msgstr "產生新代碼" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:70 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:78 msgid "Generating code…" msgstr "正在產生代碼…" @@ -16045,6 +16094,11 @@ msgstr "取得 {productName} 電腦版應用程式,即可在整個系統使用 msgid "Get the desktop app" msgstr "取得電腦版應用程式" +#. Title of the modal that explains how to install the app from its new domain. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:18 +msgid "Get the new {productName} app" +msgstr "取得新的 {productName} App" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Type label for GIF media. #: src/features/expressions/utils/FavoriteMemeUtils.ts:10 @@ -18037,6 +18091,16 @@ msgstr "插入表情符號" msgid "Install {productName} for the full source picker and smoother screen-sharing controls." msgstr "安裝 {productName} 以取得完整的來源選擇器和更流暢的螢幕分享控制項。" +#. Second install step on other devices. Refers to the install option in the browser menu. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:51 +msgid "Install it from your browser menu." +msgstr "從瀏覽器選單安裝。" + +#. Button on the domain moved banner that installs the app from the new domain. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:48 +msgid "Install the new app" +msgstr "安裝新版 App" + #. Label for the installed desktop version in the Linux update modal. #: src/features/updater/commands/UpdaterModalCommands.tsx:136 msgid "Installed" @@ -18123,7 +18187,7 @@ msgid "Invalid authorization request" msgstr "無效的授權請求" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:62 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 msgid "Invalid bot permissions." msgstr "無效的機器人權限。" @@ -18164,7 +18228,7 @@ msgid "Invalid or expired code. Try again." msgstr "代碼無效或已過期。請再試一次。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:78 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 msgid "Invalid redirect_uri" msgstr "redirect_uri 無效" @@ -19332,6 +19396,11 @@ msgstr "連結" msgid "Link (mature)" msgstr "連結(成人內容)" +#. Button on the domain moved banner that opens the code entry used to sign in a new app. Must match the translation used in the "Sign in with your old {productName} app" instructions. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:60 +msgid "Link a new device" +msgstr "連結新裝置" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #: src/features/user/components/settings_utils/search_index/ApplicationsIndex.ts:96 msgid "Link builder" @@ -20697,7 +20766,7 @@ msgstr "訊息顯示設定僅供桌面版使用。" msgid "Message editing" msgstr "編輯訊息" -#: src/features/messaging/components/modals/ForwardModal.tsx:371 +#: src/features/messaging/components/modals/ForwardModal.tsx:309 msgid "Message forwarded" msgstr "訊息已轉傳" @@ -20746,7 +20815,7 @@ msgstr "訊息輸入按鈕" #. Error modal title in the forward modal when the optional comment exceeds the message length limit. #. Form validation error indicating the message exceeds the allowed length. -#: src/features/messaging/components/modals/ForwardModal.tsx:57 +#: src/features/messaging/components/modals/ForwardModal.tsx:54 #: src/features/ui/character_counter/CharacterCountMessages.ts:16 msgid "Message is too long" msgstr "訊息過長" @@ -21045,7 +21114,7 @@ msgid "Miscellaneous" msgstr "其他" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:36 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 msgid "Missing client_id" msgstr "缺少 client_id" @@ -21862,12 +21931,12 @@ msgid "Name must be at least 2 characters" msgstr "名稱至少需要 2 個字元" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 msgid "Name passkey" msgstr "命名通行金鑰" #. Accessible form label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:15 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:19 msgid "Name passkey form" msgstr "命名通行金鑰表單" @@ -22381,7 +22450,7 @@ msgstr "沒有要儲存的變更" msgid "No channels available" msgstr "沒有可用的頻道" -#: src/features/messaging/components/modals/ForwardModal.tsx:445 +#: src/features/messaging/components/modals/ForwardModal.tsx:383 #: src/features/search/components/search/ChannelFilterSheet.tsx:120 msgid "No channels found" msgstr "找不到任何頻道" @@ -22824,7 +22893,7 @@ msgstr "找不到使用者" msgid "No verification is required." msgstr "不需要驗證。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:189 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:193 msgid "No verification methods available" msgstr "沒有可用的驗證方式" @@ -23417,6 +23486,16 @@ msgstr "開啟「{contextName}」" msgid "Open {displayName}" msgstr "開啟 {displayName}" +#. First install step on Apple devices. host is the new domain, for example fluxer.com. Safari is the browser. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:32 +msgid "Open {host} in Safari." +msgstr "在 Safari 中開啟 {host}。" + +#. First install step on other devices. host is the new domain, for example fluxer.com. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:37 +msgid "Open {host} in your browser." +msgstr "在瀏覽器中開啟 {host}。" + #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. #: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:109 msgid "Open {labelText} in settings" @@ -23437,6 +23516,11 @@ msgstr "開啟「{macosSystemSettingsName}」,然後前往「{macosPrivacyAndS msgid "Open {productName}" msgstr "開啟 {productName}" +#. Button on the domain moved banner that opens the new domain in the browser. productName is the app name. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:52 +msgid "Open {productName} in your browser" +msgstr "在瀏覽器中開啟 {productName}" + #. Button or menu action label in the channel and chat gif picker grid item. Keep it concise. Preserve {title}; it is inserted by code. #: src/features/channel/components/pickers/gif/GifPickerGridItem.tsx:37 msgid "Open {title}" @@ -23510,7 +23594,7 @@ msgstr "開啟橫幅選項" msgid "Open bookmarks" msgstr "開啟書籤" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:178 msgid "Open browser" msgstr "開啟瀏覽器" @@ -23614,7 +23698,7 @@ msgid "Open emoji in browser" msgstr "在瀏覽器中開啟表情符號" #. Accessible label for the emoji picker trigger button in the forward modal comment field. -#: src/features/messaging/components/modals/ForwardModal.tsx:103 +#: src/features/messaging/components/modals/ForwardModal.tsx:100 #: src/features/user/components/modals/tabs/my_profile_tab/BioEditor.tsx:27 msgid "Open emoji picker" msgstr "開啟表情符號選取器" @@ -23809,6 +23893,16 @@ msgstr "請在瀏覽器中開啟下方連結,然後複製完整的 JSON 回應 msgid "Open the menu and choose an option." msgstr "開啟選單並選擇一個選項。" +#. Third install step. "Sign in with your old {productName} app" and "Link a new device" are button labels and must match their translations. productName is the app name. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:55 +msgid "Open the new app and choose Sign in with your old {productName} app. Then choose Link a new device here and enter the code it shows." +msgstr "打開新應用程式,然後選擇「使用舊版 {productName} 應用程式登入」。接著選擇「在此連結新裝置」,然後輸入顯示的代碼。" + +#. Third install step in a desktop browser that already moved the account to the new domain. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:62 +msgid "Open the new app. You are already signed in." +msgstr "開啟新版應用程式。您已登入。" + #. Tip explaining the keyboard shortcut for opening the shortcuts list. #: src/features/input/components/modals/KeyboardModeIntroModal.tsx:42 msgid "Open the shortcuts list for quick actions." @@ -23859,7 +23953,7 @@ msgstr "開啟語音" msgid "Open voice & video settings" msgstr "開啟語音與視訊設定" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:131 msgid "Open your browser, sign in, then enter the code below to link your account." msgstr "開啟您的瀏覽器,登入後輸入以下代碼,即可連結您的帳號。" @@ -23868,6 +23962,10 @@ msgstr "開啟您的瀏覽器,登入後輸入以下代碼,即可連結您的 msgid "Open your messages" msgstr "開啟您的訊息" +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:129 +msgid "Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below." +msgstr "打開你的舊版 {PRODUCT_NAME} App,選擇「連結新裝置」,然後輸入下方的代碼。" + #: src/features/user/components/modals/PhoneAddModal.tsx:327 msgid "Open your phone's messaging app and create a new text message." msgstr "開啟手機的訊息應用程式,然後建立新簡訊。" @@ -24222,7 +24320,7 @@ msgid "Passkey" msgstr "通行金鑰" #. Short label in the authentication passkey name modal. Keep the tone plain and specific. -#: src/features/auth/components/modals/PasskeyNameModal.tsx:23 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:27 msgid "Passkey name" msgstr "通行金鑰名稱" @@ -28138,7 +28236,7 @@ msgstr "飽和度" #. Button or menu action label in the rename channel modal. Keep it concise. #. Save button in the community-folder settings modal footer. #: src/features/app/components/dialogs/shared/MobileSettingsComponents.tsx:113 -#: src/features/auth/components/modals/PasskeyNameModal.tsx:87 +#: src/features/auth/components/modals/PasskeyNameModal.tsx:93 #: src/features/channel/components/modals/EditGroupBottomSheet.tsx:366 #: src/features/channel/components/modals/EditGroupModal.tsx:322 #: src/features/channel/components/modals/RenameChannelModal.tsx:28 @@ -28545,7 +28643,7 @@ msgstr "拖曳預覽" #: src/features/channel/components/bottomsheets/ChannelSearchBottomSheet.tsx:854 #: src/features/channel/components/channel_header/shared.ts:47 #: src/features/channel/components/shared/PickerSearchInput.tsx:21 -#: src/features/messaging/components/modals/ForwardModal.tsx:90 +#: src/features/messaging/components/modals/ForwardModal.tsx:87 #: src/features/search/components/modals/SearchProviderPickerModal.tsx:43 #: src/features/search/utils/QuickSwitcherModalUtils.tsx:45 #: src/features/theme_studio/ui/StudioSearchInput.tsx:16 @@ -29074,8 +29172,8 @@ msgid "Select text" msgstr "選取文字" #: src/features/auth/flow/DesktopHandoffAccountSelector.tsx:69 -msgid "Select the account you want to sign in with on the desktop app." -msgstr "請選擇您想登入桌面版應用程式的帳號。" +msgid "Select the account you want to sign in with on your new device." +msgstr "選擇您想在新裝置上登入的帳號。" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. #: src/features/user/components/modals/tabs/UserVoiceTab.tsx:120 @@ -29182,7 +29280,7 @@ msgid "Send" msgstr "傳送" #. Primary button label in the forward message modal. selectedCount is the number of selected destinations; selectionLimit is the maximum allowed. -#: src/features/messaging/components/modals/ForwardModal.tsx:107 +#: src/features/messaging/components/modals/ForwardModal.tsx:104 msgid "Send ({selectedCount}/{selectionLimit})" msgstr "傳送 ({selectedCount}/{selectionLimit})" @@ -29487,7 +29585,7 @@ msgid "Session" msgstr "工作階段" #. Login layout banner shown when the session for a specific account has expired. Account identifier is interpolated. -#: src/features/auth/flow/AuthLoginLayout.tsx:46 +#: src/features/auth/flow/AuthLoginLayout.tsx:53 msgid "Session expired for {identifier}. Sign in again." msgstr "「{identifier}」的工作階段已過期。請重新登入。" @@ -29970,7 +30068,7 @@ msgid "Shortcuts are paused while recording." msgstr "錄製期間快速鍵會暫停作用。" #. Body of the error modal shown when the forward modal comment exceeds the message length limit. -#: src/features/messaging/components/modals/ForwardModal.tsx:61 +#: src/features/messaging/components/modals/ForwardModal.tsx:58 msgid "Shorten the message and try again." msgstr "請縮短訊息後再試一次。" @@ -30217,6 +30315,11 @@ msgstr "顯示最多 3 個(+5 徽章)" msgid "Show max 5 (+5 badge)" msgstr "顯示最多 5 個(+5 徽章)" +#. Button on the domain moved banner that opens the install steps. +#: src/features/app/components/layout/app_layout/nagbars/DomainMovedNagbar.tsx:56 +msgid "Show me how" +msgstr "顯示操作說明" + #. Plus menu submenu toggle that shows the saved media (memes) button next to the textarea. #. Short label for an advanced message input preference. #: src/features/channel/components/textarea/TextareaPlusMenu.tsx:47 @@ -30488,7 +30591,7 @@ msgstr "登入以領取您的禮物" #. Passkey sign-in action that opens the browser flow from the desktop app. #: src/features/auth/flow/auth_login_core/AuthLoginPasskeyActions.tsx:64 -#: src/features/auth/flow/AuthLoginLayout.tsx:67 +#: src/features/auth/flow/AuthLoginLayout.tsx:79 msgid "Sign in via browser" msgstr "透過瀏覽器登入" @@ -30496,6 +30599,11 @@ msgstr "透過瀏覽器登入" msgid "Sign in with a passkey" msgstr "使用通行金鑰登入" +#. Sign-in option and modal title on fluxer.com that pairs a newly installed app with the old installed app still signed in on the previous domain. productName is the app name. +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:27 +msgid "Sign in with your old {productName} app" +msgstr "使用你舊的 {productName} App 登入" + #. Description shown when sign-in is restricted to the configured SSO provider. #: src/features/auth/flow/AuthSsoPanel.tsx:17 msgid "Sign in with your organization's single sign-on provider." @@ -30562,7 +30670,7 @@ msgid "Sign-in details" msgstr "登入詳細資訊" #. Accessible label for the stepped sign-in panel. -#: src/features/auth/flow/AuthLoginLayout.tsx:55 +#: src/features/auth/flow/AuthLoginLayout.tsx:62 msgid "Sign-in flow" msgstr "登入流程" @@ -31030,7 +31138,7 @@ msgstr "部分使用者無法新增" #. Generic error modal title for an unexpected failure. #. Generic error toast title. #: src/features/auth/flow/HandoffApprovalFlow.tsx:248 -#: src/features/auth/flow/HandoffCodeDisplay.tsx:82 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:90 #: src/features/i18n/utils/CommonMessageDescriptors.ts:274 #: src/lib/forms/index.tsx:18 msgid "Something went wrong" @@ -32688,6 +32796,11 @@ msgstr "在此語音頻道中發言。如果沒有此權限,成員會保持靜 msgid "Talk in voice channels. Without this permission, members stay muted until someone with the \"Mute members\" permission unmutes them." msgstr "在語音頻道中發言。如果沒有此權限,成員會保持靜音,直到擁有「將成員靜音」權限的人為其解除靜音。" +#. Second install step on iPhone and iPad. Share and Add to Home Screen are the names of the Safari menu items, use the names iOS shows in this language. +#: src/features/app/domain_migration/DomainMovedStepsModal.tsx:41 +msgid "Tap Share, then Add to Home Screen." +msgstr "點一下「分享」,然後點一下「加到主畫面」。" + #. Label in the note edit sheet. #: src/features/user/components/modals/NoteEditSheet.tsx:21 msgid "Tap to add a note" @@ -33207,12 +33320,12 @@ msgid "The phone check is set aside. One email step is left." msgstr "電話驗證已暫緩。剩下一個電子郵件驗證步驟。" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:58 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 msgid "The provided redirect_uri is not registered for this application." msgstr "提供的 redirect_uri 未在此應用程式中註冊。" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:49 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "提供的 redirect_uri 必須使用 HTTP 或 HTTPS。" @@ -33445,7 +33558,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "這段動畫太長或太大,無法裁切。請嘗試較短或較小的檔案。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:82 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 msgid "This application" msgstr "這個應用程式" @@ -33455,7 +33568,7 @@ msgid "This attachment has expired" msgstr "此附件已過期" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:70 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 msgid "This bot is not public and cannot be added by other users." msgstr "此機器人非公開,其他使用者無法新增。" @@ -33757,7 +33870,7 @@ msgstr "此群組人數過多" msgid "This group is full. Remove someone before adding more people." msgstr "此群組已滿。請先移除成員,才能再新增成員。" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:105 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:113 msgid "This handoff code has expired. Generate a new one to continue." msgstr "此交接碼已過期。請產生新的交接碼以繼續。" @@ -35480,7 +35593,7 @@ msgid "Unknown" msgstr "不明" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:45 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 msgid "Unknown application, expired session, or invalid authorization request." msgstr "不明應用程式、工作階段已過期或授權請求無效。" @@ -36862,27 +36975,27 @@ msgid "Uses:" msgstr "使用次數:" #. Developer nagbar status: banner follows real account state. -#: src/features/devtools/components/NagbarControls.ts:101 +#: src/features/devtools/components/NagbarControls.ts:106 msgid "Using actual account state" msgstr "使用實際帳號狀態" #. Developer nagbar status: banner follows real gift inventory state. -#: src/features/devtools/components/NagbarControls.ts:121 +#: src/features/devtools/components/NagbarControls.ts:126 msgid "Using actual gift inventory state" msgstr "使用實際禮物庫存狀態" #. Developer nagbar status: banner follows real premium subscription state. -#: src/features/devtools/components/NagbarControls.ts:117 +#: src/features/devtools/components/NagbarControls.ts:122 msgid "Using actual premium state" msgstr "使用實際的進階方案狀態" #. Developer nagbar status: banner follows the real app state. -#: src/features/devtools/components/NagbarControls.ts:125 +#: src/features/devtools/components/NagbarControls.ts:130 msgid "Using actual state" msgstr "使用實際狀態" #. Developer nagbar status: banner follows real email verification state. -#: src/features/devtools/components/NagbarControls.ts:105 +#: src/features/devtools/components/NagbarControls.ts:110 msgid "Using actual verification state" msgstr "使用實際驗證狀態" @@ -37947,17 +38060,17 @@ msgstr "請在此等候" msgid "Waiting for connection..." msgstr "正在等待連線…" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:232 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:236 msgid "Waiting for passkey…" msgstr "正在等候通行金鑰…" #. Inline notice in the forward modal when slowmode is active in the selected target channel. forwardChannelDisplayName is the channel name. -#: src/features/messaging/components/modals/ForwardModal.tsx:77 +#: src/features/messaging/components/modals/ForwardModal.tsx:74 msgid "Waiting for slowmode in {forwardChannelDisplayName} to expire." msgstr "正在等待「{forwardChannelDisplayName}」的慢速模式結束。" #. Inline notice in the forward modal when slowmode is active in one or more selected target channels. -#: src/features/messaging/components/modals/ForwardModal.tsx:82 +#: src/features/messaging/components/modals/ForwardModal.tsx:79 msgid "Waiting for slowmode in one or more selected channels to expire." msgstr "正在等待一個或多個所選頻道的慢速模式結束。" @@ -38156,7 +38269,7 @@ msgstr "我們已將此貼圖調整大小並壓縮至 320x320 像素,但它仍 msgid "We sent a code to {email}." msgstr "我們已將驗證碼傳送至 {email}。" -#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128 +#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:152 msgid "We will prefill {prefillEmail} once browser sign-in opens." msgstr "瀏覽器登入開啟後,我們會預填 {prefillEmail}。" @@ -38356,7 +38469,7 @@ msgid "Website previews" msgstr "網站預覽" #. Heading on the standard sign-in form. -#: src/features/auth/flow/AuthLoginLayout.tsx:59 +#: src/features/auth/flow/AuthLoginLayout.tsx:66 msgid "Welcome back" msgstr "歡迎回來" @@ -39699,7 +39812,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "您的 {premiumProductName} 訂閱已到期。您已失去所有 {premiumProductName} 福利。請重新啟用訂閱以重新取得存取權。" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:40 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "您的 {productName} 工作階段已過期。請重新登入以繼續授權此應用程式。" @@ -39734,7 +39847,7 @@ msgstr "您的帳號建立時間太短,無法在此社群傳送訊息。" msgid "Your account request is pending approval. You can sign in after an admin approves it." msgstr "您的帳號申請正在等待核准。管理員核准後即可登入。" -#: src/features/auth/components/modals/SudoVerificationModal.tsx:195 +#: src/features/auth/components/modals/SudoVerificationModal.tsx:199 msgid "Your account requires multi-factor authentication, but no supported methods are set up on this device. Sign in on a device with your authenticator app or security key, or contact support." msgstr "您的帳號需要多重要素驗證,但此裝置上未設定任何支援的方法。請使用已設定驗證器應用程式或安全金鑰的裝置登入,或聯絡客服。" @@ -39757,6 +39870,11 @@ msgstr "您的操作未能完成,因為系統偵測到有安全疑慮。如果 msgid "Your application is pending review. You can still update your listing details or withdraw the application." msgstr "您的申請正在審核中。您仍然可以更新刊登資訊或撤回申請。" +#. Error shown when a passkey prompt fails because the browser cannot use the passkey on this web address. Keep plain. +#: src/features/auth/utils/WebAuthnUtils.ts:18 +msgid "Your browser does not support passkeys on this domain. Update your browser, or sign in with your password and two-factor code." +msgstr "您的瀏覽器不支援此網域的通行密鑰。請更新您的瀏覽器,或使用密碼和雙重驗證碼登入。" + #. Body of the browser-audio-required modal explaining why the user must click before audio can play. #: src/features/voice/components/modals/AudioPlaybackPermissionModal.tsx:12 msgid "Your browser requires user interaction before audio can be played. Click the button below to enable voice chat." @@ -39775,11 +39893,11 @@ msgstr "您的卡片符合資格。正在將您重新導向至付款頁面。" msgid "Your carrier says this number isn't in service. Check it and try again, or contact support if it's correct." msgstr "您的電信業者表示此號碼未啟用。請檢查號碼後再試一次,或若號碼正確,請聯絡客服。" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:128 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:136 msgid "Your code" msgstr "您的代碼" -#: src/features/auth/flow/HandoffCodeDisplay.tsx:121 +#: src/features/auth/flow/HandoffCodeDisplay.tsx:129 msgid "Your code is ready" msgstr "您的代碼已準備好" diff --git a/fluxer_app/src/features/invite/utils/InviteUtils.ts b/fluxer_app/src/features/invite/utils/InviteUtils.ts index d58f79c4b..7f83e1603 100644 --- a/fluxer_app/src/features/invite/utils/InviteUtils.ts +++ b/fluxer_app/src/features/invite/utils/InviteUtils.ts @@ -16,6 +16,8 @@ const OFFICIAL_INVITE_URL_BASES = Object.freeze([ 'https://canary.fluxer.app/invite', 'https://web.fluxer.app/invite', 'https://web.canary.fluxer.app/invite', + 'https://fluxer.com/invite', + 'https://canary.fluxer.com/invite', 'https://fluxer.gg', 'https://fluxer.gg/invite', ]); diff --git a/fluxer_app/src/features/navigation/utils/DeepLinkUtils.ts b/fluxer_app/src/features/navigation/utils/DeepLinkUtils.ts index 6bfa5504f..0f189812e 100644 --- a/fluxer_app/src/features/navigation/utils/DeepLinkUtils.ts +++ b/fluxer_app/src/features/navigation/utils/DeepLinkUtils.ts @@ -206,7 +206,14 @@ export async function startDeepLinkHandling(): Promise { } } -const OFFICIAL_INTERNAL_APP_HOSTS = ['fluxer.app', 'canary.fluxer.app', 'web.fluxer.app', 'web.canary.fluxer.app']; +const OFFICIAL_INTERNAL_APP_HOSTS = [ + 'fluxer.app', + 'canary.fluxer.app', + 'web.fluxer.app', + 'web.canary.fluxer.app', + 'fluxer.com', + 'canary.fluxer.com', +]; const getNormalizedWebAppHost = (): string => { try { return new URL(RuntimeConfig.webAppBaseUrl).host.toLowerCase(); diff --git a/fluxer_app/src/features/platform/push/PushSubscriptionService.ts b/fluxer_app/src/features/platform/push/PushSubscriptionService.ts index a8fa66d66..1e35ce2b5 100644 --- a/fluxer_app/src/features/platform/push/PushSubscriptionService.ts +++ b/fluxer_app/src/features/platform/push/PushSubscriptionService.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {Endpoints} from '@app/features/app/constants/Endpoints'; +import {detectDomainMigrationInstallKind} from '@app/features/app/domain_migration/DomainMigrationBrowser'; import RuntimeConfig from '@app/features/app/state/RuntimeConfig'; import AppStorage from '@app/features/platform/state/PersistentStorage'; import {http} from '@app/features/platform/transport/RestTransport'; @@ -244,6 +245,7 @@ export async function registerPushSubscription(): Promise { }); return null; } + const installedApp = detectDomainMigrationInstallKind() !== 'none'; const lastEndpoint = readLastEndpoint(); const isRotation = lastEndpoint !== null && lastEndpoint !== subscription.endpoint; const response = isRotation @@ -255,6 +257,7 @@ export async function registerPushSubscription(): Promise { endpoint: subscription.endpoint, keys: {p256dh, auth}, user_agent: navigator.userAgent, + installed_app: installedApp, }, }) : await http.post<{ @@ -264,6 +267,7 @@ export async function registerPushSubscription(): Promise { endpoint: subscription.endpoint, keys: {p256dh, auth}, user_agent: navigator.userAgent, + installed_app: installedApp, }, }); writeLastEndpoint(subscription.endpoint); diff --git a/fluxer_app/src/features/platform/service_worker/Worker.ts b/fluxer_app/src/features/platform/service_worker/Worker.ts index fabde8765..e20d2e07d 100644 --- a/fluxer_app/src/features/platform/service_worker/Worker.ts +++ b/fluxer_app/src/features/platform/service_worker/Worker.ts @@ -450,7 +450,7 @@ const fetchInstanceConfig = async (): Promise<{ vapidKey: string | null; } | null> => { try { - const res = await fetch('/.well-known/fluxer', {credentials: 'include'}); + const res = await fetch('/api/.well-known/fluxer', {credentials: 'include'}); if (!res.ok) return null; const data = (await res.json()) as { endpoints?: { diff --git a/fluxer_app/src/features/platform/service_worker/WorkerAppShell.test.ts b/fluxer_app/src/features/platform/service_worker/WorkerAppShell.test.ts index c4f87ecce..14a7e48c4 100644 --- a/fluxer_app/src/features/platform/service_worker/WorkerAppShell.test.ts +++ b/fluxer_app/src/features/platform/service_worker/WorkerAppShell.test.ts @@ -30,6 +30,8 @@ const RENDERED_INDEX_DOCUMENT = [ '

', ].join(''); +const MARKETING_DOCUMENT = 'Marketing'; + const DEPLOYED_PRECACHE_MANIFEST: ReadonlyArray = [ {url: '/index.html', revision: '2757:1'}, {url: '/', revision: '2757:1'}, @@ -101,7 +103,9 @@ function createAppProxyFetch(navigationDelayMs: number): (request: Request) => P return new Response('console.log(1)', {headers: {'content-type': 'text/javascript'}}); } await delay(navigationDelayMs); - return new Response(RENDERED_INDEX_DOCUMENT, {headers: {'content-type': 'text/html; charset=utf-8'}}); + return new Response(RENDERED_INDEX_DOCUMENT, { + headers: {'content-type': 'text/html; charset=utf-8', 'x-fluxer-app-shell': '1'}, + }); }; } @@ -193,4 +197,21 @@ describe('WorkerAppShell', () => { expect(html).toContain('window.__FLUXER_BOOTSTRAP__'); expect(html).not.toContain('{{STATIC_CDN_ENDPOINT}}'); }); + + it('never keeps a document without the app shell marker as the app shell', async () => { + await seedAppShell(createRuntime(0)); + const marketingRuntime: AppShellRuntime = { + ...createRuntime(0), + fetch: async () => new Response(MARKETING_DOCUMENT, {headers: {'content-type': 'text/html; charset=utf-8'}}), + }; + await fetchAppShellNavigation(marketingRuntime, navigationRequest('/channels/@me')); + + const offlineRuntime: AppShellRuntime = { + ...createRuntime(0), + fetch: () => Promise.reject(new Error('Failed to fetch')), + }; + const response = await fetchAppShellNavigation(offlineRuntime, navigationRequest('/channels/@me')); + + expect(await response.text()).toBe(RENDERED_INDEX_DOCUMENT); + }); }); diff --git a/fluxer_app/src/features/platform/service_worker/WorkerAppShell.ts b/fluxer_app/src/features/platform/service_worker/WorkerAppShell.ts index 396d5c369..8b30dcb95 100644 --- a/fluxer_app/src/features/platform/service_worker/WorkerAppShell.ts +++ b/fluxer_app/src/features/platform/service_worker/WorkerAppShell.ts @@ -1,7 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later const APP_SHELL_CACHE_KEY = '/app-shell'; -const APP_SHELL_SEED_PATH = '/'; +const APP_SHELL_SEED_PATH = '/app'; +const APP_SHELL_MARKER_HEADER = 'x-fluxer-app-shell'; export interface PrecacheEntry { readonly url: string; @@ -22,6 +23,10 @@ export function isCacheableResponse(response: Response): boolean { return response.ok || response.type === 'opaque'; } +export function isAppShellResponse(response: Response): boolean { + return response.ok && response.headers.get(APP_SHELL_MARKER_HEADER) === '1'; +} + export function isPrecacheableAssetUrl(url: string): boolean { const pathname = url.split(/[?#]/, 1)[0].toLowerCase(); return pathname !== '/' && !pathname.endsWith('.html'); @@ -54,7 +59,7 @@ async function readAppShell(runtime: AppShellRuntime): Promise { - if (!runtime.caches || !isCacheableResponse(response)) { + if (!runtime.caches || !isAppShellResponse(response)) { return; } try { diff --git a/fluxer_app/src/features/platform/service_worker/WorkerFetchRouting.test.ts b/fluxer_app/src/features/platform/service_worker/WorkerFetchRouting.test.ts index d80872456..f52f50d1c 100644 --- a/fluxer_app/src/features/platform/service_worker/WorkerFetchRouting.test.ts +++ b/fluxer_app/src/features/platform/service_worker/WorkerFetchRouting.test.ts @@ -26,6 +26,15 @@ describe('WorkerFetchRouting', () => { expect( getWorkerFetchRoute(request(`${WORKER_ORIGIN}/admin`, {headers: {accept: 'text/html'}}), WORKER_ORIGIN), ).toBe('ignore'); + expect(getWorkerFetchRoute(request(`${WORKER_ORIGIN}/app`, {headers: {accept: 'text/html'}}), WORKER_ORIGIN)).toBe( + 'navigation', + ); + expect( + getWorkerFetchRoute(request(`${WORKER_ORIGIN}/migrate/export`, {headers: {accept: 'text/html'}}), WORKER_ORIGIN), + ).toBe('navigation'); + expect(getWorkerFetchRoute(request(`${WORKER_ORIGIN}/`, {headers: {accept: 'text/html'}}), WORKER_ORIGIN)).toBe( + 'ignore', + ); expect(getWorkerFetchRoute(request(`${WORKER_ORIGIN}/manifest.json`), WORKER_ORIGIN)).toBe('metadata'); expect(getWorkerFetchRoute(request(`${WORKER_ORIGIN}/version.json`), WORKER_ORIGIN)).toBe('metadata'); }); diff --git a/fluxer_app/src/features/platform/service_worker/WorkerNavigation.ts b/fluxer_app/src/features/platform/service_worker/WorkerNavigation.ts index 65b128bbf..e1ac49114 100644 --- a/fluxer_app/src/features/platform/service_worker/WorkerNavigation.ts +++ b/fluxer_app/src/features/platform/service_worker/WorkerNavigation.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later const APP_NAVIGATION_EXACT_PATHS = new Set([ - '/', + '/app', '/login', '/register', '/forgot', @@ -27,7 +27,7 @@ const APP_NAVIGATION_EXACT_PATHS = new Set([ '/theme', ]); -const APP_NAVIGATION_PREFIXES = ['/channels/', '/users/', '/invite/', '/gift/', '/theme/']; +const APP_NAVIGATION_PREFIXES = ['/channels/', '/users/', '/invite/', '/gift/', '/theme/', '/migrate/']; export function isAppNavigationPath(pathname: string): boolean { const normalizedPathname = pathname.length > 1 ? pathname.replace(/\/+$/, '') : pathname; diff --git a/fluxer_app/src/features/platform/types/Electron.ts b/fluxer_app/src/features/platform/types/Electron.ts index 42bbee6b3..21c2cc64e 100644 --- a/fluxer_app/src/features/platform/types/Electron.ts +++ b/fluxer_app/src/features/platform/types/Electron.ts @@ -440,6 +440,7 @@ export interface ElectronAPI { virtmic: VirtmicApi; nativeAudio: NativeAudioApi; voiceEngine?: VoiceEngineV2BridgeHardwareEncoderApi; + domainMigration?: {version: number; setAppOrigin(origin: string): Promise}; } export type VirtmicUnavailableReason = diff --git a/fluxer_app/src/features/platform/utils/WorkerAssetUrl.ts b/fluxer_app/src/features/platform/utils/WorkerAssetUrl.ts index 98fa40722..f3a0fabb4 100644 --- a/fluxer_app/src/features/platform/utils/WorkerAssetUrl.ts +++ b/fluxer_app/src/features/platform/utils/WorkerAssetUrl.ts @@ -1,7 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-or-later const ASSETS_PATH_SEGMENT = 'assets'; -const OFFICIAL_WORKER_ASSET_HOSTS = new Set(['web.fluxer.app', 'web.canary.fluxer.app']); +const OFFICIAL_WORKER_ASSET_HOSTS = new Set([ + 'web.fluxer.app', + 'web.canary.fluxer.app', + 'fluxer.com', + 'canary.fluxer.com', +]); const WORKER_ASSET_VERSION_QUERY_PARAM = 'v'; function getProxiedAssetPath(pathname: string): string | null { diff --git a/fluxer_app/src/features/theme/utils/ThemeUtils.ts b/fluxer_app/src/features/theme/utils/ThemeUtils.ts index 47b1513b6..a067c8c64 100644 --- a/fluxer_app/src/features/theme/utils/ThemeUtils.ts +++ b/fluxer_app/src/features/theme/utils/ThemeUtils.ts @@ -9,6 +9,8 @@ const OFFICIAL_THEME_URL_BASES = Object.freeze([ 'https://canary.fluxer.app/theme', 'https://web.fluxer.app/theme', 'https://web.canary.fluxer.app/theme', + 'https://fluxer.com/theme', + 'https://canary.fluxer.com/theme', ]); const appendThemePath = (endpoint: string | null | undefined): string | null => { if (!endpoint) return null; diff --git a/fluxer_app/src/features/trusted_domain/state/TrustedDomain.ts b/fluxer_app/src/features/trusted_domain/state/TrustedDomain.ts index fdb92df58..6adfa8ae1 100644 --- a/fluxer_app/src/features/trusted_domain/state/TrustedDomain.ts +++ b/fluxer_app/src/features/trusted_domain/state/TrustedDomain.ts @@ -8,6 +8,8 @@ import {makeAutoObservable} from 'mobx'; const BUILT_IN_TRUST_PATTERNS = [ 'fluxer.app', '*.fluxer.app', + 'fluxer.com', + '*.fluxer.com', 'fluxerstatus.com', '*.fluxerstatus.com', 'fluxer.gg', diff --git a/fluxer_app/src/features/ui/state/Nagbar.ts b/fluxer_app/src/features/ui/state/Nagbar.ts index 8300eb5c0..1d2a7a11e 100644 --- a/fluxer_app/src/features/ui/state/Nagbar.ts +++ b/fluxer_app/src/features/ui/state/Nagbar.ts @@ -42,6 +42,7 @@ export interface NagbarSettings { forceScheduledMaintenance: boolean; forceVoiceSessionRestore: boolean; forceGuildMfaRequirement: boolean; + forceDomainMoved: boolean; forceHideConnectionNotice: boolean; forceHideEmailVerification: boolean; forceHideIOSInstall: boolean; @@ -62,6 +63,7 @@ export interface NagbarSettings { forceHideScheduledMaintenance: boolean; forceHideVoiceSessionRestore: boolean; forceHideGuildMfaRequirement: boolean; + forceHideDomainMoved: boolean; } export type NagbarToggleKey = Exclude< @@ -114,6 +116,7 @@ export class Nagbar implements NagbarSettings { forceScheduledMaintenance = false; forceVoiceSessionRestore = false; forceGuildMfaRequirement = false; + forceDomainMoved = false; forceConnectionNotice = false; forceHideOffline = false; forceHideEmailVerification = false; @@ -135,6 +138,7 @@ export class Nagbar implements NagbarSettings { forceHideScheduledMaintenance = false; forceHideVoiceSessionRestore = false; forceHideGuildMfaRequirement = false; + forceHideDomainMoved = false; forceHideConnectionNotice = false; constructor() { @@ -430,6 +434,7 @@ export class Nagbar implements NagbarSettings { this.forceScheduledMaintenance = false; this.forceVoiceSessionRestore = false; this.forceGuildMfaRequirement = false; + this.forceDomainMoved = false; this.forceConnectionNotice = false; this.forceHideOffline = false; this.forceHideEmailVerification = false; @@ -451,6 +456,7 @@ export class Nagbar implements NagbarSettings { this.forceHideScheduledMaintenance = false; this.forceHideVoiceSessionRestore = false; this.forceHideGuildMfaRequirement = false; + this.forceHideDomainMoved = false; this.forceHideConnectionNotice = false; } diff --git a/fluxer_app/src/index.tsx b/fluxer_app/src/index.tsx index 3370781f5..e16b07673 100644 --- a/fluxer_app/src/index.tsx +++ b/fluxer_app/src/index.tsx @@ -18,6 +18,7 @@ import {Routes} from '@app/app/Routes'; import {AppErrorBoundary} from '@app/features/app/components/AppErrorBoundary'; import {BootstrapErrorScreen} from '@app/features/app/components/BootstrapErrorScreen'; import {ErrorFallback} from '@app/features/app/components/ErrorFallback'; +import {runDomainMigrationPreMount} from '@app/features/app/domain_migration/DomainMigrationPreMount'; import {installSelfXssNotice} from '@app/features/devtools/utils/SelfXssNotice'; import {AppI18nProvider} from '@app/features/i18n/components/AppI18nProvider'; import {installLocaleSwitchWatchdog} from '@app/features/i18n/utils/LocaleSwitchWatchdog'; @@ -169,6 +170,9 @@ async function bootstrapApp(): Promise { } async function bootstrap(): Promise { + if (await runDomainMigrationPreMount()) { + return; + } scheduleNonLatinScriptFaces(); await initI18n(); installLocaleSwitchWatchdog(); diff --git a/fluxer_app/src/types/web-install.d.ts b/fluxer_app/src/types/web-install.d.ts new file mode 100644 index 000000000..be8969a14 --- /dev/null +++ b/fluxer_app/src/types/web-install.d.ts @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +export interface WebInstallResult { + manifestId: string; +} + +declare global { + interface Navigator { + install?: (installUrl?: string, manifestId?: string) => Promise; + } +} diff --git a/fluxer_app_proxy/src/bootstrap.rs b/fluxer_app_proxy/src/bootstrap.rs index 7f2a5d8ce..8e809d031 100644 --- a/fluxer_app_proxy/src/bootstrap.rs +++ b/fluxer_app_proxy/src/bootstrap.rs @@ -71,6 +71,27 @@ pub fn build_bootstrap_script( ) } +pub fn rewrite_endpoints_for_same_origin_host(instance: &mut serde_json::Value, host: &str) { + let Some(endpoints) = instance + .get_mut("endpoints") + .and_then(serde_json::Value::as_object_mut) + else { + return; + }; + let origin = format!("https://{host}"); + let api = format!("{origin}/api"); + for (key, value) in [ + ("api_client", &api), + ("api", &api), + ("webapp", &origin), + ("app", &origin), + ] { + if let Some(endpoint) = endpoints.get_mut(key) { + *endpoint = serde_json::Value::String(value.clone()); + } + } +} + fn api_public_endpoint<'a>( configured: Option<&'a str>, discovery: &'a DiscoveryResponse, @@ -541,6 +562,50 @@ mod tests { assert_eq!(api_public_endpoint(None, &discovery), None); } + #[test] + fn a_same_origin_host_takes_over_the_client_and_web_app_endpoints() { + let mut instance = serde_json::json!({ + "endpoints": { + "api": "https://web.fluxer.app/api", + "api_client": "https://web.fluxer.app/api", + "api_public": "https://api.fluxer.app", + "gateway": "wss://gateway.fluxer.app", + "webapp": "https://web.fluxer.app", + "app": "https://web.fluxer.app", + "marketing": "https://fluxer.app" + } + }); + rewrite_endpoints_for_same_origin_host(&mut instance, "fluxer.com"); + assert_eq!( + instance["endpoints"], + serde_json::json!({ + "api": "https://fluxer.com/api", + "api_client": "https://fluxer.com/api", + "api_public": "https://api.fluxer.app", + "gateway": "wss://gateway.fluxer.app", + "webapp": "https://fluxer.com", + "app": "https://fluxer.com", + "marketing": "https://fluxer.app" + }) + ); + } + + #[test] + fn a_same_origin_host_never_invents_endpoints_discovery_left_out() { + let mut instance = serde_json::json!({ + "endpoints": {"api_client": "https://web.fluxer.app/api"} + }); + rewrite_endpoints_for_same_origin_host(&mut instance, "fluxer.com"); + assert_eq!( + instance, + serde_json::json!({"endpoints": {"api_client": "https://fluxer.com/api"}}) + ); + + let mut without_endpoints = serde_json::json!({"name": "test"}); + rewrite_endpoints_for_same_origin_host(&mut without_endpoints, "fluxer.com"); + assert_eq!(without_endpoints, serde_json::json!({"name": "test"})); + } + #[test] fn the_boot_script_hands_the_repaired_endpoint_to_both_globals() { let discovery = discovery_offering("https://chat.example.test:8443/api"); diff --git a/fluxer_app_proxy/src/config.rs b/fluxer_app_proxy/src/config.rs index 714c015f6..44e796a04 100644 --- a/fluxer_app_proxy/src/config.rs +++ b/fluxer_app_proxy/src/config.rs @@ -356,6 +356,8 @@ pub struct AppProxyConfig { pub geoip_s3_config: Option, pub trust_client_ip_header: bool, pub client_ip_header_name: String, + pub same_origin_hosts: Vec, + pub manifest_scope_extensions: Vec, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -511,10 +513,101 @@ impl AppProxyConfig { ) .trim() .to_ascii_lowercase(), + same_origin_hosts: parse_same_origin_hosts( + "FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS", + &cfg::read_env("FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS", ""), + ), + manifest_scope_extensions: parse_manifest_scope_extensions( + "FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS", + &cfg::read_env("FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS", ""), + ), } } } +fn parse_manifest_scope_extensions(name: &'static str, raw: &str) -> Vec { + let mut origins: Vec = Vec::new(); + for value in raw + .split([',', ' ', '\t', '\n']) + .map(str::trim) + .filter(|value| !value.is_empty()) + { + match parse_manifest_scope_extension_origin(name, value) { + Ok(origin) => { + if !origins.contains(&origin) { + origins.push(origin); + } + } + Err(error) => warn_invalid(&error), + } + } + origins +} + +fn parse_manifest_scope_extension_origin( + name: &'static str, + value: &str, +) -> Result { + let origin = Url::parse(value).ok().filter(|url| { + url.scheme() == "https" + && url.host_str().is_some() + && url.username().is_empty() + && url.password().is_none() + && url.path() == "/" + && url.query().is_none() + && url.fragment().is_none() + }); + match origin { + Some(url) => Ok(url.origin().ascii_serialization()), + None => Err(InvalidAppProxyEnvironmentError::new( + name, + value, + "an HTTPS origin without a path, query or credentials", + )), + } +} + +fn parse_same_origin_hosts(name: &'static str, raw: &str) -> Vec { + let mut hosts: Vec = Vec::new(); + for value in raw + .split([',', ' ', '\t', '\n']) + .map(str::trim) + .filter(|value| !value.is_empty()) + { + match parse_same_origin_host(name, value) { + Ok(host) => { + if !hosts.contains(&host) { + hosts.push(host); + } + } + Err(error) => warn_invalid(&error), + } + } + hosts +} + +fn parse_same_origin_host( + name: &'static str, + value: &str, +) -> Result { + let host = value.trim_end_matches('.').to_ascii_lowercase(); + let is_hostname = !host.is_empty() + && Url::parse(&format!("https://{host}/")).is_ok_and(|url| { + url.host_str() == Some(host.as_str()) + && url.port().is_none() + && url.path() == "/" + && url.username().is_empty() + }); + if !is_hostname { + return Err(InvalidAppProxyEnvironmentError::new( + name, + value, + "a bare hostname without a scheme, port or path", + )); + } + Ok(host) +} + fn resolve_discovery_upstream_url_from_env() -> String { resolve_discovery_upstream_url(|name| env::var(name).ok()) } @@ -699,6 +792,65 @@ mod tests { assert!(error.to_string().contains("FLUXER_PUBLIC_ORIGIN")); } + #[test] + fn same_origin_hosts_default_to_none() { + assert!(parse_same_origin_hosts("TEST_SAME_ORIGIN_HOSTS", "").is_empty()); + assert!(parse_same_origin_hosts("TEST_SAME_ORIGIN_HOSTS", " , ").is_empty()); + } + + #[test] + fn same_origin_hosts_are_normalised_and_deduplicated() { + assert_eq!( + parse_same_origin_hosts( + "TEST_SAME_ORIGIN_HOSTS", + " web.fluxer.app, Fluxer.COM,fluxer.com. ,fluxer.com" + ), + vec!["web.fluxer.app".to_owned(), "fluxer.com".to_owned()] + ); + } + + #[test] + fn same_origin_hosts_drop_anything_but_a_bare_hostname() { + assert_eq!( + parse_same_origin_hosts( + "TEST_SAME_ORIGIN_HOSTS", + "https://fluxer.com,fluxer.com:443,fluxer.com/api,user@fluxer.com,canary.fluxer.com" + ), + vec!["canary.fluxer.com".to_owned()] + ); + } + + #[test] + fn manifest_scope_extensions_default_to_none() { + assert!(parse_manifest_scope_extensions("TEST_SCOPE_EXTENSIONS", "").is_empty()); + assert!(parse_manifest_scope_extensions("TEST_SCOPE_EXTENSIONS", " , ").is_empty()); + } + + #[test] + fn manifest_scope_extensions_are_normalised_and_deduplicated() { + assert_eq!( + parse_manifest_scope_extensions( + "TEST_SCOPE_EXTENSIONS", + " https://Fluxer.COM, https://fluxer.com/ ,https://canary.fluxer.com:443" + ), + vec![ + "https://fluxer.com".to_owned(), + "https://canary.fluxer.com".to_owned() + ] + ); + } + + #[test] + fn manifest_scope_extensions_drop_anything_but_an_https_origin() { + assert_eq!( + parse_manifest_scope_extensions( + "TEST_SCOPE_EXTENSIONS", + "fluxer.com,http://fluxer.com,https://fluxer.com/app,https://fluxer.com/?a=1,https://user@fluxer.com,https://canary.fluxer.com" + ), + vec!["https://canary.fluxer.com".to_owned()] + ); + } + #[test] fn csp_config_default_has_no_extra_sources() { let c = CspConfig::default(); diff --git a/fluxer_app_proxy/src/routes/spa_index.rs b/fluxer_app_proxy/src/routes/spa_index.rs index 44dffcd3a..a4c58e369 100644 --- a/fluxer_app_proxy/src/routes/spa_index.rs +++ b/fluxer_app_proxy/src/routes/spa_index.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -use crate::bootstrap::{build_bootstrap_script, inject_bootstrap}; -use crate::config::HttpEndpoint; +use crate::bootstrap::{ + build_bootstrap_script, inject_bootstrap, rewrite_endpoints_for_same_origin_host, +}; +use crate::config::{AppProxyConfig, HttpEndpoint}; use crate::csp::{RuntimeCspSources, generate_nonce}; use crate::discovery_cache::{DiscoveryResponse, discovery_endpoint}; use crate::geoip::build_geoip_response; @@ -141,13 +143,16 @@ async fn serve_static_file( async fn serve_spa_index(state: &AppState, headers: &HeaderMap) -> Response { let should_bust_dev_assets = state.config.index_upstream_url.is_some(); - let discovery = match refresh_discovery_for_spa(state).await { + let mut discovery = match refresh_discovery_for_spa(state).await { Some(d) => d, None => { tracing::error!("discovery cache empty, cannot serve SPA"); return StatusCode::SERVICE_UNAVAILABLE.into_response(); } }; + if let Some(host) = same_origin_host(&state.config, headers) { + rewrite_endpoints_for_same_origin_host(&mut discovery.data, host); + } let nonce = generate_nonce(); let runtime_csp_sources = build_runtime_csp_sources(state, &discovery); @@ -192,6 +197,29 @@ async fn serve_spa_index(state: &AppState, headers: &HeaderMap) -> Response { build_spa_response(html, csp, should_bust_dev_assets) } +fn same_origin_host<'a>(config: &'a AppProxyConfig, headers: &HeaderMap) -> Option<&'a str> { + if config.same_origin_hosts.is_empty() { + return None; + } + let host = request_hostname(headers.get(header::HOST)?.to_str().ok()?)?; + config + .same_origin_hosts + .iter() + .find(|candidate| candidate.eq_ignore_ascii_case(host)) + .map(String::as_str) +} + +fn request_hostname(authority: &str) -> Option<&str> { + let authority = authority.trim(); + let hostname = if authority.starts_with('[') { + &authority[..=authority.find(']')?] + } else { + authority.split(':').next()? + }; + let hostname = hostname.trim_end_matches('.'); + (!hostname.is_empty()).then_some(hostname) +} + #[derive(Debug)] struct SpaDocumentSizeLimitError { attempted_bytes: usize, @@ -432,6 +460,10 @@ fn build_spa_response(html: Box, csp: HeaderValue, dev_no_store: bool) -> R headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache")); } super::set_security_headers(headers); + headers.insert( + HeaderName::from_static("x-fluxer-app-shell"), + HeaderValue::from_static("1"), + ); headers.insert( axum::http::HeaderName::from_static("accept-ch"), HeaderValue::from_static(ACCEPT_CH_VALUE), @@ -755,6 +787,8 @@ mod tests { const DISCOVERY_BODY_WITH_BOTH_ENDPOINTS: &str = r#"{"api_code_version":"proxy-test","endpoints":{"static_cdn":"https://cdn.example.test","media":"https://media.example.test"}}"#; + const DISCOVERY_BODY_WITH_WEB_APP_ENDPOINTS: &str = r#"{"api_code_version":"proxy-test","endpoints":{"api":"https://web.fluxer.app/api","api_client":"https://web.fluxer.app/api","api_public":"https://api.fluxer.app","webapp":"https://web.fluxer.app","static_cdn":"https://cdn.example.test","media":"https://media.example.test"}}"#; + const DISCOVERY_BODY_WITHOUT_ENDPOINTS: &str = r#"{"api_code_version":"proxy-test"}"#; const DISCOVERY_BODY_SELF_HOSTED: &str = r#"{"api_code_version":"proxy-test","endpoints":{"static_cdn":"https://cdn.example.test","media":"https://media.example.test"},"features":{"self_hosted":true}}"#; @@ -924,6 +958,100 @@ mod tests { String::from_utf8(bytes.to_vec()).unwrap() } + async fn spa_state_with_same_origin_hosts(hosts: &[&str]) -> AppState { + let mut state = assemble_spa_state( + ReleaseChannel::Stable, + Some(SHELL_WITH_ENDPOINT_HOLES), + DISCOVERY_BODY_WITH_WEB_APP_ENDPOINTS, + None, + None, + ) + .await; + let mut config = (*state.config).clone(); + config.same_origin_hosts = hosts.iter().map(|host| (*host).to_owned()).collect(); + state.config = Arc::new(config); + state + } + + fn request_from_host(host: &str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert(header::HOST, HeaderValue::from_str(host).unwrap()); + headers + } + + #[test] + fn the_request_hostname_drops_its_port_and_trailing_dot() { + assert_eq!(request_hostname("fluxer.com"), Some("fluxer.com")); + assert_eq!(request_hostname("Fluxer.com:443"), Some("Fluxer.com")); + assert_eq!(request_hostname("fluxer.com.:8443"), Some("fluxer.com")); + assert_eq!(request_hostname("[::1]:8080"), Some("[::1]")); + assert_eq!(request_hostname(":443"), None); + assert_eq!(request_hostname("[::1"), None); + } + + #[tokio::test] + async fn a_listed_host_gets_same_origin_endpoints_in_its_bootstrap() { + let state = spa_state_with_same_origin_hosts(&["web.fluxer.app", "fluxer.com"]).await; + + let response = serve_spa_index(&state, &request_from_host("FLUXER.com:443")).await; + assert_eq!(response.status(), StatusCode::OK); + let served = read_document(response).await; + assert!(served.contains(r#""api_client":"https://fluxer.com/api""#)); + assert!(served.contains(r#""api":"https://fluxer.com/api""#)); + assert!(served.contains(r#""webapp":"https://fluxer.com""#)); + assert!(served.contains(r#""api_public":"https://api.fluxer.app""#)); + assert!(!served.contains("https://web.fluxer.app")); + + let cached = state.discovery_cache.get().await.unwrap(); + assert_eq!( + cached.data["endpoints"]["api_client"], "https://web.fluxer.app/api", + "the shared discovery snapshot was rewritten for one request" + ); + + let response = serve_spa_index(&state, &request_from_host("web.fluxer.app")).await; + let served = read_document(response).await; + assert!(served.contains(r#""api_client":"https://web.fluxer.app/api""#)); + assert!(!served.contains("https://fluxer.com")); + } + + #[tokio::test] + async fn an_unlisted_host_keeps_the_discovered_endpoints() { + let state = spa_state_with_same_origin_hosts(&["fluxer.com"]).await; + + for headers in [request_from_host("evil.example"), HeaderMap::new()] { + let response = serve_spa_index(&state, &headers).await; + let served = read_document(response).await; + assert!(served.contains(r#""api_client":"https://web.fluxer.app/api""#)); + assert!(served.contains(r#""webapp":"https://web.fluxer.app""#)); + assert!(!served.contains("https://fluxer.com")); + } + } + + #[tokio::test] + async fn no_host_is_rewritten_when_none_is_configured() { + let state = spa_state_with_same_origin_hosts(&[]).await; + + let response = serve_spa_index(&state, &request_from_host("fluxer.com")).await; + let served = read_document(response).await; + assert!(served.contains(r#""api_client":"https://web.fluxer.app/api""#)); + } + + #[tokio::test] + async fn the_spa_document_marks_itself_as_the_app_shell() { + let state = + spa_state_serving(ReleaseChannel::Canary, Some(SHELL_WITH_ENDPOINT_HOLES)).await; + + let response = serve_spa_index(&state, &HeaderMap::new()).await; + assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response + .headers() + .get("x-fluxer-app-shell") + .and_then(|value| value.to_str().ok()), + Some("1") + ); + } + #[tokio::test] async fn the_live_branch_serves_a_rendered_document_and_not_the_raw_shell() { let state = diff --git a/fluxer_app_proxy/src/routes/spa_static.rs b/fluxer_app_proxy/src/routes/spa_static.rs index e31eacf87..54b26b907 100644 --- a/fluxer_app_proxy/src/routes/spa_static.rs +++ b/fluxer_app_proxy/src/routes/spa_static.rs @@ -26,15 +26,46 @@ pub async fn version_json(State(state): State) -> Response { pub async fn manifest_json(State(state): State) -> Response { let static_cdn_endpoint = runtime_static_cdn_endpoint(&state).await; - serve_static_text_file_with_cdn( + serve_static_text_file_with_substitutions( &state, "manifest.json", "application/manifest+json", static_cdn_endpoint.as_ref(), + Some(&state.config.manifest_scope_extensions), ) .await } +fn with_scope_extensions(text: String, origins: &[String]) -> String { + if origins.is_empty() { + return text; + } + let Ok(serde_json::Value::Object(mut manifest)) = serde_json::from_str(&text) else { + return text; + }; + let scope_extensions = origins + .iter() + .map(|origin| serde_json::json!({ "type": "origin", "origin": origin })) + .collect(); + manifest.insert( + "scope_extensions".to_owned(), + serde_json::Value::Array(scope_extensions), + ); + serde_json::to_string_pretty(&manifest).unwrap_or(text) +} + +fn substitute_placeholders( + text: &str, + static_cdn_endpoint: &str, + scope_extensions: Option<&[String]>, +) -> String { + let text = text.replace("{{STATIC_CDN_ENDPOINT}}", static_cdn_endpoint); + match scope_extensions { + Some(origins) => with_scope_extensions(text, origins), + None => text, + } +} + pub async fn browserconfig_xml(State(state): State) -> Response { let static_cdn_endpoint = runtime_static_cdn_endpoint(&state).await; serve_static_text_file_with_cdn( @@ -73,6 +104,23 @@ async fn serve_static_text_file_with_cdn( filename: &str, content_type: &str, static_cdn_endpoint: Option<&HttpEndpoint>, +) -> Response { + serve_static_text_file_with_substitutions( + state, + filename, + content_type, + static_cdn_endpoint, + None, + ) + .await +} + +async fn serve_static_text_file_with_substitutions( + state: &AppState, + filename: &str, + content_type: &str, + static_cdn_endpoint: Option<&HttpEndpoint>, + scope_extensions: Option<&[String]>, ) -> Response { let static_dir = state.config.static_dir.as_str(); let file_path = Path::new(static_dir).join(filename); @@ -104,8 +152,7 @@ async fn serve_static_text_file_with_cdn( let replacement = static_cdn_endpoint.map_or("", HttpEndpoint::as_str); let body: axum::body::Body = match std::str::from_utf8(&content) { - Ok(text) => text - .replace("{{STATIC_CDN_ENDPOINT}}", replacement) + Ok(text) => substitute_placeholders(text, replacement, scope_extensions) .into_bytes() .into(), Err(_) => content.into(), @@ -201,6 +248,80 @@ fn is_content_hash(value: &str) -> bool { mod tests { use super::*; + const BUILT_MANIFEST: &str = r#"{ + "id": "/", + "start_url": "/app", + "scope": "/", + "scope_extensions": [], + "icons": [ + { + "src": "{{STATIC_CDN_ENDPOINT}}/web/android-chrome-192x192.png" + } + ] +}"#; + + fn substituted_manifest(origins: &[&str]) -> serde_json::Value { + let origins: Vec = origins.iter().map(|origin| (*origin).to_owned()).collect(); + let text = + substitute_placeholders(BUILT_MANIFEST, "https://fluxerstatic.com", Some(&origins)); + serde_json::from_str(&text).expect("substituted manifest must stay valid JSON") + } + + #[test] + fn manifest_scope_extensions_default_to_the_built_empty_list() { + let manifest = substituted_manifest(&[]); + assert_eq!(manifest["scope_extensions"], serde_json::json!([])); + assert_eq!( + manifest["icons"][0]["src"], + "https://fluxerstatic.com/web/android-chrome-192x192.png" + ); + } + + #[test] + fn manifest_scope_extensions_list_each_configured_origin() { + let manifest = substituted_manifest(&["https://fluxer.com", "https://canary.fluxer.com"]); + assert_eq!( + manifest["scope_extensions"], + serde_json::json!([ + { "type": "origin", "origin": "https://fluxer.com" }, + { "type": "origin", "origin": "https://canary.fluxer.com" } + ]) + ); + assert_eq!(manifest["id"], "/"); + assert_eq!(manifest["start_url"], "/app"); + } + + #[test] + fn manifest_scope_extensions_are_added_when_the_build_has_none() { + let text = substitute_placeholders( + r#"{"id":"/"}"#, + "", + Some(&["https://fluxer.com".to_owned()]), + ); + let manifest: serde_json::Value = serde_json::from_str(&text).expect("valid JSON"); + assert_eq!( + manifest["scope_extensions"], + serde_json::json!([{ "type": "origin", "origin": "https://fluxer.com" }]) + ); + } + + #[test] + fn other_text_files_are_left_alone() { + let origins = ["https://fluxer.com".to_owned()]; + assert_eq!( + substitute_placeholders( + "not json {{STATIC_CDN_ENDPOINT}}", + "https://cdn", + Some(&origins) + ), + "not json https://cdn" + ); + assert_eq!( + substitute_placeholders(BUILT_MANIFEST, "", None), + BUILT_MANIFEST.replace("{{STATIC_CDN_ENDPOINT}}", "") + ); + } + #[test] fn mime_html() { assert_eq!(guess_mime("i.html"), "text/html; charset=utf-8"); diff --git a/fluxer_desktop/src/common/Constants.ts b/fluxer_desktop/src/common/Constants.ts index 87d379b1f..28197e22d 100644 --- a/fluxer_desktop/src/common/Constants.ts +++ b/fluxer_desktop/src/common/Constants.ts @@ -3,6 +3,9 @@ export const APP_PROTOCOL = 'fluxer'; export const STABLE_APP_URL = 'https://web.fluxer.app'; export const CANARY_APP_URL = 'https://web.canary.fluxer.app'; +export const STABLE_MIGRATED_APP_ORIGIN = 'https://fluxer.com'; +export const CANARY_MIGRATED_APP_ORIGIN = 'https://canary.fluxer.com'; +export const MIGRATED_APP_ENTRY_PATH = '/app'; export const STATIC_CDN_URL = 'https://fluxerstatic.com'; export const DEFAULT_WINDOW_WIDTH = 1280; export const DEFAULT_WINDOW_HEIGHT = 800; diff --git a/fluxer_desktop/src/common/DesktopConfig.ts b/fluxer_desktop/src/common/DesktopConfig.ts index efd82e146..b229034f8 100644 --- a/fluxer_desktop/src/common/DesktopConfig.ts +++ b/fluxer_desktop/src/common/DesktopConfig.ts @@ -3,7 +3,13 @@ import fs from 'node:fs'; import path from 'node:path'; import {BUILD_CHANNEL} from '@electron/common/BuildChannel'; -import {CANARY_APP_URL, STABLE_APP_URL} from '@electron/common/Constants'; +import { + CANARY_APP_URL, + CANARY_MIGRATED_APP_ORIGIN, + MIGRATED_APP_ENTRY_PATH, + STABLE_APP_URL, + STABLE_MIGRATED_APP_ORIGIN, +} from '@electron/common/Constants'; import type {DesktopTroubleshootingSettings, DesktopWindowBehaviorSettings} from '@electron/common/Types'; import log from 'electron-log'; @@ -18,6 +24,7 @@ interface DesktopConfig extends Record { window_behavior?: PersistedDesktopWindowBehaviorSettings; troubleshooting?: PersistedDesktopTroubleshootingSettings; theme_allowed_local_files?: Array; + app_origin?: string; } export type ChromiumSwitchesSetting = ReadonlyArray | Record; @@ -133,12 +140,34 @@ function sanitizeChromiumSwitchesSetting(value: unknown): ChromiumSwitchesSettin return undefined; } +function getLegacyAppUrl(): string { + return BUILD_CHANNEL === 'canary' ? CANARY_APP_URL : STABLE_APP_URL; +} + +function getMigratedAppOrigin(): string { + return BUILD_CHANNEL === 'canary' ? CANARY_MIGRATED_APP_ORIGIN : STABLE_MIGRATED_APP_ORIGIN; +} + +export function getOfficialAppOrigins(): Array { + return [new URL(getLegacyAppUrl()).origin, getMigratedAppOrigin()]; +} + +function sanitizeAppOrigin(value: unknown): string | undefined { + return typeof value === 'string' && getOfficialAppOrigins().includes(value) ? value : undefined; +} + function sanitizeDesktopConfig(value: unknown): DesktopConfig { if (!isRecord(value)) { return {}; } const nextConfig: DesktopConfig = {...value}; delete nextConfig.app_url; + const appOrigin = sanitizeAppOrigin(value.app_origin); + if (appOrigin) { + nextConfig.app_origin = appOrigin; + } else { + delete nextConfig.app_origin; + } const chromiumSwitches = sanitizeChromiumSwitchesSetting(value.chromiumSwitches); if (chromiumSwitches) { nextConfig.chromiumSwitches = chromiumSwitches; @@ -311,7 +340,29 @@ export function getAppUrl(): string { if (runtimeAppUrlOverride) { return runtimeAppUrlOverride; } - return BUILD_CHANNEL === 'canary' ? CANARY_APP_URL : STABLE_APP_URL; + const migratedAppOrigin = getMigratedAppOrigin(); + if (config.app_origin === migratedAppOrigin) { + return `${migratedAppOrigin}${MIGRATED_APP_ENTRY_PATH}`; + } + return getLegacyAppUrl(); +} + +export function getAppUrlFallback(url: string): string | null { + try { + return new URL(url).origin === getMigratedAppOrigin() ? getLegacyAppUrl() : null; + } catch { + return null; + } +} + +export function setAppOrigin(origin: string): boolean { + const appOrigin = sanitizeAppOrigin(origin); + if (appOrigin === undefined) { + return false; + } + config.app_origin = appOrigin; + saveDesktopConfig(); + return true; } export function getCustomAppUrl(): string | null { diff --git a/fluxer_desktop/src/common/Types.ts b/fluxer_desktop/src/common/Types.ts index 0fd36c471..56710ed69 100644 --- a/fluxer_desktop/src/common/Types.ts +++ b/fluxer_desktop/src/common/Types.ts @@ -627,6 +627,11 @@ export type TrayActionPayload = action: 'check-for-updates'; }; +export interface DomainMigrationApi { + version: 1; + setAppOrigin: (origin: string) => Promise; +} + export interface ElectronAPI { platform: NodeJS.Platform; buildChannel: 'stable' | 'canary'; @@ -759,6 +764,7 @@ export interface ElectronAPI { passkeyIsSupported: () => Promise; passkeyAuthenticate: (options: PublicKeyCredentialRequestOptionsJSON) => Promise; passkeyRegister: (options: PublicKeyCredentialCreationOptionsJSON) => Promise; + domainMigration: DomainMigrationApi; virtmic: VirtmicApi; nativeAudio: NativeAudioApi; nativeScreenCapture: NativeScreenCaptureApi; diff --git a/fluxer_desktop/src/main/DomainMigration.test.mjs b/fluxer_desktop/src/main/DomainMigration.test.mjs new file mode 100644 index 000000000..ad87e4a2a --- /dev/null +++ b/fluxer_desktop/src/main/DomainMigration.test.mjs @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import {createRequire} from 'node:module'; +import os from 'node:os'; +import path from 'node:path'; +import {describe, test} from 'node:test'; +import {fileURLToPath} from 'node:url'; +import vm from 'node:vm'; + +const require = createRequire(import.meta.url); +const esbuild = require('esbuild'); + +function transform(relativePath) { + const sourcePath = fileURLToPath(new URL(relativePath, import.meta.url)); + const code = esbuild.transformSync(fs.readFileSync(sourcePath, 'utf8'), { + loader: 'ts', + format: 'cjs', + platform: 'node', + target: 'node20', + }).code; + return {sourcePath, code}; +} + +const constantsSource = transform('../common/Constants.ts'); +const desktopConfigSource = transform('../common/DesktopConfig.ts'); +const domainMigrationSource = transform('./DomainMigration.ts'); + +const silentLog = {debug() {}, info() {}, warn() {}, error() {}}; + +function runModule({sourcePath, code}, requireStub) { + const module = {exports: {}}; + const context = vm.createContext({ + require: requireStub, + module, + exports: module.exports, + process, + console, + URL, + JSON, + }); + vm.runInContext(code, context, {filename: sourcePath}); + return module.exports; +} + +function loadDesktop({channel = 'stable', settings} = {}) { + const userDataPath = fs.mkdtempSync(path.join(os.tmpdir(), 'fluxer-domain-migration-test-')); + const settingsPath = path.join(userDataPath, 'settings.json'); + if (settings !== undefined) { + fs.writeFileSync(settingsPath, JSON.stringify(settings), 'utf-8'); + } + const constants = runModule(constantsSource, (specifier) => { + throw new Error(`Unexpected import: ${specifier}`); + }); + const desktopConfig = runModule(desktopConfigSource, (specifier) => { + if (specifier === 'node:fs') return fs; + if (specifier === 'node:path') return path; + if (specifier === '@electron/common/BuildChannel') return {BUILD_CHANNEL: channel}; + if (specifier === '@electron/common/Constants') return constants; + if (specifier === 'electron-log') return silentLog; + throw new Error(`Unexpected import: ${specifier}`); + }); + desktopConfig.loadDesktopConfig(userDataPath); + const handlers = new Map(); + runModule(domainMigrationSource, (specifier) => { + if (specifier === '@electron/common/DesktopConfig') return desktopConfig; + if (specifier === '@electron/common/Logger') return {createChildLogger: () => silentLog}; + if (specifier === 'electron') { + return { + ipcMain: { + handle: (name, handler) => { + handlers.set(name, handler); + }, + }, + }; + } + throw new Error(`Unexpected import: ${specifier}`); + }).registerDomainMigrationHandlers(); + const readSettings = () => JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + const setAppOrigin = (frame, origin) => handlers.get('domain-migration:set-app-origin')({senderFrame: frame}, origin); + return {desktopConfig, readSettings, setAppOrigin, settingsPath}; +} + +function topLevelFrame(url) { + return {url, detached: false, parent: null}; +} + +describe('DesktopConfig app origin', () => { + test('keeps loading the legacy root when no app origin is stored', () => { + assert.equal(loadDesktop().desktopConfig.getAppUrl(), 'https://web.fluxer.app'); + assert.equal(loadDesktop({channel: 'canary'}).desktopConfig.getAppUrl(), 'https://web.canary.fluxer.app'); + }); + + test('loads the app entry path for a stored migrated origin', () => { + const stable = loadDesktop({settings: {app_origin: 'https://fluxer.com'}}); + const canary = loadDesktop({channel: 'canary', settings: {app_origin: 'https://canary.fluxer.com'}}); + + assert.equal(stable.desktopConfig.getAppUrl(), 'https://fluxer.com/app'); + assert.equal(canary.desktopConfig.getAppUrl(), 'https://canary.fluxer.com/app'); + }); + + test('loads the legacy root for a stored legacy origin', () => { + const {desktopConfig} = loadDesktop({settings: {app_origin: 'https://web.fluxer.app'}}); + + assert.equal(desktopConfig.getAppUrl(), 'https://web.fluxer.app'); + }); + + test('drops stored origins outside the channel allowlist', () => { + for (const appOrigin of [ + 'https://canary.fluxer.com', + 'https://fluxer.com/', + 'https://evil.example', + 'http://fluxer.com', + 42, + ]) { + const {desktopConfig} = loadDesktop({settings: {app_origin: appOrigin}}); + assert.equal(desktopConfig.getAppUrl(), 'https://web.fluxer.app'); + } + }); + + test('falls back from the migrated origin to the legacy root', () => { + const stable = loadDesktop({settings: {app_origin: 'https://fluxer.com'}}).desktopConfig; + const canary = loadDesktop({channel: 'canary', settings: {app_origin: 'https://canary.fluxer.com'}}).desktopConfig; + + assert.equal(stable.getAppUrlFallback('https://fluxer.com/app'), 'https://web.fluxer.app'); + assert.equal(stable.getAppUrlFallback('https://fluxer.com/channels/@me'), 'https://web.fluxer.app'); + assert.equal(canary.getAppUrlFallback('https://canary.fluxer.com/app'), 'https://web.canary.fluxer.app'); + assert.equal(stable.getAppUrlFallback('https://web.fluxer.app/channels/@me'), null); + assert.equal(stable.getAppUrlFallback('https://canary.fluxer.com/app'), null); + assert.equal(stable.getAppUrlFallback('not a url'), null); + }); + + test('the runtime override still wins over a stored origin', () => { + const {desktopConfig} = loadDesktop({settings: {app_origin: 'https://fluxer.com'}}); + desktopConfig.setRuntimeAppUrlOverride('http://localhost:8088/'); + + assert.equal(desktopConfig.getAppUrl(), 'http://localhost:8088/'); + }); +}); + +describe('DomainMigration set app origin IPC', () => { + test('persists an allowlisted origin from an official top-level frame', () => { + const {desktopConfig, readSettings, setAppOrigin} = loadDesktop(); + + setAppOrigin(topLevelFrame('https://fluxer.com/migrate/complete'), 'https://fluxer.com'); + + assert.equal(readSettings().app_origin, 'https://fluxer.com'); + assert.equal(desktopConfig.getAppUrl(), 'https://fluxer.com/app'); + }); + + test('accepts the legacy frame of the running channel', () => { + const {readSettings, setAppOrigin} = loadDesktop({channel: 'canary'}); + + setAppOrigin(topLevelFrame('https://web.canary.fluxer.app/channels/@me'), 'https://canary.fluxer.com'); + + assert.equal(readSettings().app_origin, 'https://canary.fluxer.com'); + }); + + test('rejects senders that are not official top-level frames of the channel', () => { + const {setAppOrigin, settingsPath} = loadDesktop(); + const rejectedFrames = [ + null, + topLevelFrame('https://evil.example/'), + topLevelFrame('https://canary.fluxer.com/app'), + {url: 'https://fluxer.com/app', detached: true, parent: null}, + {url: 'https://fluxer.com/app', detached: false, parent: topLevelFrame('https://fluxer.com/app')}, + topLevelFrame('not a url'), + ]; + for (const frame of rejectedFrames) { + assert.throws(() => setAppOrigin(frame, 'https://fluxer.com'), /official app document/); + } + assert.equal(fs.existsSync(settingsPath), false); + }); + + test('rejects origins outside the channel allowlist', () => { + const {setAppOrigin, settingsPath} = loadDesktop(); + for (const origin of ['https://canary.fluxer.com', 'https://fluxer.com/app', 'https://evil.example', null]) { + assert.throws(() => setAppOrigin(topLevelFrame('https://fluxer.com/app'), origin), /outside the allowlist/); + } + assert.equal(fs.existsSync(settingsPath), false); + }); +}); diff --git a/fluxer_desktop/src/main/DomainMigration.ts b/fluxer_desktop/src/main/DomainMigration.ts new file mode 100644 index 000000000..2d7b1de0f --- /dev/null +++ b/fluxer_desktop/src/main/DomainMigration.ts @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {getOfficialAppOrigins, setAppOrigin} from '@electron/common/DesktopConfig'; +import {createChildLogger} from '@electron/common/Logger'; +import {type IpcMainInvokeEvent, ipcMain} from 'electron'; + +const logger = createChildLogger('DomainMigration'); + +const DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL = 'domain-migration:set-app-origin'; + +function getTopLevelSenderOrigin(event: IpcMainInvokeEvent): string | null { + const frame = event.senderFrame; + if (frame == null) { + return null; + } + try { + if (frame.detached || frame.parent != null) { + return null; + } + return new URL(frame.url).origin; + } catch { + return null; + } +} + +function handleSetAppOrigin(event: IpcMainInvokeEvent, origin: unknown): void { + const officialAppOrigins = getOfficialAppOrigins(); + const senderOrigin = getTopLevelSenderOrigin(event); + if (senderOrigin === null || !officialAppOrigins.includes(senderOrigin)) { + throw new Error(`${DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL} is only reachable from an official app document`); + } + if (typeof origin !== 'string' || !officialAppOrigins.includes(origin)) { + throw new Error(`${DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL} received an origin outside the allowlist`); + } + if (!setAppOrigin(origin)) { + throw new Error(`${DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL} could not store the app origin`); + } + logger.info('Stored app origin', {origin, senderOrigin}); +} + +export function registerDomainMigrationHandlers(): void { + ipcMain.handle(DOMAIN_MIGRATION_SET_APP_ORIGIN_CHANNEL, handleSetAppOrigin); +} diff --git a/fluxer_desktop/src/main/IpcHandlers.ts b/fluxer_desktop/src/main/IpcHandlers.ts index 3d7e06ac9..f83ba48f9 100644 --- a/fluxer_desktop/src/main/IpcHandlers.ts +++ b/fluxer_desktop/src/main/IpcHandlers.ts @@ -20,6 +20,7 @@ import { hasActiveDesktopTray, updateTrayRuntimeState, } from '@electron/main/DesktopTray'; +import {registerDomainMigrationHandlers} from '@electron/main/DomainMigration'; import {DownloadChecksumError, downloadFile} from '@electron/main/FileDownloads'; import { type LinuxAppearanceSnapshot, @@ -136,6 +137,7 @@ function getActiveMiddleClickAutoscroll(): boolean { export function registerIpcHandlers(): void { registerVoiceDebugEventSinkPopoutIpcHandlers(); registerVoiceBackgroundMediaCacheHandlers(); + registerDomainMigrationHandlers(); ipcMain.handle('get-desktop-info', () => getDesktopInfo()); ipcMain.handle('get-gpu-info', () => getGpuInfo()); ipcMain.handle('get-app-metrics', () => getAppMetricsSnapshot()); diff --git a/fluxer_desktop/src/main/Window.ts b/fluxer_desktop/src/main/Window.ts index 7ee41e11d..e62d0a38d 100644 --- a/fluxer_desktop/src/main/Window.ts +++ b/fluxer_desktop/src/main/Window.ts @@ -5,13 +5,20 @@ import path from 'node:path'; import {fileURLToPath} from 'node:url'; import { CANARY_APP_URL, + CANARY_MIGRATED_APP_ORIGIN, DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, STABLE_APP_URL, + STABLE_MIGRATED_APP_ORIGIN, } from '@electron/common/Constants'; -import {getAppUrl, getCustomAppUrl, getDesktopWindowBehaviorSettings} from '@electron/common/DesktopConfig'; +import { + getAppUrl, + getAppUrlFallback, + getCustomAppUrl, + getDesktopWindowBehaviorSettings, +} from '@electron/common/DesktopConfig'; import {createChildLogger} from '@electron/common/Logger'; import type {DesktopWindowBehaviorSettings} from '@electron/common/Types'; import { @@ -60,7 +67,7 @@ const CUSTOM_TITLEBAR_TRAFFIC_LIGHT_POSITION = { y: Math.round((CUSTOM_TITLEBAR_HEIGHT_MAC - CUSTOM_TITLEBAR_TRAFFIC_LIGHT_DIAMETER) / 2), }; const trustedWebOrigins = new Set( - [STABLE_APP_URL, CANARY_APP_URL] + [STABLE_APP_URL, CANARY_APP_URL, STABLE_MIGRATED_APP_ORIGIN, CANARY_MIGRATED_APP_ORIGIN] .map((url) => { try { return new URL(url).origin; @@ -726,7 +733,7 @@ export function createWindow(options: CreateWindowOptions = {}): BrowserWindow { const acceptFirstMouseOnFocus = isMac; initialUseNativeTitleBar = useNativeTitleBar; initialAllowTransparency = allowTransparency; - const appUrl = getAppUrl(); + let appUrl = getAppUrl(); const windowOptions: Electron.BrowserWindowConstructorOptions = { width: windowWidth, height: windowHeight, @@ -998,7 +1005,25 @@ export function createWindow(options: CreateWindowOptions = {}): BrowserWindow { }); }, delay); }; + const fallBackFromMigratedAppOrigin = (failedUrl: string, detail: Record): boolean => { + const fallbackUrl = getAppUrlFallback(failedUrl); + if (!mainWindow || mainWindow.isDestroyed() || fallbackUrl === null || appUrl === fallbackUrl) { + return false; + } + logger.warn('Migrated app origin failed to load, falling back to the legacy app URL', {failedUrl, ...detail}); + appUrl = fallbackUrl; + clearAppLoadRetry(); + mainWindow.loadURL(appUrl).catch((error) => { + scheduleAppLoadRetry('legacy-fallback-load-url-rejected', {error}); + }); + return true; + }; webContents.on('did-finish-load', clearAppLoadRetry); + webContents.on('did-navigate', (_event, url, httpResponseCode) => { + if (httpResponseCode >= 400) { + fallBackFromMigratedAppOrigin(url, {httpResponseCode}); + } + }); webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL, isMainFrame) => { if (isMainFrame) { logger.error('App main-frame load failed', {errorCode, errorDescription, validatedURL}); @@ -1006,6 +1031,9 @@ export function createWindow(options: CreateWindowOptions = {}): BrowserWindow { if (!isMainFrame || !isTrustedOrigin(validatedURL) || !shouldRetryAppLoadFailure(errorCode)) { return; } + if (fallBackFromMigratedAppOrigin(validatedURL, {errorCode, errorDescription})) { + return; + } scheduleAppLoadRetry('did-fail-load', {errorCode, errorDescription, validatedURL}); }); const loadAppUrl = (): void => { diff --git a/fluxer_desktop/src/preload/index.ts b/fluxer_desktop/src/preload/index.ts index 2c75ee470..8ea054911 100644 --- a/fluxer_desktop/src/preload/index.ts +++ b/fluxer_desktop/src/preload/index.ts @@ -451,6 +451,10 @@ const api: ElectronAPI = { options: PublicKeyCredentialCreationOptionsJSON, requestContext?: {pin?: string}, ): Promise => ipcRenderer.invoke('passkey-register', options, requestContext), + domainMigration: { + version: 1, + setAppOrigin: (origin: string): Promise => ipcRenderer.invoke('domain-migration:set-app-origin', origin), + }, toggleDevTools: (): void => { ipcRenderer.send('toggle-devtools'); }, diff --git a/fluxer_docs/scripts/VerifyDocsStyle.ts b/fluxer_docs/scripts/VerifyDocsStyle.ts index a581bc4a8..c0e41dfd9 100644 --- a/fluxer_docs/scripts/VerifyDocsStyle.ts +++ b/fluxer_docs/scripts/VerifyDocsStyle.ts @@ -271,7 +271,7 @@ const ACCEPTED_TABLE_FINDINGS = new Map2 | object | `branding`, `setup`, `legal`, and `registration` sub-objects, each merged field by field | @@ -562,6 +593,8 @@ The body has one optional object for each section. Fluxer leaves an absent secti `push_service_delivery` works the same way, over the [push service delivery configuration](#push-service-delivery-configuration-object) fields and its own `config_version`. +`domain_migration` works the same way, over the [domain migration configuration](#domain-migration-configuration-object) fields and its own `config_version`. + `experiment_delivery` takes both [experiment delivery configuration](#experiment-delivery-configuration-object) fields, each bound as documented there. It is a section of its own, so a write to it changes no `config_version` and changes no assignment, only the cadence on which clients ask for one. 3 A secret such as `klipy_api_key`, `api_key`, `hcaptcha_secret_key`, `turnstile_secret_key`, or the SMTP `password` is written when supplied and left alone when absent. `integrations.bluesky.keys` is the only way to write the Bluesky signing keys counted as `bluesky.key_count`. It takes up to 8 entries of `kid` (1-255 characters) and nullable `private_key` (up to 10000 characters), and replaces the stored key set outright @@ -598,7 +631,7 @@ Fluxer skips URL validation while the merged configuration leaves single sign-on | 400 | [error response](/admin-api/#error-response) | A policy transition is refused, returned as `INSTANCE_POLICY_TRANSITION_NOT_ALLOWED` | :::caution[Sections are applied one after another] -The order is `gateway_rollout`, `voice_noise_suppression`, `push_service_delivery`, `experiment_delivery`, `sso`, `registration`, `app_public` branding, legal, and registration fields, `integrations`, `media`, `policy`, and finally `app_public.setup`. A failure part way through leaves the earlier sections written. +The order is `gateway_rollout`, `voice_noise_suppression`, `push_service_delivery`, `domain_migration`, `experiment_delivery`, `sso`, `registration`, `app_public` branding, legal, and registration fields, `integrations`, `media`, `policy`, and finally `app_public.setup`. A failure part way through leaves the earlier sections written. ::: ### Side effects diff --git a/fluxer_docs/src/content/docs/http-api/authentication.mdx b/fluxer_docs/src/content/docs/http-api/authentication.mdx index 2cee7971c..96a382570 100644 --- a/fluxer_docs/src/content/docs/http-api/authentication.mdx +++ b/fluxer_docs/src/content/docs/http-api/authentication.mdx @@ -1,7 +1,7 @@ --- # SPDX-License-Identifier: AGPL-3.0-or-later title: Authentication resource -description: Registration, login, credentials, sessions, recovery, SSO, IP authorisation, and desktop handoff. +description: Registration, login, credentials, sessions, recovery, SSO, IP authorisation, desktop handoff, and origin handoff. --- import RouteHeader from '@/components/RouteHeader.astro'; @@ -16,7 +16,7 @@ The Authentication resource covers signing up, signing in, recovering an account A client MUST treat every session token, one-use token, ticket, SSO state, and handoff code as confidential. It MUST NOT parse one, expose it to untrusted code, or write it to logs, analytics, or a URL the operation did not assign. ::: -Most operations need no `Authorization` credential, because the request has a one-use token, MFA ticket, WebAuthn assertion, or account credential of its own. [List authentication sessions](#list-authentication-sessions), [terminate authentication sessions](#terminate-authentication-sessions), [resend email verification](#resend-email-verification), [log out](#log-out), and [complete desktop handoff](#complete-desktop-handoff) are the only operations on this page that read an `Authorization` credential. Complete desktop handoff reads the header only when its body omits the `token` field. The first three require an ordinary user session, and each rejects a bot token and an OAuth2 bearer token with 403 `ACCESS_DENIED`. All three still admit a session whose account is flagged for suspicious activity. +Most operations need no `Authorization` credential, because the request has a one-use token, MFA ticket, WebAuthn assertion, or account credential of its own. [List authentication sessions](#list-authentication-sessions), [terminate authentication sessions](#terminate-authentication-sessions), [resend email verification](#resend-email-verification), [log out](#log-out), [complete desktop handoff](#complete-desktop-handoff), and [create origin handoff](#create-origin-handoff) are the only operations on this page that read an `Authorization` credential. Complete desktop handoff reads the header only when its body omits the `token` field. The first three and create origin handoff require an ordinary user session, and each rejects a bot token and an OAuth2 bearer token with 403 `ACCESS_DENIED`. All four still admit a session whose account is flagged for suspicious activity. Every route has a route bucket and is also subject to the [global HTTP limit](/topics/rate-limits/). A route bucket is keyed by the authenticated user when the request has a resolvable credential and by the client IP address otherwise. Bucket or global denial returns 429 `RATE_LIMITED`. @@ -301,6 +301,26 @@ The state of one desktop handoff as observed by the device that initiated it. 2 The fields are present together only when the status is `completed` +## Origin handoff object + +The identifier of one stored origin handoff. + +### Structure + +| Field | Type | Description | +| --- | --- | --- | +| handoff_id | string | The single-use identifier the receiving origin redeems, 32 random bytes as 43 base64url characters | + +## Origin handoff payload object + +The client state released by one redeemed origin handoff. + +### Structure + +| Field | Type | Description | +| --- | --- | --- | +| payload | string | The base64url string stored by [create origin handoff](#create-origin-handoff), returned unchanged | + ## Get SSO status @@ -1491,3 +1511,82 @@ Cancelling prevents any further token retrieval but does not revoke a session al ### Rate limit 10 requests per minute, on the `auth:handoff:cancel` bucket. + +## Create origin handoff + + + +Stores encrypted client state so that another web origin of the same instance can take it over once. The official web client uses it to move a signed-in browser from its legacy origin to a new one. Requires a user session token. Returns an [origin handoff](#origin-handoff-object) object. + +The sending origin encrypts the state and keeps the key. Fluxer never receives the key and never reads the payload. The receiving origin holds a nonce, and the sender passes only its digest here. + +### JSON body + +| Field | Type | Description | +| --- | --- | --- | +| nonce_hash | string | The SHA-256 digest of the receiving origin's nonce, as 64 lowercase hex characters | +| payload | string | The encrypted client state as base64url, 1 to 8388608 characters | + +A bot token and an OAuth2 bearer token both return 403 `ACCESS_DENIED`. A session whose account is flagged for suspicious activity is admitted. + +### Response + +| Status | Body | Condition | +| --- | --- | --- | +| 200 | [origin handoff](#origin-handoff-object) object | The state was stored | +| 400 | [error response](/http-api/#error-response) | The body is invalid, returning `INVALID_FORM_BODY` | +| 400 | [error response](/http-api/#error-response) | The body is larger than 8 MiB plus 1 KiB and the request returns `FILE_SIZE_TOO_LARGE` | +| 401 | [error response](/http-api/#error-response) | The user session credential is missing or invalid | +| 403 | [error response](/http-api/#error-response) | The credential is a bot token or an OAuth2 bearer token | +| 429 | [rate limit response](/topics/rate-limits/#rate-limit-response-object) | Route or global bucket denies the request | +| 500 | [error response](/http-api/#error-response) | Unexpected internal failure occurs | +| 503 | [error response](/http-api/#error-response) | The instance is at its in-flight request ceiling | + +### Side effects + +Fluxer keeps the payload, the nonce digest, and the account ID for two minutes under a key derived from the identifier. Creating a handoff creates no session and changes no account state. No Gateway Dispatch is emitted. + +### Rate limit + +3 requests per 10 minutes, on the `auth:origin_handoff:create` bucket. + +## Redeem origin handoff + + + +Releases the client state stored by [create origin handoff](#create-origin-handoff) and deletes it in the same step. Authentication is not required. Returns an [origin handoff payload](#origin-handoff-payload-object) object. + +### JSON body + +| Field | Type | Description | +| --- | --- | --- | +| handoff_id | string | The identifier returned by create origin handoff, 43 base64url characters | +| nonce | string | The nonce whose digest the sender supplied, as base64url, 16 to 256 characters | + +An unknown, expired, or already redeemed identifier returns 404 `UNKNOWN_ORIGIN_HANDOFF`. A nonce whose SHA-256 digest does not match returns 400 `INVALID_ORIGIN_HANDOFF_NONCE`. + +:::caution[Every attempt consumes the handoff] +Fluxer deletes the stored state before it compares the nonce. A wrong nonce leaves nothing to retry, so the sender MUST create a new handoff. +::: + +On an instance that is not self-hosted, the request MUST send an `Origin` header naming one of the instance's web app origins. Any other `Origin`, or none, returns 403 `INVALID_API_ORIGIN`. A self-hosted instance skips this check. + +### Response + +| Status | Body | Condition | +| --- | --- | --- | +| 200 | [origin handoff payload](#origin-handoff-payload-object) object | The state was released and deleted | +| 400 | [error response](/http-api/#error-response) | The body is invalid, or the nonce does not match | +| 403 | [error response](/http-api/#error-response) | The `Origin` is not a web app origin of the instance, returning `INVALID_API_ORIGIN` | +| 404 | [error response](/http-api/#error-response) | No stored handoff has this identifier, returning `UNKNOWN_ORIGIN_HANDOFF` | +| 429 | [rate limit response](/topics/rate-limits/#rate-limit-response-object) | Route or global bucket denies the request | +| 500 | [error response](/http-api/#error-response) | Unexpected internal failure occurs | +| 503 | [error response](/http-api/#error-response) | The instance is at its in-flight request ceiling | + +### Side effects + +A redemption that finds the handoff deletes it, whether or not the nonce matches. Redemption creates no session. The payload is all the receiving origin gets. No Gateway Dispatch is emitted. + +### Rate limit + +10 requests per minute, on the `auth:origin_handoff:redeem` bucket. diff --git a/fluxer_docs/src/content/docs/http-api/errors.md b/fluxer_docs/src/content/docs/http-api/errors.md index 202032532..87fb50dfc 100644 --- a/fluxer_docs/src/content/docs/http-api/errors.md +++ b/fluxer_docs/src/content/docs/http-api/errors.md @@ -540,6 +540,10 @@ Invalid form body Invalid handoff code +### `INVALID_ORIGIN_HANDOFF_NONCE` + +This sign-in transfer doesn't match the one you started + ### `INVALID_PERMISSIONS_INTEGER` Permissions must be a valid integer @@ -1100,6 +1104,10 @@ Member wasn't found in this community Message wasn't found +### `UNKNOWN_ORIGIN_HANDOFF` + +This sign-in transfer has expired or was already used + ### `UNKNOWN_REPORT` Unknown report diff --git a/fluxer_docs/src/content/docs/http-api/experiments.mdx b/fluxer_docs/src/content/docs/http-api/experiments.mdx index 2c40d0c25..01b456074 100644 --- a/fluxer_docs/src/content/docs/http-api/experiments.mdx +++ b/fluxer_docs/src/content/docs/http-api/experiments.mdx @@ -6,7 +6,7 @@ description: The experiment assignments envelope, the revalidation and polling c import RouteHeader from '@/components/RouteHeader.astro'; -An experiment is an instance-wide rollout that an operator configures. For each account, Fluxer works out from that configuration whether the account is in the rollout and which settings the account receives. The single route on this page resolves every experiment the server defines and returns them in one envelope, together with the polling cadence they share. This server defines `voice_noise_suppression`, whose placement protocol [Voice](/voice/) defines. +An experiment is an instance-wide rollout that an operator configures. For each account, Fluxer works out from that configuration whether the account is in the rollout and which settings the account receives. The single route on this page resolves every experiment the server defines and returns them in one envelope, together with the polling cadence they share. This server defines `voice_noise_suppression`, whose placement protocol [Voice](/voice/) defines, and `domain_migration`. Every assignment is advice. A client that ignores one behaves as it does with the rollout off, and no route and no Gateway event reports what a client actually ran. @@ -33,6 +33,7 @@ One entry per experiment. The envelope reports this object even when it is empty | Field | Type | Description | | --- | --- | --- | | voice_noise_suppression? | [noise suppression assignment](#noise-suppression-assignment-object) object | The caller's noise suppression assignment | +| domain_migration? | [domain migration assignment](#domain-migration-assignment-object) object | The caller's web domain migration assignment | Ignore unknown experiments and treat a missing experiment as off. @@ -101,6 +102,20 @@ One backend replacement scoped to one guild. While the caller is connected to a An override naming a backend that is absent from `enabled_backends` is dropped before the response is written, so every entry is runnable. +## Domain migration assignment object + +One resolution of the instance web domain migration rollout against one account. This server version writes the key on every response. + +### Structure + +| Field | Type | Description | +| --- | --- | --- | +| enabled | boolean | Whether the caller's web client moves to the new web origin | + +A caller is drawn either by the operator's allowlist or by the sampled share of the account population. `enabled` is false in every other case, the rollout being off included. + +Only the official web client acts on this assignment, and only on its legacy origins. Every other client ignores it. The logged-out share and the instance-wide switch are published in the [instance discovery document](/http-api/instance/#domain-migration-object) instead, because a client that holds no credential cannot read this route. + ## Get experiment assignments diff --git a/fluxer_docs/src/content/docs/http-api/instance.mdx b/fluxer_docs/src/content/docs/http-api/instance.mdx index 8b4257d1f..65b1628c0 100644 --- a/fluxer_docs/src/content/docs/http-api/instance.mdx +++ b/fluxer_docs/src/content/docs/http-api/instance.mdx @@ -16,7 +16,7 @@ Each of the routes answers with `Access-Control-Allow-Origin: *`, replaced by th ## Instance discovery object -The discovery document is the complete public description of one deployment. Every field is always present. +The discovery document is the complete public description of one deployment. Every field is always present. The one optional field, `domain_migration`, is absent only from a server older than this version. ### Structure @@ -34,6 +34,7 @@ The discovery document is the complete public description of one deployment. Eve | limits | [limit configuration](#limit-configuration-object) object | Ordered limit rules and the trait names the deployment declares | | push | [push configuration](#push-configuration-object) object | Public Web Push identity | | app_public | [public application configuration](#public-application-configuration-object) object | Public client configuration | +| domain_migration? | [domain migration](#domain-migration-object) object | Web domain migration switch and logged-out rollout | 1 The value is a plain integer that increases when the API introduces a change a client is expected to notice, and the current value is `1` @@ -424,6 +425,25 @@ Which extra fields public registration collects. | --- | --- | --- | | collect_date_of_birth | boolean | Whether public registration collects and validates a date of birth | +## Domain migration object + +The public part of the instance [domain migration configuration](/admin-api/instance/#domain-migration-configuration-object). The official web client reads it before sign-in to decide whether its legacy origin forwards to the new one. Every instance publishes it, and only the official web client acts on it. + +### Structure + +| Field | Type | Description | +| --- | --- | --- | +| enabled | boolean | Whether the migration runs at all | +| anonymous_rollout_basis_points | integer | Share of logged-out devices moved to the new origin, in basis points (0-10000) | +| rollout_salt | string | Salt of the sampling hash, shared by accounts and devices | +| standalone_forwarding | boolean | Whether installed desktop web apps forward to the new origin once their data has moved | + +`enabled` is the instance-wide switch. While it is false, no client starts a migration and a client that already migrated stops forwarding from the legacy origin. + +A logged-out device draws its own random ID once and keeps it. The device moves when its bucket over that ID and `rollout_salt` falls below `anonymous_rollout_basis_points`. Signed-in accounts use the [domain migration assignment](/http-api/experiments/#domain-migration-assignment-object) instead. + +While `standalone_forwarding` is false, an installed desktop web app moves its data and stays on the legacy origin. Browser tabs forward either way. + ## Geolocation object The approximate location Fluxer resolved for the request, together with the regions where mature content is gated behind an age check or is unavailable. diff --git a/fluxer_docs/src/content/docs/http-api/webhooks.mdx b/fluxer_docs/src/content/docs/http-api/webhooks.mdx index 12446ad11..16bf602e1 100644 --- a/fluxer_docs/src/content/docs/http-api/webhooks.mdx +++ b/fluxer_docs/src/content/docs/http-api/webhooks.mdx @@ -32,7 +32,7 @@ The [global HTTP limit](/topics/rate-limits/) applies to every management route. ## Origin refusal -Fluxer refuses a call from the official web client on [Execute webhook](#execute-webhook), [Get webhook message](#get-webhook-message), [Edit webhook message](#edit-webhook-message), and [Delete webhook message](#delete-webhook-message). A request whose `Origin` header is exactly `https://web.fluxer.app` or `https://web.canary.fluxer.app` returns 403 `INVALID_API_ORIGIN`. +Fluxer refuses a call from a first-party web client on [Execute webhook](#execute-webhook), [Get webhook message](#get-webhook-message), [Edit webhook message](#edit-webhook-message), and [Delete webhook message](#delete-webhook-message). A request whose `Origin` header is exactly `https://web.fluxer.app`, `https://web.canary.fluxer.app`, or one of the instance's own web app origins returns 403 `INVALID_API_ORIGIN`. Those are the origin of `FLUXER_APP_ENDPOINT` and every `FLUXER_APP_ORIGIN_ALIASES` entry. A request that sends no `Origin`, or any other `Origin` value, passes the check. It runs after the route rate limit and before path validation. @@ -1135,7 +1135,7 @@ The operation removes the webhook, frees its guild and channel webhook slot, and Creates a webhook-authored [message](/http-api/messages/#message-object) in the webhook's channel. When `wait` is true the created message is returned, and otherwise the operation returns 204 with an empty body. -The route refuses a call from the official web client. See [origin refusal](#origin-refusal). A successful execution emits a [Message Create](/gateway/events/#message-create) Gateway Dispatch whether or not wait is true. +The route refuses a call from a first-party web client. See [origin refusal](#origin-refusal). A successful execution emits a [Message Create](/gateway/events/#message-create) Gateway Dispatch whether or not wait is true. ### Path parameters @@ -1187,7 +1187,7 @@ An attachment metadata entry whose `id` matches a supplied file index supplies t | 4001 | [error response](/http-api/#error-response) | The resolved payload has no content, embed, or attachment | | 4001 | [error response](/http-api/#error-response) | The content exceeds the effective maximum length | | 4001 | [error response](/http-api/#error-response) | The embed or attachment count exceeds its effective ceiling | -| 403 | [error response](/http-api/#error-response) | Request has an official web client `Origin`, returning `INVALID_API_ORIGIN` | +| 403 | [error response](/http-api/#error-response) | Request has a first-party web client `Origin`, returning `INVALID_API_ORIGIN` | | 403 | [error response](/http-api/#error-response) | The resolved content or embed text is blocked, returning `CONTENT_BLOCKED` | | 404 | [error response](/http-api/#error-response) | Webhook and token pair does not exist, returning `UNKNOWN_WEBHOOK` | | 404 | [error response](/http-api/#error-response) | The target channel is missing or is not a guild text or voice channel, returning `UNKNOWN_CHANNEL` | @@ -1223,7 +1223,7 @@ The operation updates channel state and search results and emits [Message Create Returns a [message](/http-api/messages/#message-object) that the webhook authored. -The route refuses a call from the official web client. See [origin refusal](#origin-refusal). +The route refuses a call from a first-party web client. See [origin refusal](#origin-refusal). ### Path parameters @@ -1240,7 +1240,7 @@ The route refuses a call from the official web client. See [origin refusal](#ori | Status | Body | Condition | | --- | --- | --- | | 200 | [message](/http-api/messages/#message-object) object | Webhook message was returned | -| 403 | [error response](/http-api/#error-response) | Request has an official web client `Origin`, returning `INVALID_API_ORIGIN` | +| 403 | [error response](/http-api/#error-response) | Request has a first-party web client `Origin`, returning `INVALID_API_ORIGIN` | | 403 | [error response](/http-api/#error-response) | The message was authored by another webhook, a user, or a bot, returning `MISSING_PERMISSIONS` | | 404 | [error response](/http-api/#error-response) | Webhook and token pair does not exist, returning `UNKNOWN_WEBHOOK` | | 404 | [error response](/http-api/#error-response) | The webhook has no channel target, returning `UNKNOWN_CHANNEL` | @@ -1256,7 +1256,7 @@ The route refuses a call from the official web client. See [origin refusal](#ori Updates a message that the webhook authored and returns the modified [message](/http-api/messages/#message-object). -The route refuses a call from the official web client. See [origin refusal](#origin-refusal). A successful edit emits a [Message Update](/gateway/events/#message-update). +The route refuses a call from a first-party web client. See [origin refusal](#origin-refusal). A successful edit emits a [Message Update](/gateway/events/#message-update). ### Path parameters @@ -1284,7 +1284,7 @@ Any other type returns 400 `CANNOT_MODIFY_SYSTEM_WEBHOOK`, and a forward, which | 400 | [error response](/http-api/#error-response) | The content or embed count exceeds its effective ceiling | | 400 | [error response](/http-api/#error-response) | The target message is not an editable type | | 400 | [error response](/http-api/#error-response) | The webhook's stored channel no longer resolves to a guild channel | -| 403 | [error response](/http-api/#error-response) | Request has an official web client `Origin`, returning `INVALID_API_ORIGIN` | +| 403 | [error response](/http-api/#error-response) | Request has a first-party web client `Origin`, returning `INVALID_API_ORIGIN` | | 403 | [error response](/http-api/#error-response) | The message was authored by another webhook, a user, or a bot, returning `MISSING_PERMISSIONS` | | 403 | [error response](/http-api/#error-response) | The replacement content or embed text is blocked, returning `CONTENT_BLOCKED` | | 404 | [error response](/http-api/#error-response) | Webhook and token pair does not exist, returning `UNKNOWN_WEBHOOK` | @@ -1314,7 +1314,7 @@ The operation replaces the supplied fields, and a content change marks the messa Deletes a message that the webhook authored and returns 204 with an empty body on success. -The route refuses a call from the official web client. See [origin refusal](#origin-refusal). Deletion emits a [Message Delete](/gateway/events/#message-delete). +The route refuses a call from a first-party web client. See [origin refusal](#origin-refusal). Deletion emits a [Message Delete](/gateway/events/#message-delete). ### Path parameters @@ -1331,7 +1331,7 @@ The route refuses a call from the official web client. See [origin refusal](#ori | 204 | empty | Webhook message was deleted | | 400 | [error response](/http-api/#error-response) | Path parameters are invalid | | 400 | [error response](/http-api/#error-response) | The webhook's stored channel no longer resolves to a guild channel, returning `CANNOT_EXECUTE_ON_DM` | -| 403 | [error response](/http-api/#error-response) | Request has an official web client `Origin`, returning `INVALID_API_ORIGIN` | +| 403 | [error response](/http-api/#error-response) | Request has a first-party web client `Origin`, returning `INVALID_API_ORIGIN` | | 403 | [error response](/http-api/#error-response) | The message was authored by another webhook, a user, or a bot, returning `MISSING_PERMISSIONS` | | 404 | [error response](/http-api/#error-response) | Webhook and token pair does not exist, returning `UNKNOWN_WEBHOOK` | | 404 | [error response](/http-api/#error-response) | The message does not exist in the webhook's channel, returning `UNKNOWN_MESSAGE` | diff --git a/fluxer_docs/src/content/docs/operator/configuration.mdx b/fluxer_docs/src/content/docs/operator/configuration.mdx index 35140c40e..39ddadc05 100644 --- a/fluxer_docs/src/content/docs/operator/configuration.mdx +++ b/fluxer_docs/src/content/docs/operator/configuration.mdx @@ -199,6 +199,10 @@ Defaults to the derived endpoint. The API base handed to clients. Setting only o Defaults to the derived endpoint. The web app origin. Also one of the allowed CORS origins. Serving the client from another hostname requires setting this. +#### `FLUXER_APP_ORIGIN_ALIASES` + +Default empty. Further web app origins, comma separated. Each entry must be an HTTP or HTTPS origin, or the API refuses to start. The API treats each one like the `FLUXER_APP_ENDPOINT` origin. It allows it through CORS, reads invite links on its host, skips unfurling client routes on its host, and refuses webhook token calls from it. Set it when one instance serves the web app on more than one hostname. + #### `FLUXER_GATEWAY_ENDPOINT` Defaults to the derived endpoint. The public Gateway WebSocket URL. Also the source of the internal Gateway URL when `FLUXER_INTERNAL_GATEWAY_ENDPOINT` is unset, with `ws` rewritten to `http`. @@ -1597,6 +1601,14 @@ Default `/api`. The API path put in the page bootstrap. No default. The absolute API URL in the bootstrap. Compose builds it from the public origin. +#### `FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS` + +Default empty. Hostnames, comma separated, without a scheme, port or path. When a request's `Host` is in the list, the page bootstrap points the web app and its API at that host, as `https://` and `https:///api`. Every other request gets the discovery values unchanged. Set it when the web app is served on more than one hostname and each hostname routes `/api` to the API. Invalid entries are logged and dropped. + +#### `FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS` + +Default empty. HTTPS origins, comma separated, without a path, query or credentials. `app-proxy` writes each one into the `scope_extensions` list of the web app manifest, so an installed web app keeps other origins inside its window. Empty leaves the list empty. Each listed origin must serve `/.well-known/web-app-origin-association` naming this web app, or browsers ignore the entry. Invalid entries are logged and dropped. + ## Admin settings `admin` serves the dashboard at `/admin`. All are optional. @@ -2049,7 +2061,7 @@ The edge is the only HTTP entry point, and every route is served from the one pu None of the variables on this page change that routing. The `Caddyfile` is a bind mount, so an edit to it takes `docker compose restart edge`. -CORS origins are exactly the app and marketing endpoints. Serving the client from a hostname other than `FLUXER_DOMAIN` requires overriding `FLUXER_APP_ENDPOINT`. +CORS origins are exactly the app endpoint, the `FLUXER_APP_ORIGIN_ALIASES` origins, and the marketing endpoint. Serving the client from a hostname other than `FLUXER_DOMAIN` requires overriding `FLUXER_APP_ENDPOINT`. ## Volumes and buckets diff --git a/packages/config/src/ConfigLoader.ts b/packages/config/src/ConfigLoader.ts index 7577b50f3..1782a1124 100644 --- a/packages/config/src/ConfigLoader.ts +++ b/packages/config/src/ConfigLoader.ts @@ -19,6 +19,8 @@ const DEFAULT_PASSKEY_ORIGINS = [ 'https://fluxer.app', 'https://web.fluxer.app', 'https://web.canary.fluxer.app', + 'https://fluxer.com', + 'https://canary.fluxer.com', 'android:apk-key-hash:keSY4bimyLqZQV7bKXgpa2xYuqXi0qZJzsYtp6gpx7w', ]; @@ -106,6 +108,7 @@ function defaultConfig(): MasterConfig { presigned_attachment_uploads_enabled: false, presigned_harvest_downloads_enabled: true, unfurl_ignored_hosts: [], + app_origin_aliases: [], embeds: { oembed_html_enabled: false, oembed_html_allow_untrusted_on_self_hosted: false, @@ -491,6 +494,21 @@ function validateStorageChangeFeedConfig(config: MasterConfig): void { } } +function normalizeAppOriginAliases(config: MasterConfig): void { + const api = config.services.api; + api.app_origin_aliases = [ + ...new Set( + api.app_origin_aliases.map((alias, index) => { + const origin = parseWebOrigin(alias); + if (!origin) { + throw new Error(`FLUXER_APP_ORIGIN_ALIASES entry ${index + 1} must be an HTTP(S) origin`); + } + return origin.origin; + }), + ), + ]; +} + function validateCachePurgeConfig(config: MasterConfig): void { const cachePurge = config.integrations.cache_purge; if (cachePurge.adapter !== 'http') { @@ -582,6 +600,7 @@ function normalizeConfig(config: MasterConfig): MasterConfig { validateApiWorkerConfig(config); validateStorageChangeFeedConfig(config); validateCachePurgeConfig(config); + normalizeAppOriginAliases(config); assertIntegerInRange(config.services.api.max_inflight_requests, 'FLUXER_API_MAX_INFLIGHT_REQUESTS', 1, 100_000); assertIntegerInRange(config.services.api.headers_timeout_ms, 'FLUXER_API_HEADERS_TIMEOUT_MS', 1_000, 3_600_000); assertIntegerInRange(config.services.api.request_timeout_ms, 'FLUXER_API_REQUEST_TIMEOUT_MS', 1_000, 3_600_000); diff --git a/packages/config/src/MasterConfig.ts b/packages/config/src/MasterConfig.ts index 7a007e56a..62e8fe97d 100644 --- a/packages/config/src/MasterConfig.ts +++ b/packages/config/src/MasterConfig.ts @@ -94,6 +94,7 @@ export interface MasterConfig { presigned_attachment_uploads_enabled: boolean; presigned_harvest_downloads_enabled: boolean; unfurl_ignored_hosts: Array; + app_origin_aliases: Array; embeds: { oembed_html_enabled: boolean; oembed_html_allow_untrusted_on_self_hosted: boolean; diff --git a/packages/config/src/__tests__/ConfigLoader.test.ts b/packages/config/src/__tests__/ConfigLoader.test.ts index ccfea3b59..36c1ce63e 100644 --- a/packages/config/src/__tests__/ConfigLoader.test.ts +++ b/packages/config/src/__tests__/ConfigLoader.test.ts @@ -209,11 +209,40 @@ describe('ConfigLoader', () => { 'https://fluxer.app', 'https://web.fluxer.app', 'https://web.canary.fluxer.app', + 'https://fluxer.com', + 'https://canary.fluxer.com', 'android:apk-key-hash:keSY4bimyLqZQV7bKXgpa2xYuqXi0qZJzsYtp6gpx7w', 'http://localhost:8088', ]); }); + test('defaults to no app origin aliases', async () => { + stubMinimalEnv(); + const config = await loadConfig(); + expect(config.services.api.app_origin_aliases).toEqual([]); + }); + + test('normalizes and deduplicates app origin aliases', async () => { + stubMinimalEnv({ + FLUXER_APP_ORIGIN_ALIASES: + 'https://Web.Fluxer.App/, https://fluxer.com,https://fluxer.com:443,http://localhost:3000', + }); + const config = await loadConfig(); + expect(config.services.api.app_origin_aliases).toEqual([ + 'https://web.fluxer.app', + 'https://fluxer.com', + 'http://localhost:3000', + ]); + }); + + test.each(['fluxer.com', 'https://fluxer.com/app', 'ftp://fluxer.com', 'https://user@fluxer.com'])( + 'rejects the app origin alias %s', + async (alias) => { + stubMinimalEnv({FLUXER_APP_ORIGIN_ALIASES: alias}); + await expect(loadConfig()).rejects.toThrow('FLUXER_APP_ORIGIN_ALIASES entry 1 must be an HTTP(S) origin'); + }, + ); + test('rejects an empty client API endpoint override', async () => { stubMinimalEnv({FLUXER_API_CLIENT_ENDPOINT: ''}); await expect(loadConfig()).rejects.toThrow('FLUXER_API_CLIENT_ENDPOINT is required'); diff --git a/packages/config/src/config_loader/EnvironmentOverrides.ts b/packages/config/src/config_loader/EnvironmentOverrides.ts index 94f15a651..3e351bc86 100644 --- a/packages/config/src/config_loader/EnvironmentOverrides.ts +++ b/packages/config/src/config_loader/EnvironmentOverrides.ts @@ -107,6 +107,7 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record = { parse: parseCsv, }, FLUXER_API_UNFURL_IGNORED_HOSTS: {path: ['services', 'api', 'unfurl_ignored_hosts'], parse: parseCsv}, + FLUXER_APP_ORIGIN_ALIASES: {path: ['services', 'api', 'app_origin_aliases'], parse: parseCsv}, FLUXER_API_EMBEDS_OEMBED_HTML_ENABLED: { path: ['services', 'api', 'embeds', 'oembed_html_enabled'], parse: parseBoolean, diff --git a/packages/constants/src/ApiErrorCodes.ts b/packages/constants/src/ApiErrorCodes.ts index d88a948af..6b4a39742 100644 --- a/packages/constants/src/ApiErrorCodes.ts +++ b/packages/constants/src/ApiErrorCodes.ts @@ -105,6 +105,7 @@ export const APIErrorCodes = { INVALID_FLAGS_FORMAT: 'INVALID_FLAGS_FORMAT', INVALID_FORM_BODY: 'INVALID_FORM_BODY', INVALID_HANDOFF_CODE: 'INVALID_HANDOFF_CODE', + INVALID_ORIGIN_HANDOFF_NONCE: 'INVALID_ORIGIN_HANDOFF_NONCE', INVALID_PERMISSIONS_INTEGER: 'INVALID_PERMISSIONS_INTEGER', INVALID_PERMISSIONS_NEGATIVE: 'INVALID_PERMISSIONS_NEGATIVE', INVALID_PHONE_NUMBER: 'INVALID_PHONE_NUMBER', @@ -244,6 +245,7 @@ export const APIErrorCodes = { UNKNOWN_INVITE: 'UNKNOWN_INVITE', UNKNOWN_MEMBER: 'UNKNOWN_MEMBER', UNKNOWN_MESSAGE: 'UNKNOWN_MESSAGE', + UNKNOWN_ORIGIN_HANDOFF: 'UNKNOWN_ORIGIN_HANDOFF', UNKNOWN_REPORT: 'UNKNOWN_REPORT', UNKNOWN_ROLE: 'UNKNOWN_ROLE', UNKNOWN_STICKER: 'UNKNOWN_STICKER', diff --git a/packages/errors/src/domains/auth/InvalidOriginHandoffNonceError.ts b/packages/errors/src/domains/auth/InvalidOriginHandoffNonceError.ts new file mode 100644 index 000000000..4dea24dc7 --- /dev/null +++ b/packages/errors/src/domains/auth/InvalidOriginHandoffNonceError.ts @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes'; +import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError'; + +export class InvalidOriginHandoffNonceError extends BadRequestError { + constructor() { + super({code: APIErrorCodes.INVALID_ORIGIN_HANDOFF_NONCE}); + } +} diff --git a/packages/errors/src/domains/auth/UnknownOriginHandoffError.ts b/packages/errors/src/domains/auth/UnknownOriginHandoffError.ts new file mode 100644 index 000000000..8decd1d08 --- /dev/null +++ b/packages/errors/src/domains/auth/UnknownOriginHandoffError.ts @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes'; +import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError'; + +export class UnknownOriginHandoffError extends NotFoundError { + constructor() { + super({code: APIErrorCodes.UNKNOWN_ORIGIN_HANDOFF}); + } +} diff --git a/packages/errors/src/i18n/ErrorCodeMappings.ts b/packages/errors/src/i18n/ErrorCodeMappings.ts index 75514995b..a6be3d893 100644 --- a/packages/errors/src/i18n/ErrorCodeMappings.ts +++ b/packages/errors/src/i18n/ErrorCodeMappings.ts @@ -132,6 +132,7 @@ export const ErrorCodeToI18nKey = { [ValidationErrorCodes.INVALID_OR_EXPIRED_AUTHORIZATION_TICKET]: 'auth_and_oauth.invalid_or_expired_authorization_ticket', [ValidationErrorCodes.INVALID_OR_EXPIRED_SSO_STATE]: 'auth_and_oauth.invalid_or_expired_sso_state', + [APIErrorCodes.INVALID_ORIGIN_HANDOFF_NONCE]: 'auth_and_oauth.origin_handoff_nonce_invalid', [APIErrorCodes.INVALID_PERMISSIONS_INTEGER]: 'misc.invalid_permissions_integer', [APIErrorCodes.INVALID_PERMISSIONS_NEGATIVE]: 'misc.invalid_permissions_negative', [APIErrorCodes.INVALID_PHONE_NUMBER]: 'phone.invalid_number', @@ -295,6 +296,7 @@ export const ErrorCodeToI18nKey = { [APIErrorCodes.UNKNOWN_INVITE]: 'unknown_entities.unknown_invite', [APIErrorCodes.UNKNOWN_MEMBER]: 'unknown_entities.unknown_member', [APIErrorCodes.UNKNOWN_MESSAGE]: 'unknown_entities.unknown_message', + [APIErrorCodes.UNKNOWN_ORIGIN_HANDOFF]: 'auth_and_oauth.unknown_origin_handoff', [APIErrorCodes.UNKNOWN_REPORT]: 'moderation_and_reports.unknown_report', [APIErrorCodes.UNKNOWN_ROLE]: 'unknown_entities.unknown_role', [APIErrorCodes.UNKNOWN_STICKER]: 'stickers_and_emojis.unknown_sticker', diff --git a/packages/errors/src/i18n/ErrorI18nMessages.ts b/packages/errors/src/i18n/ErrorI18nMessages.ts index 2db975a77..25e90ac79 100644 --- a/packages/errors/src/i18n/ErrorI18nMessages.ts +++ b/packages/errors/src/i18n/ErrorI18nMessages.ts @@ -118,6 +118,7 @@ export const ERROR_I18N_MESSAGES = { 'auth_and_oauth.invalid_token_admin_api_key': 'Invalid admin API key.', 'auth_and_oauth.missing_authorization': 'Missing or invalid authorization header.', 'auth_and_oauth.must_start_session_before_sending_messages': 'You must start a session before sending messages.', + 'auth_and_oauth.origin_handoff_nonce_invalid': "This sign-in transfer doesn't match the one you started.", 'auth_and_oauth.registration_closed': 'Registration is closed on this instance.', 'auth_and_oauth.registration_pending_approval': 'This registration is waiting for admin approval.', 'auth_and_oauth.registration_rejected': 'This registration request was rejected.', @@ -132,6 +133,7 @@ export const ERROR_I18N_MESSAGES = { "We couldn't allocate a discriminator for your SSO account. Please try again.", 'auth_and_oauth.sudo_mode_required': 'Sudo mode is required.', 'auth_and_oauth.unknown_application': 'Unknown application.', + 'auth_and_oauth.unknown_origin_handoff': 'This sign-in transfer has expired or was already used.', 'auth_and_registration.password_is_too_common': 'Your password is too common. Please choose a less predictable password.', 'auth_and_registration.password_not_set': "Password isn't set.", diff --git a/packages/errors/src/i18n/locales/ar.ts b/packages/errors/src/i18n/locales/ar.ts index a7a0f5749..45633ff98 100644 --- a/packages/errors/src/i18n/locales/ar.ts +++ b/packages/errors/src/i18n/locales/ar.ts @@ -101,6 +101,7 @@ const ERROR_I18N_AR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "مفتاح API الإداري غير صالح.", "auth_and_oauth.missing_authorization": "رأس التفويض مفقود أو غير صالح.", "auth_and_oauth.must_start_session_before_sending_messages": "يجب عليك بدء جلسة قبل إرسال الرسائل.", + "auth_and_oauth.origin_handoff_nonce_invalid": "عملية نقل تسجيل الدخول هذه لا تطابق العملية التي بدأتها.", "auth_and_oauth.registration_closed": "التسجيل مغلق في هذا المثيل.", "auth_and_oauth.registration_pending_approval": "هذا التسجيل في انتظار موافقة المسؤول.", "auth_and_oauth.registration_rejected": "تم رفض طلب التسجيل هذا.", @@ -113,6 +114,7 @@ const ERROR_I18N_AR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "لم نتمكن من تخصيص مميّز لحساب SSO الخاص بك. حاول مجددًا.", "auth_and_oauth.sudo_mode_required": "وضع Sudo مطلوب.", "auth_and_oauth.unknown_application": "تطبيق غير معروف.", + "auth_and_oauth.unknown_origin_handoff": "انتهت صلاحية عملية نقل تسجيل الدخول هذه أو تم استخدامها بالفعل.", "auth_and_registration.password_is_too_common": "كلمة مرورك شائعة جدًا. اختر كلمة مرور يصعب تخمينها.", "auth_and_registration.password_not_set": "لم تُعيّن كلمة المرور.", "bots.bot_discriminator_cannot_be_changed": "لا يمكن تغيير مميّز البوت.", diff --git a/packages/errors/src/i18n/locales/bg.ts b/packages/errors/src/i18n/locales/bg.ts index e4888104c..1697a6562 100644 --- a/packages/errors/src/i18n/locales/bg.ts +++ b/packages/errors/src/i18n/locales/bg.ts @@ -101,6 +101,7 @@ const ERROR_I18N_BG_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Невалиден админ API ключ.", "auth_and_oauth.missing_authorization": "Липсваща или невалидна заглавка за оторизация.", "auth_and_oauth.must_start_session_before_sending_messages": "Трябва да започнеш сесия, преди да изпращаш съобщения.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Това прехвърляне на вписването не съвпада с това, което сте започнали.", "auth_and_oauth.registration_closed": "Регистрацията е затворена на тази инстанция.", "auth_and_oauth.registration_pending_approval": "Тази регистрация чака одобрение от администратор.", "auth_and_oauth.registration_rejected": "Тази заявка за регистрация беше отхвърлена.", @@ -113,6 +114,7 @@ const ERROR_I18N_BG_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Не успяхме да присвоим дискриминатор за твоя SSO акаунт. Опитай отново.", "auth_and_oauth.sudo_mode_required": "Изисква се sudo режим.", "auth_and_oauth.unknown_application": "Неизвестно приложение.", + "auth_and_oauth.unknown_origin_handoff": "Това прехвърляне на вписването е изтекло или вече е използвано.", "auth_and_registration.password_is_too_common": "Паролата ти е твърде често срещана. Избери по-трудна за отгатване парола.", "auth_and_registration.password_not_set": "Паролата не е зададена.", "bots.bot_discriminator_cannot_be_changed": "Дискриминаторът на бота не може да се променя.", diff --git a/packages/errors/src/i18n/locales/cs.ts b/packages/errors/src/i18n/locales/cs.ts index 9feb4bed3..e0c836eed 100644 --- a/packages/errors/src/i18n/locales/cs.ts +++ b/packages/errors/src/i18n/locales/cs.ts @@ -101,6 +101,7 @@ const ERROR_I18N_CS_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Neplatný API klíč administrátora.", "auth_and_oauth.missing_authorization": "Chybí nebo je neplatná autorizační hlavička.", "auth_and_oauth.must_start_session_before_sending_messages": "Před odesíláním zpráv musíte zahájit relaci.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Tento přenos přihlášení neodpovídá tomu, který jste zahájili.", "auth_and_oauth.registration_closed": "Registrace na této instanci je uzavřena.", "auth_and_oauth.registration_pending_approval": "Tato registrace čeká na schválení administrátorem.", "auth_and_oauth.registration_rejected": "Tato žádost o registraci byla zamítnuta.", @@ -113,6 +114,7 @@ const ERROR_I18N_CS_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nepodařilo se přidělit čtyřmístný identifikátor pro váš účet SSO. Zkuste to znovu.", "auth_and_oauth.sudo_mode_required": "Je vyžadován režim sudo.", "auth_and_oauth.unknown_application": "Neznámá aplikace.", + "auth_and_oauth.unknown_origin_handoff": "Platnost tohoto přenosu přihlášení vypršela nebo už byl použit.", "auth_and_registration.password_is_too_common": "Vaše heslo je příliš běžné. Zvolte si hůře odhadnutelné heslo.", "auth_and_registration.password_not_set": "Heslo není nastavené.", "bots.bot_discriminator_cannot_be_changed": "Čtyřmístný identifikátor bota nelze změnit.", diff --git a/packages/errors/src/i18n/locales/da.ts b/packages/errors/src/i18n/locales/da.ts index 96526b7d8..b81991254 100644 --- a/packages/errors/src/i18n/locales/da.ts +++ b/packages/errors/src/i18n/locales/da.ts @@ -101,6 +101,7 @@ const ERROR_I18N_DA_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Ugyldig administrator-API-nøgle.", "auth_and_oauth.missing_authorization": "Manglende eller ugyldig godkendelsesheader.", "auth_and_oauth.must_start_session_before_sending_messages": "Du skal starte en session, før du sender beskeder.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Denne overførsel af login matcher ikke den, du startede.", "auth_and_oauth.registration_closed": "Tilmelding er lukket på denne instans.", "auth_and_oauth.registration_pending_approval": "Denne tilmelding afventer godkendelse fra administratoren.", "auth_and_oauth.registration_rejected": "Denne anmodning om tilmelding blev afvist.", @@ -113,6 +114,7 @@ const ERROR_I18N_DA_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Vi kunne ikke tildele et tag til din SSO-konto. Prøv igen.", "auth_and_oauth.sudo_mode_required": "Sudo-tilstand er påkrævet.", "auth_and_oauth.unknown_application": "Ukendt applikation.", + "auth_and_oauth.unknown_origin_handoff": "Denne overførsel af login er udløbet eller allerede brugt.", "auth_and_registration.password_is_too_common": "Din adgangskode er for almindelig. Vælg en adgangskode, der er sværere at gætte.", "auth_and_registration.password_not_set": "Der er ikke angivet nogen adgangskode.", "bots.bot_discriminator_cannot_be_changed": "En bots tag kan ikke ændres.", diff --git a/packages/errors/src/i18n/locales/de.ts b/packages/errors/src/i18n/locales/de.ts index 21c46c00a..a725daca0 100644 --- a/packages/errors/src/i18n/locales/de.ts +++ b/packages/errors/src/i18n/locales/de.ts @@ -101,6 +101,7 @@ const ERROR_I18N_DE_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Ungültiger Admin-API-Schlüssel.", "auth_and_oauth.missing_authorization": "Fehlender oder ungültiger Autorisierungsheader.", "auth_and_oauth.must_start_session_before_sending_messages": "Du musst eine Sitzung starten, bevor du Nachrichten senden kannst.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Diese Anmeldeübertragung stimmt nicht mit der von dir gestarteten überein.", "auth_and_oauth.registration_closed": "Die Registrierung ist auf dieser Instanz geschlossen.", "auth_and_oauth.registration_pending_approval": "Diese Registrierung wartet auf die Genehmigung durch einen Administrator.", "auth_and_oauth.registration_rejected": "Diese Registrierungsanfrage wurde abgelehnt.", @@ -113,6 +114,7 @@ const ERROR_I18N_DE_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Wir konnten keinen Discriminator für deinen SSO-Account vergeben. Versuche es noch einmal.", "auth_and_oauth.sudo_mode_required": "Sudo-Modus ist erforderlich.", "auth_and_oauth.unknown_application": "Unbekannte Anwendung.", + "auth_and_oauth.unknown_origin_handoff": "Diese Anmeldeübertragung ist abgelaufen oder wurde bereits verwendet.", "auth_and_registration.password_is_too_common": "Dein Passwort wird zu häufig verwendet. Wähle bitte ein Passwort, das weniger leicht zu erraten ist.", "auth_and_registration.password_not_set": "Das Passwort ist nicht festgelegt.", "bots.bot_discriminator_cannot_be_changed": "Der Bot-Discriminator kann nicht geändert werden.", diff --git a/packages/errors/src/i18n/locales/el.ts b/packages/errors/src/i18n/locales/el.ts index b4ba73e19..a7bedb0e5 100644 --- a/packages/errors/src/i18n/locales/el.ts +++ b/packages/errors/src/i18n/locales/el.ts @@ -101,6 +101,7 @@ const ERROR_I18N_EL_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Το κλειδί API διαχειριστή είναι μη έγκυρο.", "auth_and_oauth.missing_authorization": "Λείπει ή είναι άκυρη η κεφαλίδα εξουσιοδότησης.", "auth_and_oauth.must_start_session_before_sending_messages": "Πρέπει να ξεκινήσεις συνεδρία πριν στείλεις μηνύματα.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Αυτή η μεταφορά σύνδεσης δεν αντιστοιχεί σε αυτήν που ξεκινήσατε.", "auth_and_oauth.registration_closed": "Η εγγραφή είναι κλειστή σε αυτήν την εγκατάσταση.", "auth_and_oauth.registration_pending_approval": "Αυτή η εγγραφή περιμένει έγκριση από τον διαχειριστή.", "auth_and_oauth.registration_rejected": "Αυτό το αίτημα εγγραφής απορρίφθηκε.", @@ -113,6 +114,7 @@ const ERROR_I18N_EL_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Δεν μπορέσαμε να εκχωρήσουμε διακριτικό για τον λογαριασμό σου SSO. Δοκίμασε ξανά.", "auth_and_oauth.sudo_mode_required": "Απαιτείται η λειτουργία sudo.", "auth_and_oauth.unknown_application": "Άγνωστη εφαρμογή.", + "auth_and_oauth.unknown_origin_handoff": "Αυτή η μεταφορά σύνδεσης έχει λήξει ή έχει ήδη χρησιμοποιηθεί.", "auth_and_registration.password_is_too_common": "Ο κωδικός πρόσβασής σου είναι πολύ κοινός. Διάλεξε έναν λιγότερο προβλέψιμο κωδικό πρόσβασης.", "auth_and_registration.password_not_set": "Ο κωδικός πρόσβασης δεν έχει οριστεί.", "bots.bot_discriminator_cannot_be_changed": "Το διακριτικό του bot δεν μπορεί να αλλάξει.", diff --git a/packages/errors/src/i18n/locales/en-GB.ts b/packages/errors/src/i18n/locales/en-GB.ts index 5742e4f81..ea79258fa 100644 --- a/packages/errors/src/i18n/locales/en-GB.ts +++ b/packages/errors/src/i18n/locales/en-GB.ts @@ -101,6 +101,7 @@ const ERROR_I18N_EN_GB_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Invalid admin API key.", "auth_and_oauth.missing_authorization": "Missing or invalid authorisation header.", "auth_and_oauth.must_start_session_before_sending_messages": "You must start a session before sending messages.", + "auth_and_oauth.origin_handoff_nonce_invalid": "This sign-in transfer doesn't match the one you started.", "auth_and_oauth.registration_closed": "Registration is closed on this instance.", "auth_and_oauth.registration_pending_approval": "This registration is waiting for admin approval.", "auth_and_oauth.registration_rejected": "This registration request was rejected.", @@ -113,6 +114,7 @@ const ERROR_I18N_EN_GB_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "We couldn't allocate a discriminator for your SSO account. Please try again.", "auth_and_oauth.sudo_mode_required": "Sudo mode is required.", "auth_and_oauth.unknown_application": "Unknown application.", + "auth_and_oauth.unknown_origin_handoff": "This sign-in transfer has expired or was already used.", "auth_and_registration.password_is_too_common": "Your password is too common. Please choose a less predictable password.", "auth_and_registration.password_not_set": "Password isn't set.", "bots.bot_discriminator_cannot_be_changed": "Bot discriminator can't be changed.", diff --git a/packages/errors/src/i18n/locales/es-419.ts b/packages/errors/src/i18n/locales/es-419.ts index 0b84e553d..7b9a437e2 100644 --- a/packages/errors/src/i18n/locales/es-419.ts +++ b/packages/errors/src/i18n/locales/es-419.ts @@ -101,6 +101,7 @@ const ERROR_I18N_ES_419_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Clave de API de administrador no válida.", "auth_and_oauth.missing_authorization": "Falta el encabezado de autorización o no es válido.", "auth_and_oauth.must_start_session_before_sending_messages": "Debes iniciar una sesión antes de enviar mensajes.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Esta transferencia de inicio de sesión no coincide con la que iniciaste.", "auth_and_oauth.registration_closed": "El registro está cerrado en esta instancia.", "auth_and_oauth.registration_pending_approval": "Este registro está esperando la aprobación del administrador.", "auth_and_oauth.registration_rejected": "Esta solicitud de registro fue rechazada.", @@ -113,6 +114,7 @@ const ERROR_I18N_ES_419_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "No pudimos asignar un discriminador para tu cuenta de SSO. Inténtalo de nuevo.", "auth_and_oauth.sudo_mode_required": "Se requiere el modo sudo.", "auth_and_oauth.unknown_application": "Aplicación desconocida.", + "auth_and_oauth.unknown_origin_handoff": "Esta transferencia de inicio de sesión caducó o ya se usó.", "auth_and_registration.password_is_too_common": "Tu contraseña es demasiado común. Elige una contraseña menos predecible.", "auth_and_registration.password_not_set": "La contraseña no está configurada.", "bots.bot_discriminator_cannot_be_changed": "El discriminador del bot no se puede cambiar.", diff --git a/packages/errors/src/i18n/locales/es-ES.ts b/packages/errors/src/i18n/locales/es-ES.ts index 36c156bea..be5bf396d 100644 --- a/packages/errors/src/i18n/locales/es-ES.ts +++ b/packages/errors/src/i18n/locales/es-ES.ts @@ -101,6 +101,7 @@ const ERROR_I18N_ES_ES_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Clave de API de administrador no válida.", "auth_and_oauth.missing_authorization": "Falta la cabecera de autorización o no es válida.", "auth_and_oauth.must_start_session_before_sending_messages": "Debes iniciar sesión antes de enviar mensajes.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Esta transferencia de inicio de sesión no coincide con la que iniciaste.", "auth_and_oauth.registration_closed": "El registro está cerrado en esta instancia.", "auth_and_oauth.registration_pending_approval": "Este registro está pendiente de aprobación por parte del administrador.", "auth_and_oauth.registration_rejected": "Esta solicitud de registro ha sido rechazada.", @@ -113,6 +114,7 @@ const ERROR_I18N_ES_ES_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "No se pudo asignar un discriminador para tu cuenta SSO. Inténtalo de nuevo.", "auth_and_oauth.sudo_mode_required": "Se requiere el modo sudo.", "auth_and_oauth.unknown_application": "Aplicación desconocida.", + "auth_and_oauth.unknown_origin_handoff": "Esta transferencia de inicio de sesión ha caducado o ya se ha usado.", "auth_and_registration.password_is_too_common": "Tu contraseña es demasiado común. Elige una contraseña menos predecible.", "auth_and_registration.password_not_set": "La contraseña no está establecida.", "bots.bot_discriminator_cannot_be_changed": "El discriminador del bot no se puede cambiar.", diff --git a/packages/errors/src/i18n/locales/fi.ts b/packages/errors/src/i18n/locales/fi.ts index 0861b202f..b886fd45d 100644 --- a/packages/errors/src/i18n/locales/fi.ts +++ b/packages/errors/src/i18n/locales/fi.ts @@ -101,6 +101,7 @@ const ERROR_I18N_FI_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Virheellinen ylläpidon API-avain.", "auth_and_oauth.missing_authorization": "Valtuutusotsake puuttuu tai on virheellinen.", "auth_and_oauth.must_start_session_before_sending_messages": "Sinun on aloitettava istunto ennen viestien lähettämistä.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Tämä kirjautumisen siirto ei vastaa aloittamaasi siirtoa.", "auth_and_oauth.registration_closed": "Rekisteröinti on suljettu tällä instanssilla.", "auth_and_oauth.registration_pending_approval": "Tämä rekisteröinti odottaa järjestelmänvalvojan hyväksyntää.", "auth_and_oauth.registration_rejected": "Tämä rekisteröintipyyntö hylättiin.", @@ -113,6 +114,7 @@ const ERROR_I18N_FI_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "SSO-tilillesi ei voitu varata nelinumeroista tunnistetta. Yritä uudelleen.", "auth_and_oauth.sudo_mode_required": "Sudo-tila vaaditaan.", "auth_and_oauth.unknown_application": "Tuntematon sovellus.", + "auth_and_oauth.unknown_origin_handoff": "Tämä kirjautumisen siirto on vanhentunut tai jo käytetty.", "auth_and_registration.password_is_too_common": "Salasanasi on liian yleinen. Valitse vaikeammin arvattava salasana.", "auth_and_registration.password_not_set": "Salasanaa ei ole asetettu.", "bots.bot_discriminator_cannot_be_changed": "Botin nelinumeroista tunnistetta ei voi muuttaa.", diff --git a/packages/errors/src/i18n/locales/fr.ts b/packages/errors/src/i18n/locales/fr.ts index 460135f49..ef140d0c4 100644 --- a/packages/errors/src/i18n/locales/fr.ts +++ b/packages/errors/src/i18n/locales/fr.ts @@ -101,6 +101,7 @@ const ERROR_I18N_FR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Clé API administrateur non valide.", "auth_and_oauth.missing_authorization": "En-tête d'autorisation manquant ou non valide.", "auth_and_oauth.must_start_session_before_sending_messages": "Vous devez démarrer une session avant d’envoyer des messages.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Ce transfert de connexion ne correspond pas à celui que tu as lancé.", "auth_and_oauth.registration_closed": "L'inscription est fermée sur cette instance.", "auth_and_oauth.registration_pending_approval": "Cette inscription est en attente d'approbation par un administrateur.", "auth_and_oauth.registration_rejected": "Cette demande d'inscription a été rejetée.", @@ -113,6 +114,7 @@ const ERROR_I18N_FR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Impossible d’attribuer un discriminateur à votre compte SSO. Réessayez.", "auth_and_oauth.sudo_mode_required": "Le mode Sudo est requis.", "auth_and_oauth.unknown_application": "Application inconnue.", + "auth_and_oauth.unknown_origin_handoff": "Ce transfert de connexion a expiré ou a déjà été utilisé.", "auth_and_registration.password_is_too_common": "Votre mot de passe est trop courant. Choisissez-en un moins prévisible.", "auth_and_registration.password_not_set": "Le mot de passe n'est pas défini.", "bots.bot_discriminator_cannot_be_changed": "Le discriminateur du bot ne peut pas être modifié.", diff --git a/packages/errors/src/i18n/locales/he.ts b/packages/errors/src/i18n/locales/he.ts index 77dfa7678..2101f23c0 100644 --- a/packages/errors/src/i18n/locales/he.ts +++ b/packages/errors/src/i18n/locales/he.ts @@ -101,6 +101,7 @@ const ERROR_I18N_HE_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "מפתח API של אדמין שגוי.", "auth_and_oauth.missing_authorization": "כותרת ההרשאה חסרה או שגויה.", "auth_and_oauth.must_start_session_before_sending_messages": "יש להתחיל סשן לפני שליחת הודעות.", + "auth_and_oauth.origin_handoff_nonce_invalid": "העברת ההתחברות הזו אינה תואמת לזו שהתחלת.", "auth_and_oauth.registration_closed": "ההרשמה סגורה במופע זה.", "auth_and_oauth.registration_pending_approval": "הרשמה זו ממתינה לאישור מנהל.", "auth_and_oauth.registration_rejected": "בקשת הרשמה זו נדחתה.", @@ -113,6 +114,7 @@ const ERROR_I18N_HE_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "לא הצלחנו להקצות מזהה לחשבון ה-SSO שלך. ניתן לנסות שוב.", "auth_and_oauth.sudo_mode_required": "נדרש מצב sudo.", "auth_and_oauth.unknown_application": "אפליקציה לא ידועה.", + "auth_and_oauth.unknown_origin_handoff": "פג תוקפה של העברת ההתחברות הזו או שכבר נעשה בה שימוש.", "auth_and_registration.password_is_too_common": "הסיסמה שלך נפוצה מדי. מומלץ לבחור סיסמה פחות צפויה.", "auth_and_registration.password_not_set": "סיסמה לא הוגדרה.", "bots.bot_discriminator_cannot_be_changed": "לא ניתן לשנות את המזהה של הבוט.", diff --git a/packages/errors/src/i18n/locales/hi.ts b/packages/errors/src/i18n/locales/hi.ts index f8e7d67f0..17de90199 100644 --- a/packages/errors/src/i18n/locales/hi.ts +++ b/packages/errors/src/i18n/locales/hi.ts @@ -101,6 +101,7 @@ const ERROR_I18N_HI_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "एडमिन API कुंजी अमान्य है।", "auth_and_oauth.missing_authorization": "ऑथराइज़ेशन हेडर गायब या अमान्य है।", "auth_and_oauth.must_start_session_before_sending_messages": "मैसेज भेजने से पहले आपको एक सेशन शुरू करना होगा।", + "auth_and_oauth.origin_handoff_nonce_invalid": "यह साइन-इन ट्रांसफ़र आपके द्वारा शुरू किए गए ट्रांसफ़र से मेल नहीं खाता।", "auth_and_oauth.registration_closed": "इस इंस्टेंस पर रजिस्ट्रेशन बंद है।", "auth_and_oauth.registration_pending_approval": "यह रजिस्ट्रेशन एडमिन अप्रूवल का इंतज़ार कर रहा है।", "auth_and_oauth.registration_rejected": "यह रजिस्ट्रेशन रिक्वेस्ट रिजेक्ट कर दी गई है।", @@ -113,6 +114,7 @@ const ERROR_I18N_HI_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "हम आपके SSO अकाउंट के लिए डिस्क्रिमिनेटर अलॉट नहीं कर सके। फिर से कोशिश करें।", "auth_and_oauth.sudo_mode_required": "सुडो मोड ज़रूरी है।", "auth_and_oauth.unknown_application": "अज्ञात एप्लीकेशन।", + "auth_and_oauth.unknown_origin_handoff": "यह साइन-इन ट्रांसफ़र समाप्त हो गया है या पहले ही उपयोग किया जा चुका है।", "auth_and_registration.password_is_too_common": "आपका पासवर्ड बहुत कॉमन है। कृपया ऐसा पासवर्ड चुनें जिसका अंदाज़ा लगाना मुश्किल हो।", "auth_and_registration.password_not_set": "पासवर्ड सेट नहीं है।", "bots.bot_discriminator_cannot_be_changed": "बॉट डिस्क्रिमिनेटर को बदला नहीं जा सकता।", diff --git a/packages/errors/src/i18n/locales/hr.ts b/packages/errors/src/i18n/locales/hr.ts index 33254da3e..bf8d58cc0 100644 --- a/packages/errors/src/i18n/locales/hr.ts +++ b/packages/errors/src/i18n/locales/hr.ts @@ -101,6 +101,7 @@ const ERROR_I18N_HR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Nevažeći administratorski API ključ.", "auth_and_oauth.missing_authorization": "Nedostaje ili je nevažeće autorizacijsko zaglavlje.", "auth_and_oauth.must_start_session_before_sending_messages": "Moraš započeti sesiju prije slanja poruka.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Ovaj prijenos prijave ne odgovara onome koji ste započeli.", "auth_and_oauth.registration_closed": "Registracija je zatvorena na ovoj instanci.", "auth_and_oauth.registration_pending_approval": "Ova registracija čeka odobrenje administratora.", "auth_and_oauth.registration_rejected": "Ovaj zahtjev za registraciju je odbijen.", @@ -113,6 +114,7 @@ const ERROR_I18N_HR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nismo mogli dodijeliti diskriminator tvom SSO računu. Pokušaj ponovno.", "auth_and_oauth.sudo_mode_required": "Potreban je sudo način rada.", "auth_and_oauth.unknown_application": "Nepoznata aplikacija.", + "auth_and_oauth.unknown_origin_handoff": "Ovaj prijenos prijave je istekao ili je već iskorišten.", "auth_and_registration.password_is_too_common": "Tvoja je lozinka previše uobičajena. Odaberi manje predvidljivu lozinku.", "auth_and_registration.password_not_set": "Lozinka nije postavljena.", "bots.bot_discriminator_cannot_be_changed": "Diskriminator bota ne može se promijeniti.", diff --git a/packages/errors/src/i18n/locales/hu.ts b/packages/errors/src/i18n/locales/hu.ts index 38bba1143..f8187940d 100644 --- a/packages/errors/src/i18n/locales/hu.ts +++ b/packages/errors/src/i18n/locales/hu.ts @@ -101,6 +101,7 @@ const ERROR_I18N_HU_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Érvénytelen adminisztrátori API-kulcs.", "auth_and_oauth.missing_authorization": "Hiányzó vagy érvénytelen hitelesítési fejléc.", "auth_and_oauth.must_start_session_before_sending_messages": "Üzenetek küldéséhez előbb munkamenetet kell indítanod.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Ez a bejelentkezés-átvitel nem egyezik azzal, amelyet elindítottál.", "auth_and_oauth.registration_closed": "Ezen a példányon a regisztráció le van zárva.", "auth_and_oauth.registration_pending_approval": "Ez a regisztráció rendszergazdai jóváhagyásra vár.", "auth_and_oauth.registration_rejected": "Ez a regisztrációs kérelem el lett utasítva.", @@ -113,6 +114,7 @@ const ERROR_I18N_HU_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nem sikerült számazonosítót kiosztani az SSO-fiókodhoz. Próbáld újra.", "auth_and_oauth.sudo_mode_required": "Sudo mód szükséges.", "auth_and_oauth.unknown_application": "Ismeretlen alkalmazás.", + "auth_and_oauth.unknown_origin_handoff": "Ez a bejelentkezés-átvitel lejárt, vagy már felhasználták.", "auth_and_registration.password_is_too_common": "A jelszavad túl gyakori. Válassz nehezebben kitalálható jelszót.", "auth_and_registration.password_not_set": "Nincs beállítva jelszó.", "bots.bot_discriminator_cannot_be_changed": "A bot számazonosítója nem módosítható.", diff --git a/packages/errors/src/i18n/locales/id.ts b/packages/errors/src/i18n/locales/id.ts index 85219591e..1a9cff425 100644 --- a/packages/errors/src/i18n/locales/id.ts +++ b/packages/errors/src/i18n/locales/id.ts @@ -101,6 +101,7 @@ const ERROR_I18N_ID_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Kunci API admin tidak valid.", "auth_and_oauth.missing_authorization": "Header otorisasi hilang atau tidak valid.", "auth_and_oauth.must_start_session_before_sending_messages": "Kamu harus memulai sesi sebelum mengirim pesan.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Transfer masuk ini tidak cocok dengan yang kamu mulai.", "auth_and_oauth.registration_closed": "Pendaftaran ditutup di instance ini.", "auth_and_oauth.registration_pending_approval": "Pendaftaran ini menunggu persetujuan admin.", "auth_and_oauth.registration_rejected": "Permintaan pendaftaran ini ditolak.", @@ -113,6 +114,7 @@ const ERROR_I18N_ID_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Tidak bisa mengalokasikan diskriminator untuk akun SSO kamu. Silakan coba lagi.", "auth_and_oauth.sudo_mode_required": "Mode sudo diperlukan.", "auth_and_oauth.unknown_application": "Aplikasi tidak dikenal.", + "auth_and_oauth.unknown_origin_handoff": "Transfer masuk ini sudah kedaluwarsa atau sudah digunakan.", "auth_and_registration.password_is_too_common": "Kata sandimu terlalu umum. Pilih kata sandi yang lebih sulit ditebak.", "auth_and_registration.password_not_set": "Kata sandi belum diatur.", "bots.bot_discriminator_cannot_be_changed": "Diskriminator bot tidak dapat diubah.", diff --git a/packages/errors/src/i18n/locales/it.ts b/packages/errors/src/i18n/locales/it.ts index 3f1fb9ad6..52a2999b0 100644 --- a/packages/errors/src/i18n/locales/it.ts +++ b/packages/errors/src/i18n/locales/it.ts @@ -101,6 +101,7 @@ const ERROR_I18N_IT_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Chiave API amministratore non valida.", "auth_and_oauth.missing_authorization": "Header di autorizzazione mancante o non valido.", "auth_and_oauth.must_start_session_before_sending_messages": "Devi avviare una sessione prima di inviare messaggi.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Questo trasferimento di accesso non corrisponde a quello che hai avviato.", "auth_and_oauth.registration_closed": "La registrazione è chiusa su questa istanza.", "auth_and_oauth.registration_pending_approval": "Questa registrazione è in attesa dell'approvazione dell'amministratore.", "auth_and_oauth.registration_rejected": "Questa richiesta di registrazione è stata rifiutata.", @@ -113,6 +114,7 @@ const ERROR_I18N_IT_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Non è stato possibile assegnare un discriminatore al tuo account SSO. Riprova.", "auth_and_oauth.sudo_mode_required": "La modalità Sudo è richiesta.", "auth_and_oauth.unknown_application": "Applicazione sconosciuta.", + "auth_and_oauth.unknown_origin_handoff": "Questo trasferimento di accesso è scaduto o è già stato usato.", "auth_and_registration.password_is_too_common": "La tua password è troppo comune. Scegli una password meno prevedibile.", "auth_and_registration.password_not_set": "La password non è impostata.", "bots.bot_discriminator_cannot_be_changed": "Il discriminatore del bot non può essere modificato.", diff --git a/packages/errors/src/i18n/locales/ja.ts b/packages/errors/src/i18n/locales/ja.ts index f19fd5888..571f744e0 100644 --- a/packages/errors/src/i18n/locales/ja.ts +++ b/packages/errors/src/i18n/locales/ja.ts @@ -101,6 +101,7 @@ const ERROR_I18N_JA_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "管理者APIキーが無効です。", "auth_and_oauth.missing_authorization": "認証ヘッダーが見つからないか、無効です。", "auth_and_oauth.must_start_session_before_sending_messages": "メッセージを送信する前にセッションを開始してください。", + "auth_and_oauth.origin_handoff_nonce_invalid": "このサインインの引き継ぎは、開始したものと一致しません。", "auth_and_oauth.registration_closed": "このインスタンスでは新規登録を受け付けていません。", "auth_and_oauth.registration_pending_approval": "この登録は管理者による承認待ちです。", "auth_and_oauth.registration_rejected": "この登録リクエストは拒否されました。", @@ -113,6 +114,7 @@ const ERROR_I18N_JA_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "SSOアカウントの識別子を割り当てられませんでした。もう一度お試しください。", "auth_and_oauth.sudo_mode_required": "Sudoモードが必要です。", "auth_and_oauth.unknown_application": "不明なアプリケーションです。", + "auth_and_oauth.unknown_origin_handoff": "このサインインの引き継ぎは期限切れか、すでに使用されています。", "auth_and_registration.password_is_too_common": "パスワードが一般的すぎます。より推測されにくいパスワードを設定してください。", "auth_and_registration.password_not_set": "パスワードが未設定です。", "bots.bot_discriminator_cannot_be_changed": "ボットの識別子は変更できません。", diff --git a/packages/errors/src/i18n/locales/ko.ts b/packages/errors/src/i18n/locales/ko.ts index f4b160db1..930eb773f 100644 --- a/packages/errors/src/i18n/locales/ko.ts +++ b/packages/errors/src/i18n/locales/ko.ts @@ -101,6 +101,7 @@ const ERROR_I18N_KO_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "잘못된 관리자 API 키예요.", "auth_and_oauth.missing_authorization": "인증 헤더가 없거나 유효하지 않아요.", "auth_and_oauth.must_start_session_before_sending_messages": "메시지를 보내기 전에 세션을 시작해야 해요.", + "auth_and_oauth.origin_handoff_nonce_invalid": "이 로그인 이전 요청이 시작한 요청과 일치하지 않습니다.", "auth_and_oauth.registration_closed": "이 인스턴스에서는 가입을 받고 있지 않아요.", "auth_and_oauth.registration_pending_approval": "이 가입 신청은 관리자 승인을 기다리고 있어요.", "auth_and_oauth.registration_rejected": "이 가입 요청은 거부되었어요.", @@ -113,6 +114,7 @@ const ERROR_I18N_KO_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "SSO 계정에 디스크리미네이터를 할당하지 못했어요. 다시 시도해 주세요.", "auth_and_oauth.sudo_mode_required": "Sudo 모드가 필요해요.", "auth_and_oauth.unknown_application": "알 수 없는 애플리케이션이에요.", + "auth_and_oauth.unknown_origin_handoff": "이 로그인 이전 요청이 만료되었거나 이미 사용되었습니다.", "auth_and_registration.password_is_too_common": "비밀번호가 너무 흔해요. 추측하기 어려운 비밀번호를 사용해 주세요.", "auth_and_registration.password_not_set": "비밀번호가 설정되지 않았어요.", "bots.bot_discriminator_cannot_be_changed": "봇 디스크리미네이터를 변경할 수 없어요.", diff --git a/packages/errors/src/i18n/locales/lt.ts b/packages/errors/src/i18n/locales/lt.ts index 61bb717b6..438eaa9b9 100644 --- a/packages/errors/src/i18n/locales/lt.ts +++ b/packages/errors/src/i18n/locales/lt.ts @@ -101,6 +101,7 @@ const ERROR_I18N_LT_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Negaliojantis administratoriaus API raktas.", "auth_and_oauth.missing_authorization": "Trūksta autorizacijos antraštės arba ji neteisinga.", "auth_and_oauth.must_start_session_before_sending_messages": "Kad galėtum siųsti žinutes, pirmiausia pradėk seansą.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Šis prisijungimo perkėlimas nesutampa su tuo, kurį pradėjote.", "auth_and_oauth.registration_closed": "Registracija šioje instancijoje yra uždaryta.", "auth_and_oauth.registration_pending_approval": "Ši registracija laukia administratoriaus patvirtinimo.", "auth_and_oauth.registration_rejected": "Šis registracijos prašymas buvo atmestas.", @@ -113,6 +114,7 @@ const ERROR_I18N_LT_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nepavyko priskirti diskriminatoriaus tavo SSO paskyrai. Pabandyk dar kartą.", "auth_and_oauth.sudo_mode_required": "Reikalingas sudo režimas.", "auth_and_oauth.unknown_application": "Nežinoma programa.", + "auth_and_oauth.unknown_origin_handoff": "Šio prisijungimo perkėlimo galiojimas baigėsi arba jis jau panaudotas.", "auth_and_registration.password_is_too_common": "Tavo slaptažodis per dažnai naudojamas. Pasirink sunkiau nuspėjamą slaptažodį.", "auth_and_registration.password_not_set": "Slaptažodis nenustatytas.", "bots.bot_discriminator_cannot_be_changed": "Boto diskriminatoriaus negalima pakeisti.", diff --git a/packages/errors/src/i18n/locales/nl.ts b/packages/errors/src/i18n/locales/nl.ts index c8bd6dbcd..37742b0b1 100644 --- a/packages/errors/src/i18n/locales/nl.ts +++ b/packages/errors/src/i18n/locales/nl.ts @@ -101,6 +101,7 @@ const ERROR_I18N_NL_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "De admin-API-sleutel is ongeldig.", "auth_and_oauth.missing_authorization": "De autorisatieheader ontbreekt of is ongeldig.", "auth_and_oauth.must_start_session_before_sending_messages": "Je moet een sessie starten voordat je berichten kunt verzenden.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Deze aanmeldoverdracht komt niet overeen met de overdracht die je hebt gestart.", "auth_and_oauth.registration_closed": "Registratie is gesloten op deze instantie.", "auth_and_oauth.registration_pending_approval": "Deze registratie wacht op goedkeuring van de beheerder.", "auth_and_oauth.registration_rejected": "Dit registratieverzoek is afgewezen.", @@ -113,6 +114,7 @@ const ERROR_I18N_NL_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "We konden geen discriminator toewijzen aan je SSO-account. Probeer het nog eens.", "auth_and_oauth.sudo_mode_required": "Sudo-modus is vereist.", "auth_and_oauth.unknown_application": "Onbekende app.", + "auth_and_oauth.unknown_origin_handoff": "Deze aanmeldoverdracht is verlopen of al gebruikt.", "auth_and_registration.password_is_too_common": "Je wachtwoord komt te vaak voor. Kies een minder voorspelbaar wachtwoord.", "auth_and_registration.password_not_set": "Wachtwoord is niet ingesteld.", "bots.bot_discriminator_cannot_be_changed": "De discriminator van een bot kan niet worden gewijzigd.", diff --git a/packages/errors/src/i18n/locales/no.ts b/packages/errors/src/i18n/locales/no.ts index 79a4a5d33..09f79410a 100644 --- a/packages/errors/src/i18n/locales/no.ts +++ b/packages/errors/src/i18n/locales/no.ts @@ -101,6 +101,7 @@ const ERROR_I18N_NO_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Ugyldig API-nøkkel for administrasjon.", "auth_and_oauth.missing_authorization": "Autorisasjonsheaderen mangler eller er ugyldig.", "auth_and_oauth.must_start_session_before_sending_messages": "Du må starte en økt før du sender meldinger.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Denne overføringen av innloggingen samsvarer ikke med den du startet.", "auth_and_oauth.registration_closed": "Registrering er stengt på denne instansen.", "auth_and_oauth.registration_pending_approval": "Denne registreringen venter på administratorgodkjenning.", "auth_and_oauth.registration_rejected": "Denne registreringsforespørselen ble avvist.", @@ -113,6 +114,7 @@ const ERROR_I18N_NO_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Vi klarte ikke å tildele en diskriminator til SSO-kontoen din. Prøv igjen.", "auth_and_oauth.sudo_mode_required": "Sudo-modus er påkrevd.", "auth_and_oauth.unknown_application": "Ukjent app.", + "auth_and_oauth.unknown_origin_handoff": "Denne overføringen av innloggingen er utløpt eller allerede brukt.", "auth_and_registration.password_is_too_common": "Passordet ditt er for vanlig. Velg et passord som er vanskeligere å gjette.", "auth_and_registration.password_not_set": "Det er ikke angitt noe passord.", "bots.bot_discriminator_cannot_be_changed": "Botens diskriminator kan ikke endres.", diff --git a/packages/errors/src/i18n/locales/pl.ts b/packages/errors/src/i18n/locales/pl.ts index cb4d5f42e..24612291e 100644 --- a/packages/errors/src/i18n/locales/pl.ts +++ b/packages/errors/src/i18n/locales/pl.ts @@ -101,6 +101,7 @@ const ERROR_I18N_PL_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Nieprawidłowy klucz API administratora.", "auth_and_oauth.missing_authorization": "Brakuje nagłówka autoryzacji lub jest on nieprawidłowy.", "auth_and_oauth.must_start_session_before_sending_messages": "Musisz rozpocząć sesję, zanim wyślesz wiadomości.", + "auth_and_oauth.origin_handoff_nonce_invalid": "To przeniesienie logowania nie pasuje do tego, które zostało rozpoczęte.", "auth_and_oauth.registration_closed": "Rejestracja na tej instancji jest zamknięta.", "auth_and_oauth.registration_pending_approval": "Ta rejestracja czeka na zatwierdzenie przez administratora.", "auth_and_oauth.registration_rejected": "Ten wniosek o rejestrację został odrzucony.", @@ -113,6 +114,7 @@ const ERROR_I18N_PL_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nie udało się przydzielić dyskryminatora dla Twojego konta SSO. Spróbuj ponownie.", "auth_and_oauth.sudo_mode_required": "Wymagany tryb sudo.", "auth_and_oauth.unknown_application": "Nieznana aplikacja.", + "auth_and_oauth.unknown_origin_handoff": "To przeniesienie logowania wygasło lub zostało już użyte.", "auth_and_registration.password_is_too_common": "Twoje hasło jest zbyt popularne. Wybierz mniej przewidywalne hasło.", "auth_and_registration.password_not_set": "Hasło nie jest ustawione.", "bots.bot_discriminator_cannot_be_changed": "Nie można zmienić dyskryminatora bota.", diff --git a/packages/errors/src/i18n/locales/pt-BR.ts b/packages/errors/src/i18n/locales/pt-BR.ts index c3ff2347f..46e1dd9c0 100644 --- a/packages/errors/src/i18n/locales/pt-BR.ts +++ b/packages/errors/src/i18n/locales/pt-BR.ts @@ -101,6 +101,7 @@ const ERROR_I18N_PT_BR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Chave de API de administrador inválida.", "auth_and_oauth.missing_authorization": "Cabeçalho de autorização ausente ou inválido.", "auth_and_oauth.must_start_session_before_sending_messages": "Você deve iniciar uma sessão antes de enviar mensagens.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Esta transferência de login não corresponde à que você iniciou.", "auth_and_oauth.registration_closed": "O cadastro está fechado nesta instância.", "auth_and_oauth.registration_pending_approval": "Este cadastro está aguardando a aprovação de um administrador.", "auth_and_oauth.registration_rejected": "Esta solicitação de cadastro foi rejeitada.", @@ -113,6 +114,7 @@ const ERROR_I18N_PT_BR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Não foi possível alocar um discriminador para sua conta SSO. Tente novamente.", "auth_and_oauth.sudo_mode_required": "O modo sudo é obrigatório.", "auth_and_oauth.unknown_application": "Aplicativo desconhecido.", + "auth_and_oauth.unknown_origin_handoff": "Esta transferência de login expirou ou já foi usada.", "auth_and_registration.password_is_too_common": "Sua senha é muito comum. Escolha uma senha menos previsível.", "auth_and_registration.password_not_set": "A senha não foi definida.", "bots.bot_discriminator_cannot_be_changed": "O discriminador do bot não pode ser alterado.", diff --git a/packages/errors/src/i18n/locales/ro.ts b/packages/errors/src/i18n/locales/ro.ts index 13ba25d90..34470ae37 100644 --- a/packages/errors/src/i18n/locales/ro.ts +++ b/packages/errors/src/i18n/locales/ro.ts @@ -101,6 +101,7 @@ const ERROR_I18N_RO_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Cheia API de administrator este invalidă.", "auth_and_oauth.missing_authorization": "Headerul de autorizare lipsește sau este invalid.", "auth_and_oauth.must_start_session_before_sending_messages": "Trebuie să începi o sesiune înainte de a trimite mesaje.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Acest transfer de autentificare nu corespunde celui pe care l-ai început.", "auth_and_oauth.registration_closed": "Înregistrarea este închisă pe această instanță.", "auth_and_oauth.registration_pending_approval": "Această înregistrare așteaptă aprobarea administratorului.", "auth_and_oauth.registration_rejected": "Această cerere de înregistrare a fost respinsă.", @@ -113,6 +114,7 @@ const ERROR_I18N_RO_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nu s-a putut aloca un discriminator pentru contul tău SSO. Încearcă din nou.", "auth_and_oauth.sudo_mode_required": "Modul Sudo este necesar.", "auth_and_oauth.unknown_application": "Aplicație necunoscută.", + "auth_and_oauth.unknown_origin_handoff": "Acest transfer de autentificare a expirat sau a fost deja folosit.", "auth_and_registration.password_is_too_common": "Parola ta este prea comună. Alege o parolă mai greu de ghicit.", "auth_and_registration.password_not_set": "Parola nu este setată.", "bots.bot_discriminator_cannot_be_changed": "Discriminatorul botului nu poate fi schimbat.", diff --git a/packages/errors/src/i18n/locales/ru.ts b/packages/errors/src/i18n/locales/ru.ts index 939e7352a..0ab2092b3 100644 --- a/packages/errors/src/i18n/locales/ru.ts +++ b/packages/errors/src/i18n/locales/ru.ts @@ -101,6 +101,7 @@ const ERROR_I18N_RU_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Недействительный ключ Admin API.", "auth_and_oauth.missing_authorization": "Заголовок авторизации отсутствует или недействителен.", "auth_and_oauth.must_start_session_before_sending_messages": "Перед отправкой сообщений нужно начать сессию.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Этот перенос входа не совпадает с тем, который вы начали.", "auth_and_oauth.registration_closed": "Регистрация в этом экземпляре закрыта.", "auth_and_oauth.registration_pending_approval": "Эта регистрация ожидает одобрения администратора.", "auth_and_oauth.registration_rejected": "Заявка на регистрацию отклонена.", @@ -113,6 +114,7 @@ const ERROR_I18N_RU_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Не удалось назначить дискриминатор для твоего аккаунта SSO. Попробуй ещё раз.", "auth_and_oauth.sudo_mode_required": "Требуется режим sudo.", "auth_and_oauth.unknown_application": "Неизвестное приложение.", + "auth_and_oauth.unknown_origin_handoff": "Срок действия этого переноса входа истёк, или он уже использован.", "auth_and_registration.password_is_too_common": "Твой пароль слишком распространённый. Придумай менее предсказуемый пароль.", "auth_and_registration.password_not_set": "Пароль не установлен.", "bots.bot_discriminator_cannot_be_changed": "Дискриминатор бота нельзя изменить.", diff --git a/packages/errors/src/i18n/locales/sv-SE.ts b/packages/errors/src/i18n/locales/sv-SE.ts index 57c2a39e5..2f491034e 100644 --- a/packages/errors/src/i18n/locales/sv-SE.ts +++ b/packages/errors/src/i18n/locales/sv-SE.ts @@ -101,6 +101,7 @@ const ERROR_I18N_SV_SE_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Ogiltig admin-API-nyckel.", "auth_and_oauth.missing_authorization": "Authorization-huvudet saknas eller är ogiltigt.", "auth_and_oauth.must_start_session_before_sending_messages": "Du måste starta en session innan du kan skicka meddelanden.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Den här inloggningsöverföringen matchar inte den du startade.", "auth_and_oauth.registration_closed": "Registrering är stängd på den här instansen.", "auth_and_oauth.registration_pending_approval": "Den här registreringen väntar på administratörsgodkännande.", "auth_and_oauth.registration_rejected": "Den här registreringsbegäran avslogs.", @@ -113,6 +114,7 @@ const ERROR_I18N_SV_SE_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Vi kunde inte tilldela en diskriminator för ditt SSO-konto. Försök igen.", "auth_and_oauth.sudo_mode_required": "Sudo-läge krävs.", "auth_and_oauth.unknown_application": "Okänd applikation.", + "auth_and_oauth.unknown_origin_handoff": "Den här inloggningsöverföringen har gått ut eller har redan använts.", "auth_and_registration.password_is_too_common": "Ditt lösenord är för vanligt. Välj ett lösenord som är svårare att gissa.", "auth_and_registration.password_not_set": "Inget lösenord har angetts.", "bots.bot_discriminator_cannot_be_changed": "Botdiskriminatorn kan inte ändras.", diff --git a/packages/errors/src/i18n/locales/th.ts b/packages/errors/src/i18n/locales/th.ts index f5ad14f34..a0dd90595 100644 --- a/packages/errors/src/i18n/locales/th.ts +++ b/packages/errors/src/i18n/locales/th.ts @@ -101,6 +101,7 @@ const ERROR_I18N_TH_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Admin API key ไม่ถูกต้อง", "auth_and_oauth.missing_authorization": "ส่วนหัว Authorization หายไปหรือไม่ถูกต้อง", "auth_and_oauth.must_start_session_before_sending_messages": "ต้องเริ่มเซสชันก่อนส่งข้อความ", + "auth_and_oauth.origin_handoff_nonce_invalid": "การโอนการลงชื่อเข้าใช้นี้ไม่ตรงกับรายการที่คุณเริ่มไว้", "auth_and_oauth.registration_closed": "การลงทะเบียนปิดให้บริการในอินสแตนซ์นี้", "auth_and_oauth.registration_pending_approval": "การลงทะเบียนนี้กำลังรอการอนุมัติจากผู้ดูแลระบบ", "auth_and_oauth.registration_rejected": "คำขอลงทะเบียนนี้ถูกปฏิเสธ", @@ -113,6 +114,7 @@ const ERROR_I18N_TH_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "ไม่สามารถสร้าง discriminator สำหรับบัญชี SSO ของคุณได้ โปรดลองอีกครั้ง", "auth_and_oauth.sudo_mode_required": "ต้องใช้โหมด sudo", "auth_and_oauth.unknown_application": "ไม่พบแอปพลิเคชัน", + "auth_and_oauth.unknown_origin_handoff": "การโอนการลงชื่อเข้าใช้นี้หมดอายุแล้วหรือถูกใช้ไปแล้ว", "auth_and_registration.password_is_too_common": "รหัสผ่านของคุณเป็นรหัสที่พบบ่อยเกินไป โปรดเลือกรหัสผ่านที่เดาได้ยากขึ้น", "auth_and_registration.password_not_set": "ยังไม่ได้ตั้งรหัสผ่าน", "bots.bot_discriminator_cannot_be_changed": "ไม่สามารถเปลี่ยน discriminator ของบอทได้", diff --git a/packages/errors/src/i18n/locales/tr.ts b/packages/errors/src/i18n/locales/tr.ts index 4a61b1f62..857ca90d1 100644 --- a/packages/errors/src/i18n/locales/tr.ts +++ b/packages/errors/src/i18n/locales/tr.ts @@ -101,6 +101,7 @@ const ERROR_I18N_TR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Geçersiz yönetici API anahtarı.", "auth_and_oauth.missing_authorization": "Eksik veya geçersiz yetkilendirme başlığı.", "auth_and_oauth.must_start_session_before_sending_messages": "Mesaj göndermeden önce bir oturum başlatmalısın.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Bu oturum açma aktarımı başlattığın aktarımla eşleşmiyor.", "auth_and_oauth.registration_closed": "Bu kurulumda kayıt kapalı.", "auth_and_oauth.registration_pending_approval": "Bu kayıt yönetici onayını bekliyor.", "auth_and_oauth.registration_rejected": "Bu kayıt isteği reddedildi.", @@ -113,6 +114,7 @@ const ERROR_I18N_TR_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "SSO hesabın için bir etiket atayamadık. Lütfen tekrar dene.", "auth_and_oauth.sudo_mode_required": "Sudo modu gerekli.", "auth_and_oauth.unknown_application": "Bilinmeyen uygulama.", + "auth_and_oauth.unknown_origin_handoff": "Bu oturum açma aktarımının süresi doldu veya zaten kullanıldı.", "auth_and_registration.password_is_too_common": "Şifren çok yaygın. Lütfen tahmin edilmesi daha zor bir şifre seç.", "auth_and_registration.password_not_set": "Şifre ayarlanmamış.", "bots.bot_discriminator_cannot_be_changed": "Bot etiketi değiştirilemez.", diff --git a/packages/errors/src/i18n/locales/uk.ts b/packages/errors/src/i18n/locales/uk.ts index 38fb04480..b4abcf74b 100644 --- a/packages/errors/src/i18n/locales/uk.ts +++ b/packages/errors/src/i18n/locales/uk.ts @@ -101,6 +101,7 @@ const ERROR_I18N_UK_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Недійсний API-ключ адміністратора.", "auth_and_oauth.missing_authorization": "Відсутній або недійсний заголовок авторизації.", "auth_and_oauth.must_start_session_before_sending_messages": "Потрібно розпочати сесію, перш ніж надсилати повідомлення.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Це перенесення входу не збігається з тим, яке ви розпочали.", "auth_and_oauth.registration_closed": "Реєстрацію на цьому екземплярі закрито.", "auth_and_oauth.registration_pending_approval": "Ця реєстрація очікує на схвалення адміністратором.", "auth_and_oauth.registration_rejected": "Цей запит на реєстрацію було відхилено.", @@ -113,6 +114,7 @@ const ERROR_I18N_UK_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Не вдалося призначити дискримінатор для твого акаунта SSO. Спробуй ще раз.", "auth_and_oauth.sudo_mode_required": "Потрібен режим sudo.", "auth_and_oauth.unknown_application": "Невідомий застосунок.", + "auth_and_oauth.unknown_origin_handoff": "Термін дії цього перенесення входу минув, або його вже використано.", "auth_and_registration.password_is_too_common": "Твій пароль занадто поширений. Обери менш передбачуваний пароль.", "auth_and_registration.password_not_set": "Пароль не встановлено.", "bots.bot_discriminator_cannot_be_changed": "Дискримінатор бота не можна змінити.", diff --git a/packages/errors/src/i18n/locales/vi.ts b/packages/errors/src/i18n/locales/vi.ts index ca6ba323a..54c6193ab 100644 --- a/packages/errors/src/i18n/locales/vi.ts +++ b/packages/errors/src/i18n/locales/vi.ts @@ -101,6 +101,7 @@ const ERROR_I18N_VI_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "Khóa API quản trị viên không hợp lệ.", "auth_and_oauth.missing_authorization": "Header ủy quyền bị thiếu hoặc không hợp lệ.", "auth_and_oauth.must_start_session_before_sending_messages": "Bạn phải bắt đầu một phiên trước khi gửi tin nhắn.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Lần chuyển đăng nhập này không khớp với lần bạn đã bắt đầu.", "auth_and_oauth.registration_closed": "Đăng ký đã bị đóng trên phiên bản này.", "auth_and_oauth.registration_pending_approval": "Đăng ký này đang chờ quản trị viên phê duyệt.", "auth_and_oauth.registration_rejected": "Yêu cầu đăng ký này đã bị từ chối.", @@ -113,6 +114,7 @@ const ERROR_I18N_VI_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Chúng tôi không thể cấp phát mã phân biệt cho tài khoản SSO của bạn. Vui lòng thử lại.", "auth_and_oauth.sudo_mode_required": "Yêu cầu chế độ sudo.", "auth_and_oauth.unknown_application": "Ứng dụng không xác định.", + "auth_and_oauth.unknown_origin_handoff": "Lần chuyển đăng nhập này đã hết hạn hoặc đã được sử dụng.", "auth_and_registration.password_is_too_common": "Mật khẩu của bạn quá phổ biến. Vui lòng chọn mật khẩu khó đoán hơn.", "auth_and_registration.password_not_set": "Mật khẩu chưa được đặt.", "bots.bot_discriminator_cannot_be_changed": "Không thể thay đổi mã phân biệt của bot.", diff --git a/packages/errors/src/i18n/locales/zh-CN.ts b/packages/errors/src/i18n/locales/zh-CN.ts index 8ba0bc2a5..903a298f1 100644 --- a/packages/errors/src/i18n/locales/zh-CN.ts +++ b/packages/errors/src/i18n/locales/zh-CN.ts @@ -101,6 +101,7 @@ const ERROR_I18N_ZH_CN_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "无效的管理员 API 密钥。", "auth_and_oauth.missing_authorization": "缺少或无效的授权请求头。", "auth_and_oauth.must_start_session_before_sending_messages": "你必须先开始会话才能发送消息。", + "auth_and_oauth.origin_handoff_nonce_invalid": "此登录转移与你发起的不一致。", "auth_and_oauth.registration_closed": "此实例的注册已关闭。", "auth_and_oauth.registration_pending_approval": "此注册正在等待管理员批准。", "auth_and_oauth.registration_rejected": "此注册请求已被拒绝。", @@ -113,6 +114,7 @@ const ERROR_I18N_ZH_CN_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "无法为你的 SSO 账号分配识别码。请重试。", "auth_and_oauth.sudo_mode_required": "需要 Sudo 模式。", "auth_and_oauth.unknown_application": "未知应用。", + "auth_and_oauth.unknown_origin_handoff": "此登录转移已过期或已被使用。", "auth_and_registration.password_is_too_common": "你的密码太常见了。请换一个更难猜的密码。", "auth_and_registration.password_not_set": "密码未设置。", "bots.bot_discriminator_cannot_be_changed": "机器人识别码无法更改。", diff --git a/packages/errors/src/i18n/locales/zh-TW.ts b/packages/errors/src/i18n/locales/zh-TW.ts index ac60879c1..2be3d4650 100644 --- a/packages/errors/src/i18n/locales/zh-TW.ts +++ b/packages/errors/src/i18n/locales/zh-TW.ts @@ -101,6 +101,7 @@ const ERROR_I18N_ZH_TW_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.invalid_token_admin_api_key": "無效的管理 API 金鑰。", "auth_and_oauth.missing_authorization": "授權標頭遺失或無效。", "auth_and_oauth.must_start_session_before_sending_messages": "你必須先開始工作階段才能傳送訊息。", + "auth_and_oauth.origin_handoff_nonce_invalid": "此登入轉移與你發起的不一致。", "auth_and_oauth.registration_closed": "此實例已關閉註冊。", "auth_and_oauth.registration_pending_approval": "此註冊正在等待管理員核准。", "auth_and_oauth.registration_rejected": "此註冊要求已被拒絕。", @@ -113,6 +114,7 @@ const ERROR_I18N_ZH_TW_MESSAGES = defineErrorI18nLocaleMessages({ "auth_and_oauth.sso_unable_to_allocate_discriminator": "無法為你的 SSO 帳號分配識別碼。請再試一次。", "auth_and_oauth.sudo_mode_required": "需要 Sudo 模式。", "auth_and_oauth.unknown_application": "不明應用程式。", + "auth_and_oauth.unknown_origin_handoff": "此登入轉移已過期或已被使用。", "auth_and_registration.password_is_too_common": "你的密碼太常見。請選擇較難預測的密碼。", "auth_and_registration.password_not_set": "密碼尚未設定。", "bots.bot_discriminator_cannot_be_changed": "機器人識別碼無法變更。", diff --git a/packages/errors/src/i18n/weblate/locales/ar.json b/packages/errors/src/i18n/weblate/locales/ar.json index ded06e0cf..1c0afacaa 100644 --- a/packages/errors/src/i18n/weblate/locales/ar.json +++ b/packages/errors/src/i18n/weblate/locales/ar.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "مفتاح API الإداري غير صالح.", "auth_and_oauth.missing_authorization": "رأس التفويض مفقود أو غير صالح.", "auth_and_oauth.must_start_session_before_sending_messages": "يجب عليك بدء جلسة قبل إرسال الرسائل.", + "auth_and_oauth.origin_handoff_nonce_invalid": "عملية نقل تسجيل الدخول هذه لا تطابق العملية التي بدأتها.", "auth_and_oauth.registration_closed": "التسجيل مغلق في هذا المثيل.", "auth_and_oauth.registration_pending_approval": "هذا التسجيل في انتظار موافقة المسؤول.", "auth_and_oauth.registration_rejected": "تم رفض طلب التسجيل هذا.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "لم نتمكن من تخصيص مميّز لحساب SSO الخاص بك. حاول مجددًا.", "auth_and_oauth.sudo_mode_required": "وضع Sudo مطلوب.", "auth_and_oauth.unknown_application": "تطبيق غير معروف.", + "auth_and_oauth.unknown_origin_handoff": "انتهت صلاحية عملية نقل تسجيل الدخول هذه أو تم استخدامها بالفعل.", "auth_and_registration.password_is_too_common": "كلمة مرورك شائعة جدًا. اختر كلمة مرور يصعب تخمينها.", "auth_and_registration.password_not_set": "لم تُعيّن كلمة المرور.", "bots.bot_discriminator_cannot_be_changed": "لا يمكن تغيير مميّز البوت.", diff --git a/packages/errors/src/i18n/weblate/locales/bg.json b/packages/errors/src/i18n/weblate/locales/bg.json index 74924fcd6..9ed1809a5 100644 --- a/packages/errors/src/i18n/weblate/locales/bg.json +++ b/packages/errors/src/i18n/weblate/locales/bg.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Невалиден админ API ключ.", "auth_and_oauth.missing_authorization": "Липсваща или невалидна заглавка за оторизация.", "auth_and_oauth.must_start_session_before_sending_messages": "Трябва да започнеш сесия, преди да изпращаш съобщения.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Това прехвърляне на вписването не съвпада с това, което сте започнали.", "auth_and_oauth.registration_closed": "Регистрацията е затворена на тази инстанция.", "auth_and_oauth.registration_pending_approval": "Тази регистрация чака одобрение от администратор.", "auth_and_oauth.registration_rejected": "Тази заявка за регистрация беше отхвърлена.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Не успяхме да присвоим дискриминатор за твоя SSO акаунт. Опитай отново.", "auth_and_oauth.sudo_mode_required": "Изисква се sudo режим.", "auth_and_oauth.unknown_application": "Неизвестно приложение.", + "auth_and_oauth.unknown_origin_handoff": "Това прехвърляне на вписването е изтекло или вече е използвано.", "auth_and_registration.password_is_too_common": "Паролата ти е твърде често срещана. Избери по-трудна за отгатване парола.", "auth_and_registration.password_not_set": "Паролата не е зададена.", "bots.bot_discriminator_cannot_be_changed": "Дискриминаторът на бота не може да се променя.", diff --git a/packages/errors/src/i18n/weblate/locales/cs.json b/packages/errors/src/i18n/weblate/locales/cs.json index d56e7871d..5c4f10386 100644 --- a/packages/errors/src/i18n/weblate/locales/cs.json +++ b/packages/errors/src/i18n/weblate/locales/cs.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Neplatný API klíč administrátora.", "auth_and_oauth.missing_authorization": "Chybí nebo je neplatná autorizační hlavička.", "auth_and_oauth.must_start_session_before_sending_messages": "Před odesíláním zpráv musíte zahájit relaci.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Tento přenos přihlášení neodpovídá tomu, který jste zahájili.", "auth_and_oauth.registration_closed": "Registrace na této instanci je uzavřena.", "auth_and_oauth.registration_pending_approval": "Tato registrace čeká na schválení administrátorem.", "auth_and_oauth.registration_rejected": "Tato žádost o registraci byla zamítnuta.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nepodařilo se přidělit čtyřmístný identifikátor pro váš účet SSO. Zkuste to znovu.", "auth_and_oauth.sudo_mode_required": "Je vyžadován režim sudo.", "auth_and_oauth.unknown_application": "Neznámá aplikace.", + "auth_and_oauth.unknown_origin_handoff": "Platnost tohoto přenosu přihlášení vypršela nebo už byl použit.", "auth_and_registration.password_is_too_common": "Vaše heslo je příliš běžné. Zvolte si hůře odhadnutelné heslo.", "auth_and_registration.password_not_set": "Heslo není nastavené.", "bots.bot_discriminator_cannot_be_changed": "Čtyřmístný identifikátor bota nelze změnit.", diff --git a/packages/errors/src/i18n/weblate/locales/da.json b/packages/errors/src/i18n/weblate/locales/da.json index 19f1fe8b5..609bcc9b7 100644 --- a/packages/errors/src/i18n/weblate/locales/da.json +++ b/packages/errors/src/i18n/weblate/locales/da.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Ugyldig administrator-API-nøgle.", "auth_and_oauth.missing_authorization": "Manglende eller ugyldig godkendelsesheader.", "auth_and_oauth.must_start_session_before_sending_messages": "Du skal starte en session, før du sender beskeder.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Denne overførsel af login matcher ikke den, du startede.", "auth_and_oauth.registration_closed": "Tilmelding er lukket på denne instans.", "auth_and_oauth.registration_pending_approval": "Denne tilmelding afventer godkendelse fra administratoren.", "auth_and_oauth.registration_rejected": "Denne anmodning om tilmelding blev afvist.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Vi kunne ikke tildele et tag til din SSO-konto. Prøv igen.", "auth_and_oauth.sudo_mode_required": "Sudo-tilstand er påkrævet.", "auth_and_oauth.unknown_application": "Ukendt applikation.", + "auth_and_oauth.unknown_origin_handoff": "Denne overførsel af login er udløbet eller allerede brugt.", "auth_and_registration.password_is_too_common": "Din adgangskode er for almindelig. Vælg en adgangskode, der er sværere at gætte.", "auth_and_registration.password_not_set": "Der er ikke angivet nogen adgangskode.", "bots.bot_discriminator_cannot_be_changed": "En bots tag kan ikke ændres.", diff --git a/packages/errors/src/i18n/weblate/locales/de.json b/packages/errors/src/i18n/weblate/locales/de.json index 11526551e..380ac08da 100644 --- a/packages/errors/src/i18n/weblate/locales/de.json +++ b/packages/errors/src/i18n/weblate/locales/de.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Ungültiger Admin-API-Schlüssel.", "auth_and_oauth.missing_authorization": "Fehlender oder ungültiger Autorisierungsheader.", "auth_and_oauth.must_start_session_before_sending_messages": "Du musst eine Sitzung starten, bevor du Nachrichten senden kannst.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Diese Anmeldeübertragung stimmt nicht mit der von dir gestarteten überein.", "auth_and_oauth.registration_closed": "Die Registrierung ist auf dieser Instanz geschlossen.", "auth_and_oauth.registration_pending_approval": "Diese Registrierung wartet auf die Genehmigung durch einen Administrator.", "auth_and_oauth.registration_rejected": "Diese Registrierungsanfrage wurde abgelehnt.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Wir konnten keinen Discriminator für deinen SSO-Account vergeben. Versuche es noch einmal.", "auth_and_oauth.sudo_mode_required": "Sudo-Modus ist erforderlich.", "auth_and_oauth.unknown_application": "Unbekannte Anwendung.", + "auth_and_oauth.unknown_origin_handoff": "Diese Anmeldeübertragung ist abgelaufen oder wurde bereits verwendet.", "auth_and_registration.password_is_too_common": "Dein Passwort wird zu häufig verwendet. Wähle bitte ein Passwort, das weniger leicht zu erraten ist.", "auth_and_registration.password_not_set": "Das Passwort ist nicht festgelegt.", "bots.bot_discriminator_cannot_be_changed": "Der Bot-Discriminator kann nicht geändert werden.", diff --git a/packages/errors/src/i18n/weblate/locales/el.json b/packages/errors/src/i18n/weblate/locales/el.json index ab52b2978..bf8fa8440 100644 --- a/packages/errors/src/i18n/weblate/locales/el.json +++ b/packages/errors/src/i18n/weblate/locales/el.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Το κλειδί API διαχειριστή είναι μη έγκυρο.", "auth_and_oauth.missing_authorization": "Λείπει ή είναι άκυρη η κεφαλίδα εξουσιοδότησης.", "auth_and_oauth.must_start_session_before_sending_messages": "Πρέπει να ξεκινήσεις συνεδρία πριν στείλεις μηνύματα.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Αυτή η μεταφορά σύνδεσης δεν αντιστοιχεί σε αυτήν που ξεκινήσατε.", "auth_and_oauth.registration_closed": "Η εγγραφή είναι κλειστή σε αυτήν την εγκατάσταση.", "auth_and_oauth.registration_pending_approval": "Αυτή η εγγραφή περιμένει έγκριση από τον διαχειριστή.", "auth_and_oauth.registration_rejected": "Αυτό το αίτημα εγγραφής απορρίφθηκε.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Δεν μπορέσαμε να εκχωρήσουμε διακριτικό για τον λογαριασμό σου SSO. Δοκίμασε ξανά.", "auth_and_oauth.sudo_mode_required": "Απαιτείται η λειτουργία sudo.", "auth_and_oauth.unknown_application": "Άγνωστη εφαρμογή.", + "auth_and_oauth.unknown_origin_handoff": "Αυτή η μεταφορά σύνδεσης έχει λήξει ή έχει ήδη χρησιμοποιηθεί.", "auth_and_registration.password_is_too_common": "Ο κωδικός πρόσβασής σου είναι πολύ κοινός. Διάλεξε έναν λιγότερο προβλέψιμο κωδικό πρόσβασης.", "auth_and_registration.password_not_set": "Ο κωδικός πρόσβασης δεν έχει οριστεί.", "bots.bot_discriminator_cannot_be_changed": "Το διακριτικό του bot δεν μπορεί να αλλάξει.", diff --git a/packages/errors/src/i18n/weblate/locales/en-GB.json b/packages/errors/src/i18n/weblate/locales/en-GB.json index 64613680c..fae359958 100644 --- a/packages/errors/src/i18n/weblate/locales/en-GB.json +++ b/packages/errors/src/i18n/weblate/locales/en-GB.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Invalid admin API key.", "auth_and_oauth.missing_authorization": "Missing or invalid authorisation header.", "auth_and_oauth.must_start_session_before_sending_messages": "You must start a session before sending messages.", + "auth_and_oauth.origin_handoff_nonce_invalid": "This sign-in transfer doesn't match the one you started.", "auth_and_oauth.registration_closed": "Registration is closed on this instance.", "auth_and_oauth.registration_pending_approval": "This registration is waiting for admin approval.", "auth_and_oauth.registration_rejected": "This registration request was rejected.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "We couldn't allocate a discriminator for your SSO account. Please try again.", "auth_and_oauth.sudo_mode_required": "Sudo mode is required.", "auth_and_oauth.unknown_application": "Unknown application.", + "auth_and_oauth.unknown_origin_handoff": "This sign-in transfer has expired or was already used.", "auth_and_registration.password_is_too_common": "Your password is too common. Please choose a less predictable password.", "auth_and_registration.password_not_set": "Password isn't set.", "bots.bot_discriminator_cannot_be_changed": "Bot discriminator can't be changed.", diff --git a/packages/errors/src/i18n/weblate/locales/es-419.json b/packages/errors/src/i18n/weblate/locales/es-419.json index e36049dfd..d7240ca90 100644 --- a/packages/errors/src/i18n/weblate/locales/es-419.json +++ b/packages/errors/src/i18n/weblate/locales/es-419.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Clave de API de administrador no válida.", "auth_and_oauth.missing_authorization": "Falta el encabezado de autorización o no es válido.", "auth_and_oauth.must_start_session_before_sending_messages": "Debes iniciar una sesión antes de enviar mensajes.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Esta transferencia de inicio de sesión no coincide con la que iniciaste.", "auth_and_oauth.registration_closed": "El registro está cerrado en esta instancia.", "auth_and_oauth.registration_pending_approval": "Este registro está esperando la aprobación del administrador.", "auth_and_oauth.registration_rejected": "Esta solicitud de registro fue rechazada.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "No pudimos asignar un discriminador para tu cuenta de SSO. Inténtalo de nuevo.", "auth_and_oauth.sudo_mode_required": "Se requiere el modo sudo.", "auth_and_oauth.unknown_application": "Aplicación desconocida.", + "auth_and_oauth.unknown_origin_handoff": "Esta transferencia de inicio de sesión caducó o ya se usó.", "auth_and_registration.password_is_too_common": "Tu contraseña es demasiado común. Elige una contraseña menos predecible.", "auth_and_registration.password_not_set": "La contraseña no está configurada.", "bots.bot_discriminator_cannot_be_changed": "El discriminador del bot no se puede cambiar.", diff --git a/packages/errors/src/i18n/weblate/locales/es-ES.json b/packages/errors/src/i18n/weblate/locales/es-ES.json index 740f7114b..7792ad82a 100644 --- a/packages/errors/src/i18n/weblate/locales/es-ES.json +++ b/packages/errors/src/i18n/weblate/locales/es-ES.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Clave de API de administrador no válida.", "auth_and_oauth.missing_authorization": "Falta la cabecera de autorización o no es válida.", "auth_and_oauth.must_start_session_before_sending_messages": "Debes iniciar sesión antes de enviar mensajes.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Esta transferencia de inicio de sesión no coincide con la que iniciaste.", "auth_and_oauth.registration_closed": "El registro está cerrado en esta instancia.", "auth_and_oauth.registration_pending_approval": "Este registro está pendiente de aprobación por parte del administrador.", "auth_and_oauth.registration_rejected": "Esta solicitud de registro ha sido rechazada.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "No se pudo asignar un discriminador para tu cuenta SSO. Inténtalo de nuevo.", "auth_and_oauth.sudo_mode_required": "Se requiere el modo sudo.", "auth_and_oauth.unknown_application": "Aplicación desconocida.", + "auth_and_oauth.unknown_origin_handoff": "Esta transferencia de inicio de sesión ha caducado o ya se ha usado.", "auth_and_registration.password_is_too_common": "Tu contraseña es demasiado común. Elige una contraseña menos predecible.", "auth_and_registration.password_not_set": "La contraseña no está establecida.", "bots.bot_discriminator_cannot_be_changed": "El discriminador del bot no se puede cambiar.", diff --git a/packages/errors/src/i18n/weblate/locales/fi.json b/packages/errors/src/i18n/weblate/locales/fi.json index 08f3f83ed..884af858a 100644 --- a/packages/errors/src/i18n/weblate/locales/fi.json +++ b/packages/errors/src/i18n/weblate/locales/fi.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Virheellinen ylläpidon API-avain.", "auth_and_oauth.missing_authorization": "Valtuutusotsake puuttuu tai on virheellinen.", "auth_and_oauth.must_start_session_before_sending_messages": "Sinun on aloitettava istunto ennen viestien lähettämistä.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Tämä kirjautumisen siirto ei vastaa aloittamaasi siirtoa.", "auth_and_oauth.registration_closed": "Rekisteröinti on suljettu tällä instanssilla.", "auth_and_oauth.registration_pending_approval": "Tämä rekisteröinti odottaa järjestelmänvalvojan hyväksyntää.", "auth_and_oauth.registration_rejected": "Tämä rekisteröintipyyntö hylättiin.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "SSO-tilillesi ei voitu varata nelinumeroista tunnistetta. Yritä uudelleen.", "auth_and_oauth.sudo_mode_required": "Sudo-tila vaaditaan.", "auth_and_oauth.unknown_application": "Tuntematon sovellus.", + "auth_and_oauth.unknown_origin_handoff": "Tämä kirjautumisen siirto on vanhentunut tai jo käytetty.", "auth_and_registration.password_is_too_common": "Salasanasi on liian yleinen. Valitse vaikeammin arvattava salasana.", "auth_and_registration.password_not_set": "Salasanaa ei ole asetettu.", "bots.bot_discriminator_cannot_be_changed": "Botin nelinumeroista tunnistetta ei voi muuttaa.", diff --git a/packages/errors/src/i18n/weblate/locales/fr.json b/packages/errors/src/i18n/weblate/locales/fr.json index 6ecc7ff09..20c81d37e 100644 --- a/packages/errors/src/i18n/weblate/locales/fr.json +++ b/packages/errors/src/i18n/weblate/locales/fr.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Clé API administrateur non valide.", "auth_and_oauth.missing_authorization": "En-tête d'autorisation manquant ou non valide.", "auth_and_oauth.must_start_session_before_sending_messages": "Vous devez démarrer une session avant d’envoyer des messages.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Ce transfert de connexion ne correspond pas à celui que tu as lancé.", "auth_and_oauth.registration_closed": "L'inscription est fermée sur cette instance.", "auth_and_oauth.registration_pending_approval": "Cette inscription est en attente d'approbation par un administrateur.", "auth_and_oauth.registration_rejected": "Cette demande d'inscription a été rejetée.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Impossible d’attribuer un discriminateur à votre compte SSO. Réessayez.", "auth_and_oauth.sudo_mode_required": "Le mode Sudo est requis.", "auth_and_oauth.unknown_application": "Application inconnue.", + "auth_and_oauth.unknown_origin_handoff": "Ce transfert de connexion a expiré ou a déjà été utilisé.", "auth_and_registration.password_is_too_common": "Votre mot de passe est trop courant. Choisissez-en un moins prévisible.", "auth_and_registration.password_not_set": "Le mot de passe n'est pas défini.", "bots.bot_discriminator_cannot_be_changed": "Le discriminateur du bot ne peut pas être modifié.", diff --git a/packages/errors/src/i18n/weblate/locales/he.json b/packages/errors/src/i18n/weblate/locales/he.json index 2c7679478..bffa2e6d8 100644 --- a/packages/errors/src/i18n/weblate/locales/he.json +++ b/packages/errors/src/i18n/weblate/locales/he.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "מפתח API של אדמין שגוי.", "auth_and_oauth.missing_authorization": "כותרת ההרשאה חסרה או שגויה.", "auth_and_oauth.must_start_session_before_sending_messages": "יש להתחיל סשן לפני שליחת הודעות.", + "auth_and_oauth.origin_handoff_nonce_invalid": "העברת ההתחברות הזו אינה תואמת לזו שהתחלת.", "auth_and_oauth.registration_closed": "ההרשמה סגורה במופע זה.", "auth_and_oauth.registration_pending_approval": "הרשמה זו ממתינה לאישור מנהל.", "auth_and_oauth.registration_rejected": "בקשת הרשמה זו נדחתה.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "לא הצלחנו להקצות מזהה לחשבון ה-SSO שלך. ניתן לנסות שוב.", "auth_and_oauth.sudo_mode_required": "נדרש מצב sudo.", "auth_and_oauth.unknown_application": "אפליקציה לא ידועה.", + "auth_and_oauth.unknown_origin_handoff": "פג תוקפה של העברת ההתחברות הזו או שכבר נעשה בה שימוש.", "auth_and_registration.password_is_too_common": "הסיסמה שלך נפוצה מדי. מומלץ לבחור סיסמה פחות צפויה.", "auth_and_registration.password_not_set": "סיסמה לא הוגדרה.", "bots.bot_discriminator_cannot_be_changed": "לא ניתן לשנות את המזהה של הבוט.", diff --git a/packages/errors/src/i18n/weblate/locales/hi.json b/packages/errors/src/i18n/weblate/locales/hi.json index ac542b84b..cfb05e003 100644 --- a/packages/errors/src/i18n/weblate/locales/hi.json +++ b/packages/errors/src/i18n/weblate/locales/hi.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "एडमिन API कुंजी अमान्य है।", "auth_and_oauth.missing_authorization": "ऑथराइज़ेशन हेडर गायब या अमान्य है।", "auth_and_oauth.must_start_session_before_sending_messages": "मैसेज भेजने से पहले आपको एक सेशन शुरू करना होगा।", + "auth_and_oauth.origin_handoff_nonce_invalid": "यह साइन-इन ट्रांसफ़र आपके द्वारा शुरू किए गए ट्रांसफ़र से मेल नहीं खाता।", "auth_and_oauth.registration_closed": "इस इंस्टेंस पर रजिस्ट्रेशन बंद है।", "auth_and_oauth.registration_pending_approval": "यह रजिस्ट्रेशन एडमिन अप्रूवल का इंतज़ार कर रहा है।", "auth_and_oauth.registration_rejected": "यह रजिस्ट्रेशन रिक्वेस्ट रिजेक्ट कर दी गई है।", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "हम आपके SSO अकाउंट के लिए डिस्क्रिमिनेटर अलॉट नहीं कर सके। फिर से कोशिश करें।", "auth_and_oauth.sudo_mode_required": "सुडो मोड ज़रूरी है।", "auth_and_oauth.unknown_application": "अज्ञात एप्लीकेशन।", + "auth_and_oauth.unknown_origin_handoff": "यह साइन-इन ट्रांसफ़र समाप्त हो गया है या पहले ही उपयोग किया जा चुका है।", "auth_and_registration.password_is_too_common": "आपका पासवर्ड बहुत कॉमन है। कृपया ऐसा पासवर्ड चुनें जिसका अंदाज़ा लगाना मुश्किल हो।", "auth_and_registration.password_not_set": "पासवर्ड सेट नहीं है।", "bots.bot_discriminator_cannot_be_changed": "बॉट डिस्क्रिमिनेटर को बदला नहीं जा सकता।", diff --git a/packages/errors/src/i18n/weblate/locales/hr.json b/packages/errors/src/i18n/weblate/locales/hr.json index eb991415e..c30754430 100644 --- a/packages/errors/src/i18n/weblate/locales/hr.json +++ b/packages/errors/src/i18n/weblate/locales/hr.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Nevažeći administratorski API ključ.", "auth_and_oauth.missing_authorization": "Nedostaje ili je nevažeće autorizacijsko zaglavlje.", "auth_and_oauth.must_start_session_before_sending_messages": "Moraš započeti sesiju prije slanja poruka.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Ovaj prijenos prijave ne odgovara onome koji ste započeli.", "auth_and_oauth.registration_closed": "Registracija je zatvorena na ovoj instanci.", "auth_and_oauth.registration_pending_approval": "Ova registracija čeka odobrenje administratora.", "auth_and_oauth.registration_rejected": "Ovaj zahtjev za registraciju je odbijen.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nismo mogli dodijeliti diskriminator tvom SSO računu. Pokušaj ponovno.", "auth_and_oauth.sudo_mode_required": "Potreban je sudo način rada.", "auth_and_oauth.unknown_application": "Nepoznata aplikacija.", + "auth_and_oauth.unknown_origin_handoff": "Ovaj prijenos prijave je istekao ili je već iskorišten.", "auth_and_registration.password_is_too_common": "Tvoja je lozinka previše uobičajena. Odaberi manje predvidljivu lozinku.", "auth_and_registration.password_not_set": "Lozinka nije postavljena.", "bots.bot_discriminator_cannot_be_changed": "Diskriminator bota ne može se promijeniti.", diff --git a/packages/errors/src/i18n/weblate/locales/hu.json b/packages/errors/src/i18n/weblate/locales/hu.json index be6276f65..717145882 100644 --- a/packages/errors/src/i18n/weblate/locales/hu.json +++ b/packages/errors/src/i18n/weblate/locales/hu.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Érvénytelen adminisztrátori API-kulcs.", "auth_and_oauth.missing_authorization": "Hiányzó vagy érvénytelen hitelesítési fejléc.", "auth_and_oauth.must_start_session_before_sending_messages": "Üzenetek küldéséhez előbb munkamenetet kell indítanod.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Ez a bejelentkezés-átvitel nem egyezik azzal, amelyet elindítottál.", "auth_and_oauth.registration_closed": "Ezen a példányon a regisztráció le van zárva.", "auth_and_oauth.registration_pending_approval": "Ez a regisztráció rendszergazdai jóváhagyásra vár.", "auth_and_oauth.registration_rejected": "Ez a regisztrációs kérelem el lett utasítva.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nem sikerült számazonosítót kiosztani az SSO-fiókodhoz. Próbáld újra.", "auth_and_oauth.sudo_mode_required": "Sudo mód szükséges.", "auth_and_oauth.unknown_application": "Ismeretlen alkalmazás.", + "auth_and_oauth.unknown_origin_handoff": "Ez a bejelentkezés-átvitel lejárt, vagy már felhasználták.", "auth_and_registration.password_is_too_common": "A jelszavad túl gyakori. Válassz nehezebben kitalálható jelszót.", "auth_and_registration.password_not_set": "Nincs beállítva jelszó.", "bots.bot_discriminator_cannot_be_changed": "A bot számazonosítója nem módosítható.", diff --git a/packages/errors/src/i18n/weblate/locales/id.json b/packages/errors/src/i18n/weblate/locales/id.json index 184e74807..cd3a519f8 100644 --- a/packages/errors/src/i18n/weblate/locales/id.json +++ b/packages/errors/src/i18n/weblate/locales/id.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Kunci API admin tidak valid.", "auth_and_oauth.missing_authorization": "Header otorisasi hilang atau tidak valid.", "auth_and_oauth.must_start_session_before_sending_messages": "Kamu harus memulai sesi sebelum mengirim pesan.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Transfer masuk ini tidak cocok dengan yang kamu mulai.", "auth_and_oauth.registration_closed": "Pendaftaran ditutup di instance ini.", "auth_and_oauth.registration_pending_approval": "Pendaftaran ini menunggu persetujuan admin.", "auth_and_oauth.registration_rejected": "Permintaan pendaftaran ini ditolak.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Tidak bisa mengalokasikan diskriminator untuk akun SSO kamu. Silakan coba lagi.", "auth_and_oauth.sudo_mode_required": "Mode sudo diperlukan.", "auth_and_oauth.unknown_application": "Aplikasi tidak dikenal.", + "auth_and_oauth.unknown_origin_handoff": "Transfer masuk ini sudah kedaluwarsa atau sudah digunakan.", "auth_and_registration.password_is_too_common": "Kata sandimu terlalu umum. Pilih kata sandi yang lebih sulit ditebak.", "auth_and_registration.password_not_set": "Kata sandi belum diatur.", "bots.bot_discriminator_cannot_be_changed": "Diskriminator bot tidak dapat diubah.", diff --git a/packages/errors/src/i18n/weblate/locales/it.json b/packages/errors/src/i18n/weblate/locales/it.json index 6a3e5eabd..a1b28be68 100644 --- a/packages/errors/src/i18n/weblate/locales/it.json +++ b/packages/errors/src/i18n/weblate/locales/it.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Chiave API amministratore non valida.", "auth_and_oauth.missing_authorization": "Header di autorizzazione mancante o non valido.", "auth_and_oauth.must_start_session_before_sending_messages": "Devi avviare una sessione prima di inviare messaggi.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Questo trasferimento di accesso non corrisponde a quello che hai avviato.", "auth_and_oauth.registration_closed": "La registrazione è chiusa su questa istanza.", "auth_and_oauth.registration_pending_approval": "Questa registrazione è in attesa dell'approvazione dell'amministratore.", "auth_and_oauth.registration_rejected": "Questa richiesta di registrazione è stata rifiutata.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Non è stato possibile assegnare un discriminatore al tuo account SSO. Riprova.", "auth_and_oauth.sudo_mode_required": "La modalità Sudo è richiesta.", "auth_and_oauth.unknown_application": "Applicazione sconosciuta.", + "auth_and_oauth.unknown_origin_handoff": "Questo trasferimento di accesso è scaduto o è già stato usato.", "auth_and_registration.password_is_too_common": "La tua password è troppo comune. Scegli una password meno prevedibile.", "auth_and_registration.password_not_set": "La password non è impostata.", "bots.bot_discriminator_cannot_be_changed": "Il discriminatore del bot non può essere modificato.", diff --git a/packages/errors/src/i18n/weblate/locales/ja.json b/packages/errors/src/i18n/weblate/locales/ja.json index 11671008e..9b5c7dd21 100644 --- a/packages/errors/src/i18n/weblate/locales/ja.json +++ b/packages/errors/src/i18n/weblate/locales/ja.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "管理者APIキーが無効です。", "auth_and_oauth.missing_authorization": "認証ヘッダーが見つからないか、無効です。", "auth_and_oauth.must_start_session_before_sending_messages": "メッセージを送信する前にセッションを開始してください。", + "auth_and_oauth.origin_handoff_nonce_invalid": "このサインインの引き継ぎは、開始したものと一致しません。", "auth_and_oauth.registration_closed": "このインスタンスでは新規登録を受け付けていません。", "auth_and_oauth.registration_pending_approval": "この登録は管理者による承認待ちです。", "auth_and_oauth.registration_rejected": "この登録リクエストは拒否されました。", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "SSOアカウントの識別子を割り当てられませんでした。もう一度お試しください。", "auth_and_oauth.sudo_mode_required": "Sudoモードが必要です。", "auth_and_oauth.unknown_application": "不明なアプリケーションです。", + "auth_and_oauth.unknown_origin_handoff": "このサインインの引き継ぎは期限切れか、すでに使用されています。", "auth_and_registration.password_is_too_common": "パスワードが一般的すぎます。より推測されにくいパスワードを設定してください。", "auth_and_registration.password_not_set": "パスワードが未設定です。", "bots.bot_discriminator_cannot_be_changed": "ボットの識別子は変更できません。", diff --git a/packages/errors/src/i18n/weblate/locales/ko.json b/packages/errors/src/i18n/weblate/locales/ko.json index 5dfaa057d..180b57213 100644 --- a/packages/errors/src/i18n/weblate/locales/ko.json +++ b/packages/errors/src/i18n/weblate/locales/ko.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "잘못된 관리자 API 키예요.", "auth_and_oauth.missing_authorization": "인증 헤더가 없거나 유효하지 않아요.", "auth_and_oauth.must_start_session_before_sending_messages": "메시지를 보내기 전에 세션을 시작해야 해요.", + "auth_and_oauth.origin_handoff_nonce_invalid": "이 로그인 이전 요청이 시작한 요청과 일치하지 않습니다.", "auth_and_oauth.registration_closed": "이 인스턴스에서는 가입을 받고 있지 않아요.", "auth_and_oauth.registration_pending_approval": "이 가입 신청은 관리자 승인을 기다리고 있어요.", "auth_and_oauth.registration_rejected": "이 가입 요청은 거부되었어요.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "SSO 계정에 디스크리미네이터를 할당하지 못했어요. 다시 시도해 주세요.", "auth_and_oauth.sudo_mode_required": "Sudo 모드가 필요해요.", "auth_and_oauth.unknown_application": "알 수 없는 애플리케이션이에요.", + "auth_and_oauth.unknown_origin_handoff": "이 로그인 이전 요청이 만료되었거나 이미 사용되었습니다.", "auth_and_registration.password_is_too_common": "비밀번호가 너무 흔해요. 추측하기 어려운 비밀번호를 사용해 주세요.", "auth_and_registration.password_not_set": "비밀번호가 설정되지 않았어요.", "bots.bot_discriminator_cannot_be_changed": "봇 디스크리미네이터를 변경할 수 없어요.", diff --git a/packages/errors/src/i18n/weblate/locales/lt.json b/packages/errors/src/i18n/weblate/locales/lt.json index 13082786a..17883d48c 100644 --- a/packages/errors/src/i18n/weblate/locales/lt.json +++ b/packages/errors/src/i18n/weblate/locales/lt.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Negaliojantis administratoriaus API raktas.", "auth_and_oauth.missing_authorization": "Trūksta autorizacijos antraštės arba ji neteisinga.", "auth_and_oauth.must_start_session_before_sending_messages": "Kad galėtum siųsti žinutes, pirmiausia pradėk seansą.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Šis prisijungimo perkėlimas nesutampa su tuo, kurį pradėjote.", "auth_and_oauth.registration_closed": "Registracija šioje instancijoje yra uždaryta.", "auth_and_oauth.registration_pending_approval": "Ši registracija laukia administratoriaus patvirtinimo.", "auth_and_oauth.registration_rejected": "Šis registracijos prašymas buvo atmestas.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nepavyko priskirti diskriminatoriaus tavo SSO paskyrai. Pabandyk dar kartą.", "auth_and_oauth.sudo_mode_required": "Reikalingas sudo režimas.", "auth_and_oauth.unknown_application": "Nežinoma programa.", + "auth_and_oauth.unknown_origin_handoff": "Šio prisijungimo perkėlimo galiojimas baigėsi arba jis jau panaudotas.", "auth_and_registration.password_is_too_common": "Tavo slaptažodis per dažnai naudojamas. Pasirink sunkiau nuspėjamą slaptažodį.", "auth_and_registration.password_not_set": "Slaptažodis nenustatytas.", "bots.bot_discriminator_cannot_be_changed": "Boto diskriminatoriaus negalima pakeisti.", diff --git a/packages/errors/src/i18n/weblate/locales/nl.json b/packages/errors/src/i18n/weblate/locales/nl.json index c443212a5..64d988fd4 100644 --- a/packages/errors/src/i18n/weblate/locales/nl.json +++ b/packages/errors/src/i18n/weblate/locales/nl.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "De admin-API-sleutel is ongeldig.", "auth_and_oauth.missing_authorization": "De autorisatieheader ontbreekt of is ongeldig.", "auth_and_oauth.must_start_session_before_sending_messages": "Je moet een sessie starten voordat je berichten kunt verzenden.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Deze aanmeldoverdracht komt niet overeen met de overdracht die je hebt gestart.", "auth_and_oauth.registration_closed": "Registratie is gesloten op deze instantie.", "auth_and_oauth.registration_pending_approval": "Deze registratie wacht op goedkeuring van de beheerder.", "auth_and_oauth.registration_rejected": "Dit registratieverzoek is afgewezen.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "We konden geen discriminator toewijzen aan je SSO-account. Probeer het nog eens.", "auth_and_oauth.sudo_mode_required": "Sudo-modus is vereist.", "auth_and_oauth.unknown_application": "Onbekende app.", + "auth_and_oauth.unknown_origin_handoff": "Deze aanmeldoverdracht is verlopen of al gebruikt.", "auth_and_registration.password_is_too_common": "Je wachtwoord komt te vaak voor. Kies een minder voorspelbaar wachtwoord.", "auth_and_registration.password_not_set": "Wachtwoord is niet ingesteld.", "bots.bot_discriminator_cannot_be_changed": "De discriminator van een bot kan niet worden gewijzigd.", diff --git a/packages/errors/src/i18n/weblate/locales/no.json b/packages/errors/src/i18n/weblate/locales/no.json index 49637695f..ce224a75b 100644 --- a/packages/errors/src/i18n/weblate/locales/no.json +++ b/packages/errors/src/i18n/weblate/locales/no.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Ugyldig API-nøkkel for administrasjon.", "auth_and_oauth.missing_authorization": "Autorisasjonsheaderen mangler eller er ugyldig.", "auth_and_oauth.must_start_session_before_sending_messages": "Du må starte en økt før du sender meldinger.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Denne overføringen av innloggingen samsvarer ikke med den du startet.", "auth_and_oauth.registration_closed": "Registrering er stengt på denne instansen.", "auth_and_oauth.registration_pending_approval": "Denne registreringen venter på administratorgodkjenning.", "auth_and_oauth.registration_rejected": "Denne registreringsforespørselen ble avvist.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Vi klarte ikke å tildele en diskriminator til SSO-kontoen din. Prøv igjen.", "auth_and_oauth.sudo_mode_required": "Sudo-modus er påkrevd.", "auth_and_oauth.unknown_application": "Ukjent app.", + "auth_and_oauth.unknown_origin_handoff": "Denne overføringen av innloggingen er utløpt eller allerede brukt.", "auth_and_registration.password_is_too_common": "Passordet ditt er for vanlig. Velg et passord som er vanskeligere å gjette.", "auth_and_registration.password_not_set": "Det er ikke angitt noe passord.", "bots.bot_discriminator_cannot_be_changed": "Botens diskriminator kan ikke endres.", diff --git a/packages/errors/src/i18n/weblate/locales/pl.json b/packages/errors/src/i18n/weblate/locales/pl.json index bf4948596..b1f1419c8 100644 --- a/packages/errors/src/i18n/weblate/locales/pl.json +++ b/packages/errors/src/i18n/weblate/locales/pl.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Nieprawidłowy klucz API administratora.", "auth_and_oauth.missing_authorization": "Brakuje nagłówka autoryzacji lub jest on nieprawidłowy.", "auth_and_oauth.must_start_session_before_sending_messages": "Musisz rozpocząć sesję, zanim wyślesz wiadomości.", + "auth_and_oauth.origin_handoff_nonce_invalid": "To przeniesienie logowania nie pasuje do tego, które zostało rozpoczęte.", "auth_and_oauth.registration_closed": "Rejestracja na tej instancji jest zamknięta.", "auth_and_oauth.registration_pending_approval": "Ta rejestracja czeka na zatwierdzenie przez administratora.", "auth_and_oauth.registration_rejected": "Ten wniosek o rejestrację został odrzucony.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nie udało się przydzielić dyskryminatora dla Twojego konta SSO. Spróbuj ponownie.", "auth_and_oauth.sudo_mode_required": "Wymagany tryb sudo.", "auth_and_oauth.unknown_application": "Nieznana aplikacja.", + "auth_and_oauth.unknown_origin_handoff": "To przeniesienie logowania wygasło lub zostało już użyte.", "auth_and_registration.password_is_too_common": "Twoje hasło jest zbyt popularne. Wybierz mniej przewidywalne hasło.", "auth_and_registration.password_not_set": "Hasło nie jest ustawione.", "bots.bot_discriminator_cannot_be_changed": "Nie można zmienić dyskryminatora bota.", diff --git a/packages/errors/src/i18n/weblate/locales/pt-BR.json b/packages/errors/src/i18n/weblate/locales/pt-BR.json index f1c71b07f..a19e6b6d8 100644 --- a/packages/errors/src/i18n/weblate/locales/pt-BR.json +++ b/packages/errors/src/i18n/weblate/locales/pt-BR.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Chave de API de administrador inválida.", "auth_and_oauth.missing_authorization": "Cabeçalho de autorização ausente ou inválido.", "auth_and_oauth.must_start_session_before_sending_messages": "Você deve iniciar uma sessão antes de enviar mensagens.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Esta transferência de login não corresponde à que você iniciou.", "auth_and_oauth.registration_closed": "O cadastro está fechado nesta instância.", "auth_and_oauth.registration_pending_approval": "Este cadastro está aguardando a aprovação de um administrador.", "auth_and_oauth.registration_rejected": "Esta solicitação de cadastro foi rejeitada.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Não foi possível alocar um discriminador para sua conta SSO. Tente novamente.", "auth_and_oauth.sudo_mode_required": "O modo sudo é obrigatório.", "auth_and_oauth.unknown_application": "Aplicativo desconhecido.", + "auth_and_oauth.unknown_origin_handoff": "Esta transferência de login expirou ou já foi usada.", "auth_and_registration.password_is_too_common": "Sua senha é muito comum. Escolha uma senha menos previsível.", "auth_and_registration.password_not_set": "A senha não foi definida.", "bots.bot_discriminator_cannot_be_changed": "O discriminador do bot não pode ser alterado.", diff --git a/packages/errors/src/i18n/weblate/locales/ro.json b/packages/errors/src/i18n/weblate/locales/ro.json index 6191a34cf..4f2762c06 100644 --- a/packages/errors/src/i18n/weblate/locales/ro.json +++ b/packages/errors/src/i18n/weblate/locales/ro.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Cheia API de administrator este invalidă.", "auth_and_oauth.missing_authorization": "Headerul de autorizare lipsește sau este invalid.", "auth_and_oauth.must_start_session_before_sending_messages": "Trebuie să începi o sesiune înainte de a trimite mesaje.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Acest transfer de autentificare nu corespunde celui pe care l-ai început.", "auth_and_oauth.registration_closed": "Înregistrarea este închisă pe această instanță.", "auth_and_oauth.registration_pending_approval": "Această înregistrare așteaptă aprobarea administratorului.", "auth_and_oauth.registration_rejected": "Această cerere de înregistrare a fost respinsă.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Nu s-a putut aloca un discriminator pentru contul tău SSO. Încearcă din nou.", "auth_and_oauth.sudo_mode_required": "Modul Sudo este necesar.", "auth_and_oauth.unknown_application": "Aplicație necunoscută.", + "auth_and_oauth.unknown_origin_handoff": "Acest transfer de autentificare a expirat sau a fost deja folosit.", "auth_and_registration.password_is_too_common": "Parola ta este prea comună. Alege o parolă mai greu de ghicit.", "auth_and_registration.password_not_set": "Parola nu este setată.", "bots.bot_discriminator_cannot_be_changed": "Discriminatorul botului nu poate fi schimbat.", diff --git a/packages/errors/src/i18n/weblate/locales/ru.json b/packages/errors/src/i18n/weblate/locales/ru.json index ba77a19f8..4a22d60ac 100644 --- a/packages/errors/src/i18n/weblate/locales/ru.json +++ b/packages/errors/src/i18n/weblate/locales/ru.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Недействительный ключ Admin API.", "auth_and_oauth.missing_authorization": "Заголовок авторизации отсутствует или недействителен.", "auth_and_oauth.must_start_session_before_sending_messages": "Перед отправкой сообщений нужно начать сессию.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Этот перенос входа не совпадает с тем, который вы начали.", "auth_and_oauth.registration_closed": "Регистрация в этом экземпляре закрыта.", "auth_and_oauth.registration_pending_approval": "Эта регистрация ожидает одобрения администратора.", "auth_and_oauth.registration_rejected": "Заявка на регистрацию отклонена.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Не удалось назначить дискриминатор для твоего аккаунта SSO. Попробуй ещё раз.", "auth_and_oauth.sudo_mode_required": "Требуется режим sudo.", "auth_and_oauth.unknown_application": "Неизвестное приложение.", + "auth_and_oauth.unknown_origin_handoff": "Срок действия этого переноса входа истёк, или он уже использован.", "auth_and_registration.password_is_too_common": "Твой пароль слишком распространённый. Придумай менее предсказуемый пароль.", "auth_and_registration.password_not_set": "Пароль не установлен.", "bots.bot_discriminator_cannot_be_changed": "Дискриминатор бота нельзя изменить.", diff --git a/packages/errors/src/i18n/weblate/locales/sv-SE.json b/packages/errors/src/i18n/weblate/locales/sv-SE.json index 8a2960b88..d25964d88 100644 --- a/packages/errors/src/i18n/weblate/locales/sv-SE.json +++ b/packages/errors/src/i18n/weblate/locales/sv-SE.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Ogiltig admin-API-nyckel.", "auth_and_oauth.missing_authorization": "Authorization-huvudet saknas eller är ogiltigt.", "auth_and_oauth.must_start_session_before_sending_messages": "Du måste starta en session innan du kan skicka meddelanden.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Den här inloggningsöverföringen matchar inte den du startade.", "auth_and_oauth.registration_closed": "Registrering är stängd på den här instansen.", "auth_and_oauth.registration_pending_approval": "Den här registreringen väntar på administratörsgodkännande.", "auth_and_oauth.registration_rejected": "Den här registreringsbegäran avslogs.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Vi kunde inte tilldela en diskriminator för ditt SSO-konto. Försök igen.", "auth_and_oauth.sudo_mode_required": "Sudo-läge krävs.", "auth_and_oauth.unknown_application": "Okänd applikation.", + "auth_and_oauth.unknown_origin_handoff": "Den här inloggningsöverföringen har gått ut eller har redan använts.", "auth_and_registration.password_is_too_common": "Ditt lösenord är för vanligt. Välj ett lösenord som är svårare att gissa.", "auth_and_registration.password_not_set": "Inget lösenord har angetts.", "bots.bot_discriminator_cannot_be_changed": "Botdiskriminatorn kan inte ändras.", diff --git a/packages/errors/src/i18n/weblate/locales/th.json b/packages/errors/src/i18n/weblate/locales/th.json index c762ef20e..b7db4b5c9 100644 --- a/packages/errors/src/i18n/weblate/locales/th.json +++ b/packages/errors/src/i18n/weblate/locales/th.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Admin API key ไม่ถูกต้อง", "auth_and_oauth.missing_authorization": "ส่วนหัว Authorization หายไปหรือไม่ถูกต้อง", "auth_and_oauth.must_start_session_before_sending_messages": "ต้องเริ่มเซสชันก่อนส่งข้อความ", + "auth_and_oauth.origin_handoff_nonce_invalid": "การโอนการลงชื่อเข้าใช้นี้ไม่ตรงกับรายการที่คุณเริ่มไว้", "auth_and_oauth.registration_closed": "การลงทะเบียนปิดให้บริการในอินสแตนซ์นี้", "auth_and_oauth.registration_pending_approval": "การลงทะเบียนนี้กำลังรอการอนุมัติจากผู้ดูแลระบบ", "auth_and_oauth.registration_rejected": "คำขอลงทะเบียนนี้ถูกปฏิเสธ", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "ไม่สามารถสร้าง discriminator สำหรับบัญชี SSO ของคุณได้ โปรดลองอีกครั้ง", "auth_and_oauth.sudo_mode_required": "ต้องใช้โหมด sudo", "auth_and_oauth.unknown_application": "ไม่พบแอปพลิเคชัน", + "auth_and_oauth.unknown_origin_handoff": "การโอนการลงชื่อเข้าใช้นี้หมดอายุแล้วหรือถูกใช้ไปแล้ว", "auth_and_registration.password_is_too_common": "รหัสผ่านของคุณเป็นรหัสที่พบบ่อยเกินไป โปรดเลือกรหัสผ่านที่เดาได้ยากขึ้น", "auth_and_registration.password_not_set": "ยังไม่ได้ตั้งรหัสผ่าน", "bots.bot_discriminator_cannot_be_changed": "ไม่สามารถเปลี่ยน discriminator ของบอทได้", diff --git a/packages/errors/src/i18n/weblate/locales/tr.json b/packages/errors/src/i18n/weblate/locales/tr.json index d7ebce135..1c85cd2f6 100644 --- a/packages/errors/src/i18n/weblate/locales/tr.json +++ b/packages/errors/src/i18n/weblate/locales/tr.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Geçersiz yönetici API anahtarı.", "auth_and_oauth.missing_authorization": "Eksik veya geçersiz yetkilendirme başlığı.", "auth_and_oauth.must_start_session_before_sending_messages": "Mesaj göndermeden önce bir oturum başlatmalısın.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Bu oturum açma aktarımı başlattığın aktarımla eşleşmiyor.", "auth_and_oauth.registration_closed": "Bu kurulumda kayıt kapalı.", "auth_and_oauth.registration_pending_approval": "Bu kayıt yönetici onayını bekliyor.", "auth_and_oauth.registration_rejected": "Bu kayıt isteği reddedildi.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "SSO hesabın için bir etiket atayamadık. Lütfen tekrar dene.", "auth_and_oauth.sudo_mode_required": "Sudo modu gerekli.", "auth_and_oauth.unknown_application": "Bilinmeyen uygulama.", + "auth_and_oauth.unknown_origin_handoff": "Bu oturum açma aktarımının süresi doldu veya zaten kullanıldı.", "auth_and_registration.password_is_too_common": "Şifren çok yaygın. Lütfen tahmin edilmesi daha zor bir şifre seç.", "auth_and_registration.password_not_set": "Şifre ayarlanmamış.", "bots.bot_discriminator_cannot_be_changed": "Bot etiketi değiştirilemez.", diff --git a/packages/errors/src/i18n/weblate/locales/uk.json b/packages/errors/src/i18n/weblate/locales/uk.json index 65920e186..8ed8d6fea 100644 --- a/packages/errors/src/i18n/weblate/locales/uk.json +++ b/packages/errors/src/i18n/weblate/locales/uk.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Недійсний API-ключ адміністратора.", "auth_and_oauth.missing_authorization": "Відсутній або недійсний заголовок авторизації.", "auth_and_oauth.must_start_session_before_sending_messages": "Потрібно розпочати сесію, перш ніж надсилати повідомлення.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Це перенесення входу не збігається з тим, яке ви розпочали.", "auth_and_oauth.registration_closed": "Реєстрацію на цьому екземплярі закрито.", "auth_and_oauth.registration_pending_approval": "Ця реєстрація очікує на схвалення адміністратором.", "auth_and_oauth.registration_rejected": "Цей запит на реєстрацію було відхилено.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Не вдалося призначити дискримінатор для твого акаунта SSO. Спробуй ще раз.", "auth_and_oauth.sudo_mode_required": "Потрібен режим sudo.", "auth_and_oauth.unknown_application": "Невідомий застосунок.", + "auth_and_oauth.unknown_origin_handoff": "Термін дії цього перенесення входу минув, або його вже використано.", "auth_and_registration.password_is_too_common": "Твій пароль занадто поширений. Обери менш передбачуваний пароль.", "auth_and_registration.password_not_set": "Пароль не встановлено.", "bots.bot_discriminator_cannot_be_changed": "Дискримінатор бота не можна змінити.", diff --git a/packages/errors/src/i18n/weblate/locales/vi.json b/packages/errors/src/i18n/weblate/locales/vi.json index d072236c1..186c5921a 100644 --- a/packages/errors/src/i18n/weblate/locales/vi.json +++ b/packages/errors/src/i18n/weblate/locales/vi.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Khóa API quản trị viên không hợp lệ.", "auth_and_oauth.missing_authorization": "Header ủy quyền bị thiếu hoặc không hợp lệ.", "auth_and_oauth.must_start_session_before_sending_messages": "Bạn phải bắt đầu một phiên trước khi gửi tin nhắn.", + "auth_and_oauth.origin_handoff_nonce_invalid": "Lần chuyển đăng nhập này không khớp với lần bạn đã bắt đầu.", "auth_and_oauth.registration_closed": "Đăng ký đã bị đóng trên phiên bản này.", "auth_and_oauth.registration_pending_approval": "Đăng ký này đang chờ quản trị viên phê duyệt.", "auth_and_oauth.registration_rejected": "Yêu cầu đăng ký này đã bị từ chối.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "Chúng tôi không thể cấp phát mã phân biệt cho tài khoản SSO của bạn. Vui lòng thử lại.", "auth_and_oauth.sudo_mode_required": "Yêu cầu chế độ sudo.", "auth_and_oauth.unknown_application": "Ứng dụng không xác định.", + "auth_and_oauth.unknown_origin_handoff": "Lần chuyển đăng nhập này đã hết hạn hoặc đã được sử dụng.", "auth_and_registration.password_is_too_common": "Mật khẩu của bạn quá phổ biến. Vui lòng chọn mật khẩu khó đoán hơn.", "auth_and_registration.password_not_set": "Mật khẩu chưa được đặt.", "bots.bot_discriminator_cannot_be_changed": "Không thể thay đổi mã phân biệt của bot.", diff --git a/packages/errors/src/i18n/weblate/locales/zh-CN.json b/packages/errors/src/i18n/weblate/locales/zh-CN.json index da7458563..f0aa69320 100644 --- a/packages/errors/src/i18n/weblate/locales/zh-CN.json +++ b/packages/errors/src/i18n/weblate/locales/zh-CN.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "无效的管理员 API 密钥。", "auth_and_oauth.missing_authorization": "缺少或无效的授权请求头。", "auth_and_oauth.must_start_session_before_sending_messages": "你必须先开始会话才能发送消息。", + "auth_and_oauth.origin_handoff_nonce_invalid": "此登录转移与你发起的不一致。", "auth_and_oauth.registration_closed": "此实例的注册已关闭。", "auth_and_oauth.registration_pending_approval": "此注册正在等待管理员批准。", "auth_and_oauth.registration_rejected": "此注册请求已被拒绝。", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "无法为你的 SSO 账号分配识别码。请重试。", "auth_and_oauth.sudo_mode_required": "需要 Sudo 模式。", "auth_and_oauth.unknown_application": "未知应用。", + "auth_and_oauth.unknown_origin_handoff": "此登录转移已过期或已被使用。", "auth_and_registration.password_is_too_common": "你的密码太常见了。请换一个更难猜的密码。", "auth_and_registration.password_not_set": "密码未设置。", "bots.bot_discriminator_cannot_be_changed": "机器人识别码无法更改。", diff --git a/packages/errors/src/i18n/weblate/locales/zh-TW.json b/packages/errors/src/i18n/weblate/locales/zh-TW.json index f79a10b99..698febcec 100644 --- a/packages/errors/src/i18n/weblate/locales/zh-TW.json +++ b/packages/errors/src/i18n/weblate/locales/zh-TW.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "無效的管理 API 金鑰。", "auth_and_oauth.missing_authorization": "授權標頭遺失或無效。", "auth_and_oauth.must_start_session_before_sending_messages": "你必須先開始工作階段才能傳送訊息。", + "auth_and_oauth.origin_handoff_nonce_invalid": "此登入轉移與你發起的不一致。", "auth_and_oauth.registration_closed": "此實例已關閉註冊。", "auth_and_oauth.registration_pending_approval": "此註冊正在等待管理員核准。", "auth_and_oauth.registration_rejected": "此註冊要求已被拒絕。", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "無法為你的 SSO 帳號分配識別碼。請再試一次。", "auth_and_oauth.sudo_mode_required": "需要 Sudo 模式。", "auth_and_oauth.unknown_application": "不明應用程式。", + "auth_and_oauth.unknown_origin_handoff": "此登入轉移已過期或已被使用。", "auth_and_registration.password_is_too_common": "你的密碼太常見。請選擇較難預測的密碼。", "auth_and_registration.password_not_set": "密碼尚未設定。", "bots.bot_discriminator_cannot_be_changed": "機器人識別碼無法變更。", diff --git a/packages/errors/src/i18n/weblate/messages.json b/packages/errors/src/i18n/weblate/messages.json index c481e9050..f169f3760 100644 --- a/packages/errors/src/i18n/weblate/messages.json +++ b/packages/errors/src/i18n/weblate/messages.json @@ -97,6 +97,7 @@ "auth_and_oauth.invalid_token_admin_api_key": "Invalid admin API key.", "auth_and_oauth.missing_authorization": "Missing or invalid authorization header.", "auth_and_oauth.must_start_session_before_sending_messages": "You must start a session before sending messages.", + "auth_and_oauth.origin_handoff_nonce_invalid": "This sign-in transfer doesn't match the one you started.", "auth_and_oauth.registration_closed": "Registration is closed on this instance.", "auth_and_oauth.registration_pending_approval": "This registration is waiting for admin approval.", "auth_and_oauth.registration_rejected": "This registration request was rejected.", @@ -109,6 +110,7 @@ "auth_and_oauth.sso_unable_to_allocate_discriminator": "We couldn't allocate a discriminator for your SSO account. Please try again.", "auth_and_oauth.sudo_mode_required": "Sudo mode is required.", "auth_and_oauth.unknown_application": "Unknown application.", + "auth_and_oauth.unknown_origin_handoff": "This sign-in transfer has expired or was already used.", "auth_and_registration.password_is_too_common": "Your password is too common. Please choose a less predictable password.", "auth_and_registration.password_not_set": "Password isn't set.", "bots.bot_discriminator_cannot_be_changed": "Bot discriminator can't be changed.", diff --git a/packages/schema/src/domains/admin/AdminSchemas.ts b/packages/schema/src/domains/admin/AdminSchemas.ts index 935eac872..c41a3f096 100644 --- a/packages/schema/src/domains/admin/AdminSchemas.ts +++ b/packages/schema/src/domains/admin/AdminSchemas.ts @@ -11,6 +11,10 @@ import {ADMIN_ACL_COUNT, AdminAclType} from '@fluxer/schema/src/domains/admin/Ad import {AdminArchiveResponseSchema} from '@fluxer/schema/src/domains/admin/AdminArchiveSchemas'; import {GuildAdminResponse} from '@fluxer/schema/src/domains/admin/AdminGuildSchemas'; import {UserAdminResponseSchema} from '@fluxer/schema/src/domains/admin/AdminUserSchemas'; +import { + DomainMigrationConfigResponse, + DomainMigrationConfigUpdateRequest, +} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import { GatewayRolloutConfigResponse, GatewayRolloutConfigUpdateRequest, @@ -649,6 +653,7 @@ export const InstanceConfigResponse = z.object({ gateway_rollout: GatewayRolloutConfigResponse, voice_noise_suppression: VoiceNoiseSuppressionConfigResponse, push_service_delivery: PushServiceDeliveryConfigResponse, + domain_migration: DomainMigrationConfigResponse, experiment_delivery: ExperimentDeliveryConfigResponse, registration: InstanceRegistrationResponse, self_hosted: z.boolean(), @@ -686,6 +691,7 @@ export const InstanceConfigUpdateRequest = z.object({ gateway_rollout: GatewayRolloutConfigUpdateRequest.nullish(), voice_noise_suppression: VoiceNoiseSuppressionConfigUpdateRequest.nullish(), push_service_delivery: PushServiceDeliveryConfigUpdateRequest.nullish(), + domain_migration: DomainMigrationConfigUpdateRequest.nullish(), experiment_delivery: ExperimentDeliveryConfigUpdateRequest.nullish(), registration: z .object({ diff --git a/packages/schema/src/domains/admin/DomainMigrationSchemas.test.ts b/packages/schema/src/domains/admin/DomainMigrationSchemas.test.ts new file mode 100644 index 000000000..a71e199be --- /dev/null +++ b/packages/schema/src/domains/admin/DomainMigrationSchemas.test.ts @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + DEFAULT_DOMAIN_MIGRATION_CONFIG, + type DomainMigrationConfig, + DomainMigrationConfigSchema, + DomainMigrationConfigUpdateRequest, + INERT_DOMAIN_MIGRATION_ASSIGNMENT, + resolveDomainMigrationAssignment, + toDomainMigrationDiscovery, +} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; +import {experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket'; +import {describe, expect, test} from 'vitest'; + +const TARGETED_USER_ID = '1000000000000000001'; +const OTHER_USER_ID = '1000000000000000002'; + +function createConfig(overrides: Partial = {}): DomainMigrationConfig { + return { + ...DEFAULT_DOMAIN_MIGRATION_CONFIG, + included_user_ids: [], + excluded_user_ids: [], + ...overrides, + }; +} + +function syntheticUserIds(count: number): Array { + const ids: Array = []; + for (let index = 0; index < count; index++) { + ids.push((1400000000000000000n + BigInt(index)).toString()); + } + return ids; +} + +function targetedUserIds(config: DomainMigrationConfig, userIds: ReadonlyArray): Set { + const targeted = new Set(); + for (const userId of userIds) { + if (resolveDomainMigrationAssignment(config, userId).enabled) { + targeted.add(userId); + } + } + return targeted; +} + +describe('domain migration configuration', () => { + test('derives defaults from the schema with independently owned arrays', () => { + const first = DomainMigrationConfigSchema.parse({}); + const second = DomainMigrationConfigSchema.parse({}); + expect(first).toEqual(DEFAULT_DOMAIN_MIGRATION_CONFIG); + first.included_user_ids.push(TARGETED_USER_ID); + first.excluded_user_ids.push(OTHER_USER_ID); + expect(second).toEqual(DEFAULT_DOMAIN_MIGRATION_CONFIG); + }); + + test('defaults to disabled with an empty rollout', () => { + expect(DEFAULT_DOMAIN_MIGRATION_CONFIG.enabled).toBe(false); + expect(DEFAULT_DOMAIN_MIGRATION_CONFIG.rollout_basis_points).toBe(0); + expect(DEFAULT_DOMAIN_MIGRATION_CONFIG.anonymous_rollout_basis_points).toBe(0); + expect(DEFAULT_DOMAIN_MIGRATION_CONFIG.rollout_salt).toBe('domain-migration-v1'); + expect(DEFAULT_DOMAIN_MIGRATION_CONFIG.standalone_forwarding).toBe(false); + }); + + test('fills standalone forwarding in for configs stored before it existed', () => { + const stored = DomainMigrationConfigSchema.parse({enabled: true, config_version: 4, rollout_basis_points: 100}); + expect(stored.standalone_forwarding).toBe(false); + }); + + test.each([{}, {enabled: false}, {enabled: undefined}, {rollout_basis_points: 2500}, {standalone_forwarding: true}])( + 'keeps partial updates free of configuration defaults: %j', + (patch) => { + expect(DomainMigrationConfigUpdateRequest.parse(patch)).toEqual(patch); + }, + ); + + test('does not accept a client-provided configuration version', () => { + expect(DomainMigrationConfigUpdateRequest.parse({config_version: 12})).toEqual({}); + }); + + test.each([ + {rollout_basis_points: -1}, + {rollout_basis_points: 10001}, + {anonymous_rollout_basis_points: -1}, + {anonymous_rollout_basis_points: 10001}, + {anonymous_rollout_basis_points: 1.5}, + {rollout_salt: ' '}, + {rollout_salt: 'domain-migration-\u00e9'}, + {included_user_ids: ['not-an-id']}, + {excluded_user_ids: ['not-an-id']}, + {standalone_forwarding: 'yes'}, + ])('applies the same validation to stored configuration and updates: %j', (value) => { + expect(DomainMigrationConfigSchema.safeParse(value).success).toBe(false); + expect(DomainMigrationConfigUpdateRequest.safeParse(value).success).toBe(false); + }); + + test('rejects more than a thousand targeted user ids', () => { + const ids = syntheticUserIds(1001); + expect(DomainMigrationConfigSchema.safeParse({included_user_ids: ids}).success).toBe(false); + expect(DomainMigrationConfigSchema.safeParse({included_user_ids: ids.slice(0, 1000)}).success).toBe(true); + }); +}); + +describe('resolveDomainMigrationAssignment', () => { + test('the inert assignment is disabled', () => { + expect(INERT_DOMAIN_MIGRATION_ASSIGNMENT.enabled).toBe(false); + }); + + test('returns the inert assignment when the master switch is off', () => { + const config = createConfig({ + enabled: false, + rollout_basis_points: 10000, + included_user_ids: [TARGETED_USER_ID], + }); + expect(resolveDomainMigrationAssignment(config, TARGETED_USER_ID)).toEqual(INERT_DOMAIN_MIGRATION_ASSIGNMENT); + }); + + test('returns the inert assignment for the default config', () => { + expect(resolveDomainMigrationAssignment(createConfig(), TARGETED_USER_ID)).toEqual( + INERT_DOMAIN_MIGRATION_ASSIGNMENT, + ); + }); + + test('never hands back the shared inert object', () => { + const assignment = resolveDomainMigrationAssignment(createConfig(), TARGETED_USER_ID); + expect(assignment).not.toBe(INERT_DOMAIN_MIGRATION_ASSIGNMENT); + }); + + test('denylist beats allowlist', () => { + const config = createConfig({ + enabled: true, + included_user_ids: [TARGETED_USER_ID], + excluded_user_ids: [TARGETED_USER_ID], + }); + expect(resolveDomainMigrationAssignment(config, TARGETED_USER_ID).enabled).toBe(false); + }); + + test('denylist beats the bucket', () => { + const config = createConfig({ + enabled: true, + rollout_basis_points: 10000, + excluded_user_ids: [TARGETED_USER_ID], + }); + expect(resolveDomainMigrationAssignment(config, TARGETED_USER_ID).enabled).toBe(false); + expect(resolveDomainMigrationAssignment(config, OTHER_USER_ID).enabled).toBe(true); + }); + + test('allowlist beats the bucket', () => { + const config = createConfig({ + enabled: true, + rollout_basis_points: 0, + included_user_ids: [TARGETED_USER_ID], + }); + expect(resolveDomainMigrationAssignment(config, TARGETED_USER_ID).enabled).toBe(true); + expect(resolveDomainMigrationAssignment(config, OTHER_USER_ID).enabled).toBe(false); + }); + + test.each([ + {basisPoints: 0, enabled: false}, + {basisPoints: 10000, enabled: true}, + ])('a rollout of $basisPoints basis points targets $enabled', ({basisPoints, enabled}) => { + const config = createConfig({enabled: true, rollout_basis_points: basisPoints}); + for (const userId of syntheticUserIds(200)) { + expect(resolveDomainMigrationAssignment(config, userId).enabled).toBe(enabled); + } + }); + + test('the bucket boundary is exclusive at the low end and inclusive one point above', () => { + const salt = DEFAULT_DOMAIN_MIGRATION_CONFIG.rollout_salt; + const bucket = experimentBucket(TARGETED_USER_ID, salt); + expect( + resolveDomainMigrationAssignment(createConfig({enabled: true, rollout_basis_points: bucket}), TARGETED_USER_ID) + .enabled, + ).toBe(false); + expect( + resolveDomainMigrationAssignment( + createConfig({enabled: true, rollout_basis_points: bucket + 1}), + TARGETED_USER_ID, + ).enabled, + ).toBe(true); + }); + + test('raising the rollout basis points only ever adds users', () => { + const userIds = syntheticUserIds(2000); + const atOneThousand = targetedUserIds(createConfig({enabled: true, rollout_basis_points: 1000}), userIds); + const atTwoThousand = targetedUserIds(createConfig({enabled: true, rollout_basis_points: 2000}), userIds); + expect(atOneThousand.size).toBeGreaterThan(0); + for (const userId of atOneThousand) { + expect(atTwoThousand.has(userId)).toBe(true); + } + expect(atTwoThousand.size).toBeGreaterThan(atOneThousand.size); + }); + + test('the targeted set follows the salt', () => { + const userIds = syntheticUserIds(2000); + const first = targetedUserIds( + createConfig({enabled: true, rollout_basis_points: 5000, rollout_salt: 'domain-migration-v1'}), + userIds, + ); + const second = targetedUserIds( + createConfig({enabled: true, rollout_basis_points: 5000, rollout_salt: 'domain-migration-v2'}), + userIds, + ); + expect(first).not.toEqual(second); + }); +}); + +describe('toDomainMigrationDiscovery', () => { + test('projects only the public fields of the config', () => { + const config = createConfig({ + enabled: true, + config_version: 3, + rollout_basis_points: 500, + rollout_salt: 'domain-migration-v2', + anonymous_rollout_basis_points: 250, + included_user_ids: [TARGETED_USER_ID], + excluded_user_ids: [OTHER_USER_ID], + standalone_forwarding: true, + }); + expect(toDomainMigrationDiscovery(config)).toEqual({ + enabled: true, + anonymous_rollout_basis_points: 250, + rollout_salt: 'domain-migration-v2', + standalone_forwarding: true, + }); + }); + + test('reports the kill switch for the default config', () => { + expect(toDomainMigrationDiscovery(DEFAULT_DOMAIN_MIGRATION_CONFIG)).toEqual({ + enabled: false, + anonymous_rollout_basis_points: 0, + rollout_salt: 'domain-migration-v1', + standalone_forwarding: false, + }); + }); +}); diff --git a/packages/schema/src/domains/admin/DomainMigrationSchemas.ts b/packages/schema/src/domains/admin/DomainMigrationSchemas.ts new file mode 100644 index 000000000..b05590b95 --- /dev/null +++ b/packages/schema/src/domains/admin/DomainMigrationSchemas.ts @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {EXPERIMENT_BUCKET_RESOLUTION, experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket'; +import {z} from 'zod'; + +const DOMAIN_MIGRATION_ROLLOUT_BASIS_POINTS_MAX = EXPERIMENT_BUCKET_RESOLUTION; +const DOMAIN_MIGRATION_MAX_TARGETED_USERS = 1000; +const DEFAULT_DOMAIN_MIGRATION_SALT = 'domain-migration-v1'; + +const DOMAIN_MIGRATION_SALT_PATTERN = /^[\x20-\x7e]+$/u; + +const DomainMigrationTargetIdSchema = z.string().regex(/^\d{1,20}$/u); +const DomainMigrationTargetedUserIdsSchema = z + .array(DomainMigrationTargetIdSchema) + .max(DOMAIN_MIGRATION_MAX_TARGETED_USERS); + +const domainMigrationConfigFields = { + enabled: z.boolean(), + config_version: z.number().int().min(0), + rollout_basis_points: z.number().int().min(0).max(DOMAIN_MIGRATION_ROLLOUT_BASIS_POINTS_MAX), + rollout_salt: z.string().trim().min(1).max(64).regex(DOMAIN_MIGRATION_SALT_PATTERN), + included_user_ids: DomainMigrationTargetedUserIdsSchema, + excluded_user_ids: DomainMigrationTargetedUserIdsSchema, + anonymous_rollout_basis_points: z.number().int().min(0).max(DOMAIN_MIGRATION_ROLLOUT_BASIS_POINTS_MAX), + standalone_forwarding: z.boolean(), +}; + +export const DomainMigrationConfigSchema = z.object({ + enabled: domainMigrationConfigFields.enabled.default(false), + config_version: domainMigrationConfigFields.config_version.default(0), + rollout_basis_points: domainMigrationConfigFields.rollout_basis_points.default(0), + rollout_salt: domainMigrationConfigFields.rollout_salt.default(DEFAULT_DOMAIN_MIGRATION_SALT), + included_user_ids: domainMigrationConfigFields.included_user_ids.default([]), + excluded_user_ids: domainMigrationConfigFields.excluded_user_ids.default([]), + anonymous_rollout_basis_points: domainMigrationConfigFields.anonymous_rollout_basis_points.default(0), + standalone_forwarding: domainMigrationConfigFields.standalone_forwarding.default(false), +}); + +export type DomainMigrationConfig = z.infer; + +export const DEFAULT_DOMAIN_MIGRATION_CONFIG: DomainMigrationConfig = DomainMigrationConfigSchema.parse({}); + +export const DomainMigrationConfigUpdateRequest = z + .object(domainMigrationConfigFields) + .omit({config_version: true}) + .partial(); + +export type DomainMigrationConfigUpdateRequest = z.infer; + +export const DomainMigrationConfigResponse = DomainMigrationConfigSchema; + +export type DomainMigrationConfigResponse = z.infer; + +export const DomainMigrationAssignmentResponse = z.object({ + enabled: domainMigrationConfigFields.enabled, +}); + +export type DomainMigrationAssignmentResponse = z.infer; + +export const INERT_DOMAIN_MIGRATION_ASSIGNMENT: DomainMigrationAssignmentResponse = { + enabled: false, +}; + +export function resolveDomainMigrationAssignment( + config: DomainMigrationConfig, + userId: string, +): DomainMigrationAssignmentResponse { + if (!config.enabled) return {...INERT_DOMAIN_MIGRATION_ASSIGNMENT}; + if (config.excluded_user_ids.includes(userId)) return {...INERT_DOMAIN_MIGRATION_ASSIGNMENT}; + if (config.included_user_ids.includes(userId)) return {enabled: true}; + return {enabled: experimentBucket(userId, config.rollout_salt) < config.rollout_basis_points}; +} + +export const DomainMigrationDiscoveryResponse = z.object({ + enabled: domainMigrationConfigFields.enabled.describe('Whether the domain migration is switched on'), + anonymous_rollout_basis_points: domainMigrationConfigFields.anonymous_rollout_basis_points.describe( + 'Share of logged-out devices, in basis points, that move to the new domain', + ), + rollout_salt: domainMigrationConfigFields.rollout_salt.describe('Salt used to bucket devices and users'), + standalone_forwarding: domainMigrationConfigFields.standalone_forwarding.describe( + 'Whether installed desktop web apps forward to the new domain after moving their session', + ), +}); + +export type DomainMigrationDiscoveryResponse = z.infer; + +export function toDomainMigrationDiscovery(config: DomainMigrationConfig): DomainMigrationDiscoveryResponse { + return { + enabled: config.enabled, + anonymous_rollout_basis_points: config.anonymous_rollout_basis_points, + rollout_salt: config.rollout_salt, + standalone_forwarding: config.standalone_forwarding, + }; +} diff --git a/packages/schema/src/domains/auth/OriginHandoffSchemas.ts b/packages/schema/src/domains/auth/OriginHandoffSchemas.ts new file mode 100644 index 000000000..cdf8b8ff8 --- /dev/null +++ b/packages/schema/src/domains/auth/OriginHandoffSchemas.ts @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {z} from 'zod'; + +export const ORIGIN_HANDOFF_MAX_PAYLOAD_LENGTH = 8 * 1024 * 1024; + +const Base64UrlPattern = /^[A-Za-z0-9_-]+$/u; + +export const OriginHandoffCreateRequest = z.object({ + nonce_hash: z + .string() + .regex(/^[0-9a-f]{64}$/u) + .describe('Lowercase hex SHA-256 digest of the nonce the receiving origin holds'), + payload: z + .string() + .min(1) + .max(ORIGIN_HANDOFF_MAX_PAYLOAD_LENGTH) + .regex(Base64UrlPattern) + .describe('Encrypted client state encoded as base64url'), +}); + +export type OriginHandoffCreateRequest = z.infer; + +export const OriginHandoffCreateResponse = z.object({ + handoff_id: z.string().describe('Single-use identifier the receiving origin redeems'), +}); + +export type OriginHandoffCreateResponse = z.infer; + +export const OriginHandoffRedeemRequest = z.object({ + handoff_id: z + .string() + .regex(/^[A-Za-z0-9_-]{43}$/u) + .describe('Identifier returned when the handoff was created'), + nonce: z + .string() + .min(16) + .max(256) + .regex(Base64UrlPattern) + .describe('Nonce whose SHA-256 digest was sent when the handoff was created'), +}); + +export type OriginHandoffRedeemRequest = z.infer; + +export const OriginHandoffRedeemResponse = z.object({ + payload: z.string().describe('Encrypted client state encoded as base64url'), +}); + +export type OriginHandoffRedeemResponse = z.infer; diff --git a/packages/schema/src/domains/experiment/ExperimentSchemas.ts b/packages/schema/src/domains/experiment/ExperimentSchemas.ts index 73c36f9cc..4c6b5adc4 100644 --- a/packages/schema/src/domains/experiment/ExperimentSchemas.ts +++ b/packages/schema/src/domains/experiment/ExperimentSchemas.ts @@ -1,5 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +import { + DomainMigrationAssignmentResponse, + INERT_DOMAIN_MIGRATION_ASSIGNMENT, +} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import { INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, VoiceNoiseSuppressionAssignmentResponse, @@ -42,6 +46,7 @@ export type ExperimentDeliveryConfigResponse = z.infer; diff --git a/packages/schema/src/domains/user/UserRequestSchemas.ts b/packages/schema/src/domains/user/UserRequestSchemas.ts index 04ef82d5c..f0c1d6e8a 100644 --- a/packages/schema/src/domains/user/UserRequestSchemas.ts +++ b/packages/schema/src/domains/user/UserRequestSchemas.ts @@ -615,6 +615,7 @@ export const PushSubscribeRequest = z.object({ }) .describe('Encryption keys for the push subscription'), user_agent: createStringType(1, 1024).optional().describe('The user agent string identifying the client'), + installed_app: z.boolean().optional().describe('Whether the client runs in an installed web app window'), }); export type PushSubscribeRequest = z.infer; @@ -629,6 +630,7 @@ export const PushRotateRequest = z.object({ }) .describe('Encryption keys for the new push subscription'), user_agent: createStringType(1, 1024).optional().describe('The user agent string identifying the client'), + installed_app: z.boolean().optional().describe('Whether the client runs in an installed web app window'), }); export type PushRotateRequest = z.infer;