diff --git a/fluxer_admin/openapi-admin.json b/fluxer_admin/openapi-admin.json index debfe5022..066e49689 100644 --- a/fluxer_admin/openapi-admin.json +++ b/fluxer_admin/openapi-admin.json @@ -10527,6 +10527,7 @@ "push_relay": {"$ref": "#/components/schemas/PushRelayConfigResponse"}, "domain_migration": {"$ref": "#/components/schemas/DomainMigrationConfigResponse"}, "altcha_captcha": {"$ref": "#/components/schemas/AltchaCaptchaConfigResponse"}, + "profile_timezone": {"$ref": "#/components/schemas/ProfileTimezoneConfigResponse"}, "experiment_delivery": {"$ref": "#/components/schemas/ExperimentDeliveryConfigResponse"}, "registration": { "type": "object", @@ -10957,6 +10958,7 @@ "push_relay", "domain_migration", "altcha_captcha", + "profile_timezone", "experiment_delivery", "registration", "self_hosted", @@ -11100,6 +11102,10 @@ "nullable": true, "allOf": [{"$ref": "#/components/schemas/AltchaCaptchaConfigUpdateRequest"}] }, + "profile_timezone": { + "nullable": true, + "allOf": [{"$ref": "#/components/schemas/ProfileTimezoneConfigUpdateRequest"}] + }, "experiment_delivery": { "nullable": true, "allOf": [{"$ref": "#/components/schemas/ExperimentDeliveryConfigUpdateRequest"}] @@ -15193,6 +15199,24 @@ "poll_jitter_percent": {"type": "integer", "minimum": 0, "maximum": 50} } }, + "ProfileTimezoneConfigUpdateRequest": { + "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}$"} + } + } + }, "AltchaCaptchaConfigUpdateRequest": { "type": "object", "properties": { @@ -15300,6 +15324,42 @@ "required": ["poll_interval_seconds", "poll_jitter_percent"], "additionalProperties": false }, + "ProfileTimezoneConfigResponse": { + "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": "profile-timezone-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}$"} + } + }, + "required": [ + "enabled", + "config_version", + "rollout_basis_points", + "rollout_salt", + "included_user_ids", + "excluded_user_ids" + ], + "additionalProperties": false + }, "AltchaCaptchaConfigResponse": { "type": "object", "properties": { diff --git a/fluxer_admin/src/api/types/instance_config.rs b/fluxer_admin/src/api/types/instance_config.rs index b488bb1d0..701edfdd7 100644 --- a/fluxer_admin/src/api/types/instance_config.rs +++ b/fluxer_admin/src/api/types/instance_config.rs @@ -29,6 +29,8 @@ pub struct InstanceConfigResponse { #[serde(default)] pub altcha_captcha: AltchaCaptchaConfigResponse, #[serde(default)] + pub profile_timezone: ProfileTimezoneConfigResponse, + #[serde(default)] pub experiment_delivery: ExperimentDeliveryConfigResponse, } @@ -457,6 +459,7 @@ pub const DOMAIN_MIGRATION_DEFAULT_SALT: &str = "domain-migration-v1"; pub const ALTCHA_CAPTCHA_DEFAULT_SALT: &str = "altcha-captcha-v1"; pub const ALTCHA_CAPTCHA_COST_RANGE: std::ops::RangeInclusive = 1_000..=100_000; pub const ALTCHA_CAPTCHA_MAX_COUNTER_RANGE: std::ops::RangeInclusive = 100..=1_000_000; +pub const PROFILE_TIMEZONE_DEFAULT_SALT: &str = "profile-timezone-v1"; pub const VOICE_NS_MAX_GUILD_OVERRIDES: usize = 200; impl NoiseSuppressionBackend { @@ -657,6 +660,44 @@ pub struct AltchaCaptchaConfigUpdateRequest { pub max_counter: Option, } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(default)] +pub struct ProfileTimezoneConfigResponse { + 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, +} + +impl Default for ProfileTimezoneConfigResponse { + fn default() -> Self { + Self { + enabled: false, + config_version: 0, + rollout_basis_points: 0, + rollout_salt: PROFILE_TIMEZONE_DEFAULT_SALT.to_owned(), + included_user_ids: Vec::new(), + excluded_user_ids: Vec::new(), + } + } +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct ProfileTimezoneConfigUpdateRequest { + #[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>, +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(default)] pub struct ExperimentDeliveryConfigResponse { @@ -779,6 +820,8 @@ pub struct InstanceConfigUpdateRequest { #[serde(skip_serializing_if = "Option::is_none")] pub altcha_captcha: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub profile_timezone: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub experiment_delivery: Option, } @@ -1120,6 +1163,8 @@ mod tests { .expect("default domain migration config"); let altcha_captcha = serde_json::from_value::(json!({})) .expect("default altcha captcha config"); + let profile_timezone = serde_json::from_value::(json!({})) + .expect("default profile timezone config"); let delivery = serde_json::from_value::(json!({})) .expect("default delivery config"); let noise = serde_json::to_value(noise).expect("serializable noise config"); @@ -1127,6 +1172,8 @@ mod tests { serde_json::to_value(domain_migration).expect("serializable domain migration config"); let altcha_captcha = serde_json::to_value(altcha_captcha).expect("serializable altcha captcha config"); + let profile_timezone = + serde_json::to_value(profile_timezone).expect("serializable profile timezone 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"); @@ -1136,6 +1183,9 @@ mod tests { let generated_altcha_captcha: generated_types::AltchaCaptchaConfigResponse = serde_json::from_value(altcha_captcha.clone()) .expect("generated altcha captcha config contract"); + let generated_profile_timezone: generated_types::ProfileTimezoneConfigResponse = + serde_json::from_value(profile_timezone.clone()) + .expect("generated profile timezone config contract"); let generated_delivery: generated_types::ExperimentDeliveryConfigResponse = serde_json::from_value(delivery.clone()).expect("generated delivery config contract"); assert_eq!( @@ -1152,6 +1202,11 @@ mod tests { .expect("serializable generated altcha captcha config"), altcha_captcha ); + assert_eq!( + serde_json::to_value(generated_profile_timezone) + .expect("serializable generated profile timezone config"), + profile_timezone + ); assert_eq!( serde_json::to_value(generated_delivery) .expect("serializable generated delivery config"), @@ -1161,6 +1216,7 @@ mod tests { ("VoiceNoiseSuppressionConfigResponse", noise), ("DomainMigrationConfigResponse", domain_migration), ("AltchaCaptchaConfigResponse", altcha_captcha), + ("ProfileTimezoneConfigResponse", profile_timezone), ("ExperimentDeliveryConfigResponse", delivery), ] { for (field, value) in value.as_object().expect("config object") { diff --git a/fluxer_admin/src/routes/system_actions.rs b/fluxer_admin/src/routes/system_actions.rs index 0c1457d9e..ecb94a834 100644 --- a/fluxer_admin/src/routes/system_actions.rs +++ b/fluxer_admin/src/routes/system_actions.rs @@ -20,9 +20,9 @@ use crate::{ InstancePolicyUpdateRequest, InstanceRegistrationConfigUpdateRequest, InstanceServicesUpdateRequest, InstanceYoutubeIntegrationUpdateRequest, LimitConfigUpdateRequest, LimitRule, LimitRuleFilters, NoiseSuppressionBackend, - PremiumMode, PushRelayConfigUpdateRequest, RegistrationMode, SsoConfigUpdateRequest, - VOICE_NS_MAX_GUILD_OVERRIDES, VoiceE2eeScope, VoiceNoiseSuppressionConfigUpdateRequest, - VoiceNoiseSuppressionGuildOverride, + PremiumMode, ProfileTimezoneConfigUpdateRequest, PushRelayConfigUpdateRequest, + RegistrationMode, SsoConfigUpdateRequest, VOICE_NS_MAX_GUILD_OVERRIDES, VoiceE2eeScope, + VoiceNoiseSuppressionConfigUpdateRequest, VoiceNoiseSuppressionGuildOverride, }, }, config::AdminConfig, @@ -221,6 +221,10 @@ pub async fn instance_config_post( Ok(update) => instance_config_result(client.update_instance_config(&update).await), Err(message) => FlashData::error(message), }, + "update_profile_timezone" => match build_profile_timezone_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), @@ -751,6 +755,38 @@ fn build_altcha_captcha_update( }) } +fn build_profile_timezone_update( + form: &MultiValueForm, +) -> Result { + Ok(InstanceConfigUpdateRequest { + profile_timezone: Some(ProfileTimezoneConfigUpdateRequest { + enabled: Some(form.bool_value("profile_timezone_enabled")), + rollout_basis_points: parse_form_number( + form, + "profile_timezone_rollout_basis_points", + "Rollout basis points", + 0, + EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, + )?, + rollout_salt: parse_ascii_experiment_rollout_salt( + form, + "profile_timezone_rollout_salt", + )?, + included_user_ids: Some(parse_experiment_user_ids( + form.first("profile_timezone_included_user_ids") + .unwrap_or_default(), + "Included user IDs", + )?), + excluded_user_ids: Some(parse_experiment_user_ids( + form.first("profile_timezone_excluded_user_ids") + .unwrap_or_default(), + "Excluded user IDs", + )?), + }), + ..Default::default() + }) +} + fn build_experiment_delivery_update( form: &MultiValueForm, ) -> Result { @@ -1838,6 +1874,51 @@ mod tests { } } + #[test] + fn build_profile_timezone_update_reads_the_rollout_fields() { + let form = MultiValueForm::parse( + b"profile_timezone_enabled=true&profile_timezone_rollout_basis_points=%20500%20&profile_timezone_rollout_salt=%20profile-timezone-v2%20&profile_timezone_included_user_ids=1500000000000000001&profile_timezone_excluded_user_ids=1500000000000000002", + ); + let update = build_profile_timezone_update(&form) + .expect("valid form") + .profile_timezone + .expect("profile timezone update"); + assert_eq!(update.enabled, Some(true)); + assert_eq!(update.rollout_basis_points, Some(500)); + assert_eq!(update.rollout_salt, Some("profile-timezone-v2".to_owned())); + assert_eq!( + update.included_user_ids, + Some(vec!["1500000000000000001".to_owned()]) + ); + assert_eq!( + update.excluded_user_ids, + Some(vec!["1500000000000000002".to_owned()]) + ); + } + + #[test] + fn build_profile_timezone_update_leaves_the_feature_inert_when_nothing_is_submitted() { + let form = MultiValueForm::parse(b"_csrf=token"); + let request = build_profile_timezone_update(&form).expect("valid form"); + assert_eq!( + serde_json::to_value(request).expect("serializable update"), + serde_json::json!({"profile_timezone": { + "enabled": false, + "included_user_ids": [], + "excluded_user_ids": [], + }}) + ); + } + + #[test] + fn build_profile_timezone_update_rejects_a_rollout_above_everybody() { + let form = MultiValueForm::parse(b"profile_timezone_rollout_basis_points=10001"); + assert_eq!( + build_profile_timezone_update(&form).expect_err("invalid field"), + "Rollout basis points must be a whole number between 0 and 10000" + ); + } + #[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 1cfa964c3..dda31a906 100644 --- a/fluxer_admin/src/templates/pages/instance_config.rs +++ b/fluxer_admin/src/templates/pages/instance_config.rs @@ -8,8 +8,9 @@ use crate::{ ExperimentDeliveryConfigResponse, GatewayRolloutConfigResponse, InstanceConfigResponse, InstanceIntegrationsResponse, InstanceMediaResponse, InstancePolicyResponse, InstanceRegistrationResponse, LimitConfigResponse, NoiseSuppressionBackend, - PendingRegistrationResponse, PushRelayConfigResponse, RegistrationUrlResponse, - SsoConfigResponse, VOICE_NS_MAX_GUILD_OVERRIDES, VoiceNoiseSuppressionConfigResponse, + PROFILE_TIMEZONE_DEFAULT_SALT, PendingRegistrationResponse, ProfileTimezoneConfigResponse, + PushRelayConfigResponse, RegistrationUrlResponse, SsoConfigResponse, + VOICE_NS_MAX_GUILD_OVERRIDES, VoiceNoiseSuppressionConfigResponse, }, config::AdminConfig, middleware::auth::AuthContext, @@ -159,6 +160,7 @@ pub fn instance_config_page( (voice_noise_suppression_section(base, csrf_token, &instance_config.voice_noise_suppression)) (domain_migration_section(base, csrf_token, &instance_config.domain_migration)) (altcha_captcha_section(base, csrf_token, &instance_config.altcha_captcha)) + (profile_timezone_section(base, csrf_token, &instance_config.profile_timezone)) (experiment_delivery_section(base, csrf_token, &instance_config.experiment_delivery)) @if let Some(limit_config) = limit_config { (limit_config_section(base, limit_config)) @@ -1529,6 +1531,111 @@ fn altcha_captcha_section( ) } +fn profile_timezone_section( + base: &str, + csrf_token: &str, + profile_timezone: &ProfileTimezoneConfigResponse, +) -> Markup { + let status = if profile_timezone.enabled { + ("Live", BadgeVariant::Success) + } else { + ("Inert", BadgeVariant::Default) + }; + let included_user_ids = profile_timezone.included_user_ids.join("\n"); + let excluded_user_ids = profile_timezone.excluded_user_ids.join("\n"); + section_card_with_description( + "Profile Timezone", + "Lets the selected users save a time zone in profile settings and show their local time \ + on their profile. Users outside the rollout cannot change it, and a saved time zone \ + stays hidden from everyone while its owner is outside the rollout.", + html! { + form method="post" action={(base) "/instance-config?action=update_profile_timezone"} { + (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 " (profile_timezone.config_version) + } + } + (checkbox( + "profile_timezone_enabled", + "true", + "Serve profile timezone to the selected users", + profile_timezone.enabled, + true, + )) + p class="text-xs text-neutral-500" { + "Off is the safe state and the kill switch. With this unchecked nobody \ + sees the setting and every saved time zone is hidden." + } + + h3 class="text-sm font-semibold text-neutral-900" { "Rollout" } + (number_field( + "profile_timezone_rollout_basis_points", + "Rollout (basis points)", + &profile_timezone.rollout_basis_points.to_string(), + Some(0), Some(10000), "1", + Some("Share of users bucketed into profile timezone, in basis points: 0 is nobody, 100 is 1%, 10000 is everybody."), + )) + div class="flex flex-col gap-2" { + (text_input( + "profile_timezone_rollout_salt", + "Rollout Salt", + &profile_timezone.rollout_salt, + PROFILE_TIMEZONE_DEFAULT_SALT, + )) + p class="text-xs text-neutral-500" { + "Seeds the bucketing hash. Changing it reshuffles which users fall \ + inside the percentage above." + } + } + div class="flex flex-col gap-2" { + (textarea_input( + "profile_timezone_included_user_ids", + "Always-on User IDs", + "1500000000000000001\n1500000000000000002", + &included_user_ids, + 4, + false, + )) + (entry_count_hint( + profile_timezone.included_user_ids.len(), + EXPERIMENT_MAX_TARGETED_USERS, + )) + p class="text-xs text-neutral-500" { + "One snowflake per line, or comma separated. These users get profile \ + timezone regardless of the percentage above. Invalid entries prevent the save." + } + } + div class="flex flex-col gap-2" { + (textarea_input( + "profile_timezone_excluded_user_ids", + "Never-on User IDs", + "1500000000000000003\n1500000000000000004", + &excluded_user_ids, + 4, + false, + )) + (entry_count_hint( + profile_timezone.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." + } + } + + (form_actions(html! { + (submit_button("Save Profile Timezone Configuration")) + })) + } + } + }, + ) +} + fn experiment_delivery_section( base: &str, csrf_token: &str, diff --git a/fluxer_admin/tests/api_deserialization.rs b/fluxer_admin/tests/api_deserialization.rs index 667d7bbda..73210208e 100644 --- a/fluxer_admin/tests/api_deserialization.rs +++ b/fluxer_admin/tests/api_deserialization.rs @@ -437,6 +437,15 @@ fn deserialize_instance_config_response_with_unknown_keys() { "max_counter": 10000, "future_altcha_knob": "argon2id" }, + "profile_timezone": { + "enabled": true, + "config_version": 2, + "rollout_basis_points": 0, + "rollout_salt": "profile-timezone-v1", + "included_user_ids": ["1500000000000000001"], + "excluded_user_ids": [], + "future_profile_timezone_knob": true + }, "experiment_delivery": {"poll_interval_seconds": 300, "poll_jitter_percent": 15}, "registration": { "mode": "open", @@ -579,6 +588,9 @@ fn deserialize_instance_config_response_with_unknown_keys() { assert!(resp.altcha_captcha.anonymous_enabled); assert_eq!(resp.altcha_captcha.excluded_user_ids.len(), 1); assert_eq!(resp.altcha_captcha.max_counter, 10000); + assert!(resp.profile_timezone.enabled); + assert_eq!(resp.profile_timezone.config_version, 2); + assert_eq!(resp.profile_timezone.included_user_ids.len(), 1); 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)); diff --git a/fluxer_api/src/api/admin/controllers/InstanceConfigAdminController.ts b/fluxer_api/src/api/admin/controllers/InstanceConfigAdminController.ts index 20e7a1266..92e17fbf4 100644 --- a/fluxer_api/src/api/admin/controllers/InstanceConfigAdminController.ts +++ b/fluxer_api/src/api/admin/controllers/InstanceConfigAdminController.ts @@ -37,6 +37,7 @@ import { import {AltchaCaptchaConfigSchema} from '@fluxer/schema/src/domains/admin/AltchaCaptchaSchemas'; import {DomainMigrationConfigSchema} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; import {GatewayRolloutConfigSchema} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas'; +import {ProfileTimezoneConfigSchema} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; import type {PushRelayConfig, PushRelayConfigUpdateRequest} from '@fluxer/schema/src/domains/admin/PushRelaySchemas'; import {VoiceNoiseSuppressionConfigSchema} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas'; import {UserIdParam} from '@fluxer/schema/src/domains/common/CommonParamSchemas'; @@ -69,6 +70,7 @@ async function buildInstanceConfigResponse(): Promise { pushRelay, domainMigration, altchaCaptcha, + profileTimezone, experimentDelivery, registrationConfig, registrationUrls, @@ -80,6 +82,7 @@ async function buildInstanceConfigResponse(): Promise { instanceConfigRepository.getPushRelayConfig(), instanceConfigRepository.getDomainMigrationConfig(), instanceConfigRepository.getAltchaCaptchaConfig(), + instanceConfigRepository.getProfileTimezoneConfig(), instanceConfigRepository.getExperimentDeliveryConfig(), instanceConfigRepository.getRegistrationConfig(), instanceConfigRepository.getRegistrationUrlsForAdmin(), @@ -114,6 +117,7 @@ async function buildInstanceConfigResponse(): Promise { push_relay: pushRelay, domain_migration: domainMigration, altcha_captcha: altchaCaptcha, + profile_timezone: profileTimezone, experiment_delivery: experimentDelivery, registration: { ...registrationConfig, @@ -327,6 +331,18 @@ export function InstanceConfigAdminController(app: HonoApp) { ); } } + if (data.profile_timezone) { + const patch = omitUndefinedFields(data.profile_timezone); + if (Object.keys(patch).length > 0) { + await instanceConfigRepository.updateProfileTimezoneConfig((current) => + ProfileTimezoneConfigSchema.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/experiment/ExperimentController.ts b/fluxer_api/src/api/experiment/ExperimentController.ts index 9bd573ffd..dbf90dbce 100644 --- a/fluxer_api/src/api/experiment/ExperimentController.ts +++ b/fluxer_api/src/api/experiment/ExperimentController.ts @@ -10,6 +10,7 @@ import {entityTagMatches} from '@app/api/utils/EntityTag'; import {Headers as HttpHeaders} from '@fluxer/constants/src/Headers'; import {resolveAltchaCaptchaAssignment} from '@fluxer/schema/src/domains/admin/AltchaCaptchaSchemas'; import {resolveDomainMigrationAssignment} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; +import {resolveProfileTimezoneAssignment} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; import {resolveVoiceNoiseSuppressionAssignment} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas'; import {ExperimentAssignmentsResponse} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; @@ -30,12 +31,14 @@ export function ExperimentController(app: HonoApp) { }), async (ctx) => { const instanceConfigRepository = ctx.get('instanceConfigRepository'); - const [delivery, voiceConfig, domainMigrationConfig, altchaCaptchaConfig] = await Promise.all([ - instanceConfigRepository.getExperimentDeliveryConfig(), - instanceConfigRepository.getVoiceNoiseSuppressionConfig(), - instanceConfigRepository.getDomainMigrationConfig(), - instanceConfigRepository.getAltchaCaptchaConfig(), - ]); + const [delivery, voiceConfig, domainMigrationConfig, altchaCaptchaConfig, profileTimezoneConfig] = + await Promise.all([ + instanceConfigRepository.getExperimentDeliveryConfig(), + instanceConfigRepository.getVoiceNoiseSuppressionConfig(), + instanceConfigRepository.getDomainMigrationConfig(), + instanceConfigRepository.getAltchaCaptchaConfig(), + instanceConfigRepository.getProfileTimezoneConfig(), + ]); const userId = ctx.get('user').id.toString(); const body: ExperimentAssignmentsResponse = { poll_interval_seconds: delivery.poll_interval_seconds, @@ -44,6 +47,7 @@ export function ExperimentController(app: HonoApp) { voice_noise_suppression: resolveVoiceNoiseSuppressionAssignment(voiceConfig, userId), domain_migration: resolveDomainMigrationAssignment(domainMigrationConfig, userId), altcha_captcha: resolveAltchaCaptchaAssignment(altchaCaptchaConfig, userId), + profile_timezone: resolveProfileTimezoneAssignment(profileTimezoneConfig, 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 3db541785..54fbc97df 100644 --- a/fluxer_api/src/api/experiment/tests/ExperimentController.test.ts +++ b/fluxer_api/src/api/experiment/tests/ExperimentController.test.ts @@ -14,6 +14,10 @@ import { DEFAULT_DOMAIN_MIGRATION_CONFIG, INERT_DOMAIN_MIGRATION_ASSIGNMENT, } from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; +import { + DEFAULT_PROFILE_TIMEZONE_CONFIG, + INERT_PROFILE_TIMEZONE_ASSIGNMENT, +} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; import { DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG, INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, @@ -62,6 +66,7 @@ describe('GET /experiments', () => { voice_noise_suppression: INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, domain_migration: INERT_DOMAIN_MIGRATION_ASSIGNMENT, altcha_captcha: INERT_ALTCHA_CAPTCHA_ASSIGNMENT, + profile_timezone: INERT_PROFILE_TIMEZONE_ASSIGNMENT, }, }); }); @@ -195,6 +200,56 @@ describe('GET /experiments', () => { expect(body.assignments.altcha_captcha).toEqual({enabled: true}); }); + it('resolves the profile timezone caller through the allowlist and the exclusion list', async () => { + const targeted = await createTestAccount(harness); + const excluded = await createTestAccount(harness); + await getInstanceConfigRepository().setProfileTimezoneConfig({ + ...DEFAULT_PROFILE_TIMEZONE_CONFIG, + enabled: true, + rollout_basis_points: 10000, + included_user_ids: [targeted.userId], + excluded_user_ids: [excluded.userId], + }); + + const targetedBody = await createBuilder(harness, targeted.token) + .get(ENDPOINT) + .execute(); + expect(targetedBody.assignments.profile_timezone).toEqual({enabled: true}); + + const excludedBody = await createBuilder(harness, excluded.token) + .get(ENDPOINT) + .execute(); + expect(excludedBody.assignments.profile_timezone).toEqual({enabled: false}); + }); + + it('bumps the profile timezone 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<{profile_timezone: {config_version: number; enabled: boolean}}>( + harness, + admin.token, + ) + .patch('/admin/instance/config') + .body({profile_timezone: {enabled: true, included_user_ids: [admin.userId]}}) + .execute(); + expect(afterFirst.profile_timezone).toMatchObject({config_version: 1, enabled: true}); + + const afterSecond = await createBuilder<{ + profile_timezone: {config_version: number; rollout_basis_points: number}; + }>(harness, admin.token) + .patch('/admin/instance/config') + .body({profile_timezone: {rollout_basis_points: 2500}}) + .execute(); + expect(afterSecond.profile_timezone).toMatchObject({config_version: 2, rollout_basis_points: 2500}); + + const body = await createBuilder(harness, admin.token).get(ENDPOINT).execute(); + expect(body.assignments.profile_timezone).toEqual({enabled: true}); + }); + it('serves the delivery cadence from the delivery config and not from the voice config', async () => { const account = await createTestAccount(harness); await getInstanceConfigRepository().setExperimentDeliveryConfig({ diff --git a/fluxer_api/src/api/instance/InstanceConfigRepository.ts b/fluxer_api/src/api/instance/InstanceConfigRepository.ts index c33a96062..3ecd202ec 100644 --- a/fluxer_api/src/api/instance/InstanceConfigRepository.ts +++ b/fluxer_api/src/api/instance/InstanceConfigRepository.ts @@ -40,6 +40,10 @@ import { type GatewayRolloutConfig, GatewayRolloutConfigSchema, } from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas'; +import { + type ProfileTimezoneConfig, + ProfileTimezoneConfigSchema, +} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; import { type LegacyPushServiceDeliveryWire, type PushRelayConfig, @@ -75,6 +79,7 @@ const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config'; const PUSH_RELAY_CONFIG_KEY = 'push_service_delivery_config'; const DOMAIN_MIGRATION_CONFIG_KEY = 'domain_migration_config'; const ALTCHA_CAPTCHA_CONFIG_KEY = 'altcha_captcha_config'; +const PROFILE_TIMEZONE_CONFIG_KEY = 'profile_timezone_config'; const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config'; const REGISTRATION_CONFIG_KEY = 'registration_config'; const REGISTRATION_URLS_KEY = 'registration_urls'; @@ -384,6 +389,7 @@ type StoredConfigSection = | 'push relay' | 'domain migration' | 'altcha captcha' + | 'profile timezone' | 'experiment delivery' | 'instance policy' | 'integrations' @@ -551,6 +557,10 @@ function parseStoredAltchaCaptchaConfig(raw: string | null): AltchaCaptchaConfig return parseStoredConfigOrDefault(AltchaCaptchaConfigSchema, raw, 'altcha captcha'); } +function parseStoredProfileTimezoneConfig(raw: string | null): ProfileTimezoneConfig { + return parseStoredConfigOrDefault(ProfileTimezoneConfigSchema, raw, 'profile timezone'); +} + function parseStoredExperimentDeliveryConfig(raw: string | null): ExperimentDeliveryConfig { return parseStoredConfigOrDefault(ExperimentDeliveryConfigSchema, raw, 'experiment delivery'); } @@ -1201,6 +1211,7 @@ export class InstanceConfigRepository { parseStoredPushRelayConfig(snapshot.get(PUSH_RELAY_CONFIG_KEY) ?? null); parseStoredDomainMigrationConfig(snapshot.get(DOMAIN_MIGRATION_CONFIG_KEY) ?? null); parseStoredAltchaCaptchaConfig(snapshot.get(ALTCHA_CAPTCHA_CONFIG_KEY) ?? null); + parseStoredProfileTimezoneConfig(snapshot.get(PROFILE_TIMEZONE_CONFIG_KEY) ?? null); parseStoredExperimentDeliveryConfig(snapshot.get(EXPERIMENT_DELIVERY_CONFIG_KEY) ?? null); const policy = parseStoredInstancePolicyConfig(snapshot.get(INSTANCE_POLICY_CONFIG_KEY) ?? null); checkStoredConfig('registration', () => @@ -1352,6 +1363,27 @@ export class InstanceConfigRepository { ); } + async getProfileTimezoneConfig(): Promise { + const raw = await this.getConfig(PROFILE_TIMEZONE_CONFIG_KEY); + return parseStoredProfileTimezoneConfig(raw); + } + + async setProfileTimezoneConfig(config: ProfileTimezoneConfig): Promise { + await this.updateProfileTimezoneConfig(() => config); + } + + updateProfileTimezoneConfig( + update: (current: ProfileTimezoneConfig) => ProfileTimezoneConfig, + ): Promise { + return this.updateStoredConfig(PROFILE_TIMEZONE_CONFIG_KEY, (raw) => + validateStoredConfig( + ProfileTimezoneConfigSchema, + update(parseStoredProfileTimezoneConfig(raw)), + 'profile timezone', + ), + ); + } + async getExperimentDeliveryConfig(): Promise { const raw = await this.getConfig(EXPERIMENT_DELIVERY_CONFIG_KEY); return parseStoredExperimentDeliveryConfig(raw); diff --git a/fluxer_api/src/api/openapi/openapi.json b/fluxer_api/src/api/openapi/openapi.json index e114b0f07..b527ca2fa 100644 --- a/fluxer_api/src/api/openapi/openapi.json +++ b/fluxer_api/src/api/openapi/openapi.json @@ -22404,14 +22404,8 @@ "anyOf": [{"$ref": "#/components/schemas/Int32Type"}, {"type": "null"}], "description": "The user-selected accent color as an integer" }, - "timezone": { - "description": "The IANA timezone identifier saved by the user. Omitted unless the user has staff access.", - "type": ["string", "null"] - }, - "timezone_privacy_flags": { - "description": "Bitfield controlling who can see the profile timezone. Omitted unless the user has staff access.", - "$ref": "#/components/schemas/ProfileFieldPrivacyFlags" - }, + "timezone": {"description": "The IANA timezone identifier saved by the user", "type": ["string", "null"]}, + "timezone_privacy_flags": {"$ref": "#/components/schemas/ProfileFieldPrivacyFlags"}, "banner": {"description": "The hash of the user profile banner image", "type": ["string", "null"]}, "banner_color": { "anyOf": [{"$ref": "#/components/schemas/Int32Type"}, {"type": "null"}], @@ -25121,11 +25115,11 @@ "anyOf": [{"$ref": "#/components/schemas/ColorType"}, {"type": "null"}] }, "timezone": { - "description": "Staff-only IANA timezone identifier saved for profile local time. Ignored for non-staff users.", + "description": "IANA timezone identifier saved for profile local time. Ignored unless the profile_timezone experiment serves the user.", "type": ["string", "null"] }, "timezone_privacy_flags": { - "description": "Staff-only bitfield controlling who can see the profile timezone. Ignored for non-staff users.", + "description": "Bitfield controlling who can see the profile timezone. Ignored unless the profile_timezone experiment serves the user.", "$ref": "#/components/schemas/ProfileFieldPrivacyFlags" }, "premium_badge_hidden": {"type": "boolean", "description": "Whether to hide the premium badge"}, @@ -27954,7 +27948,8 @@ "properties": { "voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionAssignmentResponse"}, "domain_migration": {"$ref": "#/components/schemas/DomainMigrationAssignmentResponse"}, - "altcha_captcha": {"$ref": "#/components/schemas/AltchaCaptchaAssignmentResponse"} + "altcha_captcha": {"$ref": "#/components/schemas/AltchaCaptchaAssignmentResponse"}, + "profile_timezone": {"$ref": "#/components/schemas/ProfileTimezoneAssignmentResponse"} }, "additionalProperties": false } @@ -31630,6 +31625,12 @@ "additionalProperties": false }, "DonationCurrency": {"type": "string", "enum": ["usd", "eur", "brl", "inr", "pln", "try", "sek", "dkk", "nok"]}, + "ProfileTimezoneAssignmentResponse": { + "type": "object", + "properties": {"enabled": {"type": "boolean"}}, + "required": ["enabled"], + "additionalProperties": false + }, "AltchaCaptchaAssignmentResponse": { "type": "object", "properties": {"enabled": {"type": "boolean"}}, diff --git a/fluxer_api/src/api/user/UserHelpers.ts b/fluxer_api/src/api/user/UserHelpers.ts index 05d726348..44ee5a8c0 100644 --- a/fluxer_api/src/api/user/UserHelpers.ts +++ b/fluxer_api/src/api/user/UserHelpers.ts @@ -3,6 +3,7 @@ import {Config} from '@app/api/Config'; import type {UserRow} from '@app/api/database/types/UserTypes'; import {getCachedInstancePremiumMode} from '@app/api/limits/InstancePremiumModeCache'; +import {getInstanceConfigRepository} from '@app/api/middleware/ServiceSingletons'; import type {User} from '@app/api/models/User'; import {accountPolicyContactHasCapability} from '@app/api/risk/AccountPolicyService'; import {getCachedDeferredPhoneGateEnabled} from '@app/api/risk/DeferredPhoneGateCache'; @@ -13,6 +14,7 @@ import { SuspiciousActivityFlags, UserFlags, } from '@fluxer/constants/src/UserConstants'; +import {resolveProfileTimezoneAssignment} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; import type {RequiredAction} from '@fluxer/schema/src/domains/user/UserResponseSchemas'; import {ms} from 'itty-time'; @@ -289,6 +291,7 @@ export function isBugHunterBotUser(user: Pick): boolean return user.isBot && (user.flags & UserFlags.BUG_HUNTER) !== 0n; } -export function canUseProfileTimezone(user: Pick): boolean { - return (user.flags & UserFlags.STAFF) !== 0n; +export async function canUseProfileTimezone(user: Pick): Promise { + const config = await getInstanceConfigRepository().getProfileTimezoneConfig(); + return resolveProfileTimezoneAssignment(config, user.id.toString()).enabled; } diff --git a/fluxer_api/src/api/user/UserMappers.ts b/fluxer_api/src/api/user/UserMappers.ts index 18c7dbfdc..6e9afc74c 100644 --- a/fluxer_api/src/api/user/UserMappers.ts +++ b/fluxer_api/src/api/user/UserMappers.ts @@ -10,7 +10,7 @@ import type {User} from '@app/api/models/User'; import type {UserGuildSettings} from '@app/api/models/UserGuildSettings'; import type {UserSettings} from '@app/api/models/UserSettings'; import type {WebAuthnCredential} from '@app/api/models/WebAuthnCredential'; -import {canUseProfileTimezone, getRequiredActions} from '@app/api/user/UserHelpers'; +import {getRequiredActions} from '@app/api/user/UserHelpers'; import {canUserAccessNsfwContent} from '@app/api/utils/AgeUtils'; import type {ChannelMessageNotifications} from '@fluxer/constants/src/NotificationConstants'; import { @@ -121,7 +121,6 @@ export function mapUserToPrivateResponse(user: User): UserPrivateResponse { const isStaff = (user.flags & UserFlags.STAFF) !== 0n; const partialResponse = mapUserToPartialResponse(user); const isActuallyPremium = user.isPremium(); - const includeProfileTimezone = canUseProfileTimezone(user); const traitSet = new Set(); for (const trait of user.traits ?? []) { if (trait && trait !== 'premium') { @@ -147,12 +146,8 @@ export function mapUserToPrivateResponse(user: User): UserPrivateResponse { bio: user.bio, pronouns: user.pronouns, accent_color: user.accentColor, - ...(includeProfileTimezone - ? { - timezone: user.timezone, - timezone_privacy_flags: user.timezonePrivacyFlags, - } - : {}), + timezone: user.timezone, + timezone_privacy_flags: user.timezonePrivacyFlags, banner: stripBannerForUser(user), banner_color: user.bannerColor, mfa_enabled: authenticatorTypes.length > 0, diff --git a/fluxer_api/src/api/user/services/UserAccountLookupService.ts b/fluxer_api/src/api/user/services/UserAccountLookupService.ts index c29f391ed..7e2ee10ba 100644 --- a/fluxer_api/src/api/user/services/UserAccountLookupService.ts +++ b/fluxer_api/src/api/user/services/UserAccountLookupService.ts @@ -127,10 +127,10 @@ export class UserAccountLookupService { : await this.getProfileFieldPrivacyContext(userId, targetId); const timezoneVisible = !restrictProfile && - canUseProfileTimezone(user) && user.timezone != null && profileFieldPrivacyContext != null && - this.canViewProfileField(user.timezonePrivacyFlags, profileFieldPrivacyContext); + this.canViewProfileField(user.timezonePrivacyFlags, profileFieldPrivacyContext) && + (await canUseProfileTimezone(user)); const [mutualFriends, mutualGuilds, connections] = await Promise.all([ withMutualFriends && userId !== targetId ? this.getMutualFriends(userId, targetId) : undefined, withMutualGuilds && userId !== targetId ? this.getMutualGuilds(userId, targetId) : undefined, diff --git a/fluxer_api/src/api/user/services/UserAccountProfileService.ts b/fluxer_api/src/api/user/services/UserAccountProfileService.ts index 2feb07a7b..e3294614b 100644 --- a/fluxer_api/src/api/user/services/UserAccountProfileService.ts +++ b/fluxer_api/src/api/user/services/UserAccountProfileService.ts @@ -83,7 +83,8 @@ export class UserAccountProfileService { if (data.accent_color !== undefined) { await this.processAccentColorUpdate({user, accentColor: data.accent_color, updates}); } - const canUpdateProfileTimezone = canUseProfileTimezone(user); + const canUpdateProfileTimezone = + (data.timezone !== undefined || data.timezone_privacy_flags !== undefined) && (await canUseProfileTimezone(user)); if (canUpdateProfileTimezone && data.timezone !== undefined) { const nextTimezone = this.processTimezoneUpdate({user, timezone: data.timezone, updates}); if (nextTimezone !== null && user.timezone === null && data.timezone_privacy_flags === undefined) { diff --git a/fluxer_api/src/api/user/services/UserAccountRequestService.ts b/fluxer_api/src/api/user/services/UserAccountRequestService.ts index e5dcc5c1f..4da7156fd 100644 --- a/fluxer_api/src/api/user/services/UserAccountRequestService.ts +++ b/fluxer_api/src/api/user/services/UserAccountRequestService.ts @@ -85,11 +85,14 @@ function hasProfileCustomizationUpdate(data: UserUpdatePayload): boolean { return EMAIL_VERIFICATION_REQUIRED_PROFILE_UPDATE_FIELDS.some((field) => data[field] !== undefined); } -function stripUnauthorizedProfileTimezoneUpdate( +async function stripUnauthorizedProfileTimezoneUpdate( user: User, body: UserUpdateWithVerificationRequest, -): UserUpdateWithVerificationRequest { - if (canUseProfileTimezone(user)) { +): Promise { + if (body.timezone === undefined && body.timezone_privacy_flags === undefined) { + return body; + } + if (await canUseProfileTimezone(user)) { return body; } const {timezone: _timezone, timezone_privacy_flags: _timezonePrivacyFlags, ...rest} = body; @@ -187,7 +190,7 @@ export class UserAccountRequestService { const {ctx, body, authSession} = params; let {user} = params; const oldEmail = user.email; - const sanitizedBody = stripUnauthorizedProfileTimezoneUpdate(user, body); + const sanitizedBody = await stripUnauthorizedProfileTimezoneUpdate(user, body); const { mfa_method: _mfaMethod, mfa_code: _mfaCode, diff --git a/fluxer_api/src/api/user/tests/UserProfileTimezoneVisibility.test.ts b/fluxer_api/src/api/user/tests/UserProfileTimezoneVisibility.test.ts index ab88fee41..196853ad4 100644 --- a/fluxer_api/src/api/user/tests/UserProfileTimezoneVisibility.test.ts +++ b/fluxer_api/src/api/user/tests/UserProfileTimezoneVisibility.test.ts @@ -3,16 +3,16 @@ import {createTestAccount} from '@app/api/auth/tests/AuthTestUtils'; import {createFriendship} from '@app/api/channel/tests/ChannelTestUtils'; import {acceptInvite, createChannelInvite, createGuild, getChannel} from '@app/api/guild/tests/GuildTestUtils'; +import {getInstanceConfigRepository} from '@app/api/middleware/ServiceSingletons'; import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness'; -import {HTTP_STATUS} from '@app/api/test/TestConstants'; import {createBuilder} from '@app/api/test/TestRequestBuilder'; import { ProfileFieldPrivacyFlags, type ProfilePrivacyLevel, ProfilePrivacyLevels, - UserFlags, } from '@fluxer/constants/src/UserConstants'; import {getCurrentTimeZoneOffsetMinutes} from '@fluxer/date_utils/src/TimeZoneUtils'; +import {DEFAULT_PROFILE_TIMEZONE_CONFIG} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; import type {UserPrivateResponse, UserProfileFullResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas'; import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest'; @@ -31,12 +31,12 @@ async function updateProfileTimezone( return createBuilder(harness, token).patch('/users/@me').body(data).execute(); } -async function setUserFlags(harness: ApiTestHarness, userId: string, flags: bigint): Promise { - await createBuilder(harness, '') - .patch(`/test/users/${userId}/flags`) - .body({flags: flags.toString()}) - .expect(HTTP_STATUS.OK) - .execute(); +async function setProfileTimezoneUsers(userIds: Array): Promise { + await getInstanceConfigRepository().setProfileTimezoneConfig({ + ...DEFAULT_PROFILE_TIMEZONE_CONFIG, + enabled: true, + included_user_ids: userIds, + }); } async function updateProfilePrivacy( @@ -76,7 +76,7 @@ describe('User Profile Timezone Visibility', () => { it('defaults timezone visibility to everyone when a timezone is set', async () => { const targetAccount = await createTestAccount(harness); const viewerAccount = await createTestAccount(harness); - await setUserFlags(harness, targetAccount.userId, UserFlags.STAFF); + await setProfileTimezoneUsers([targetAccount.userId]); const updated = await updateProfileTimezone(harness, targetAccount.token, {timezone: TEST_TIMEZONE}); expect(updated.timezone).toBe(TEST_TIMEZONE); expect(updated.timezone_privacy_flags).toBe(ProfileFieldPrivacyFlags.EVERYONE); @@ -86,7 +86,7 @@ describe('User Profile Timezone Visibility', () => { }); it('restores default timezone visibility when a timezone is set again without explicit flags', async () => { const targetAccount = await createTestAccount(harness); - await setUserFlags(harness, targetAccount.userId, UserFlags.STAFF); + await setProfileTimezoneUsers([targetAccount.userId]); await updateProfileTimezone(harness, targetAccount.token, { timezone: TEST_TIMEZONE, timezone_privacy_flags: 0, @@ -97,7 +97,7 @@ describe('User Profile Timezone Visibility', () => { }); it('hides timezone from the public profile when privacy flags are unset', async () => { const targetAccount = await createTestAccount(harness); - await setUserFlags(harness, targetAccount.userId, UserFlags.STAFF); + await setProfileTimezoneUsers([targetAccount.userId]); await updateProfileTimezone(harness, targetAccount.token, { timezone: TEST_TIMEZONE, timezone_privacy_flags: 0, @@ -109,7 +109,7 @@ describe('User Profile Timezone Visibility', () => { const targetAccount = await createTestAccount(harness); const friendAccount = await createTestAccount(harness); const guildMemberAccount = await createTestAccount(harness); - await setUserFlags(harness, targetAccount.userId, UserFlags.STAFF); + await setProfileTimezoneUsers([targetAccount.userId]); await updateProfileTimezone(harness, targetAccount.token, { timezone: TEST_TIMEZONE, timezone_privacy_flags: ProfileFieldPrivacyFlags.FRIENDS, @@ -125,7 +125,7 @@ describe('User Profile Timezone Visibility', () => { const targetAccount = await createTestAccount(harness); const friendAccount = await createTestAccount(harness); const guildMemberAccount = await createTestAccount(harness); - await setUserFlags(harness, targetAccount.userId, UserFlags.STAFF); + await setProfileTimezoneUsers([targetAccount.userId]); await updateProfileTimezone(harness, targetAccount.token, { timezone: TEST_TIMEZONE, timezone_privacy_flags: ProfileFieldPrivacyFlags.MUTUAL_GUILDS, @@ -140,7 +140,7 @@ describe('User Profile Timezone Visibility', () => { it('hides timezone when full profile privacy restricts the viewer', async () => { const targetAccount = await createTestAccount(harness); const guildMemberAccount = await createTestAccount(harness); - await setUserFlags(harness, targetAccount.userId, UserFlags.STAFF); + await setProfileTimezoneUsers([targetAccount.userId]); await updateProfileTimezone(harness, targetAccount.token, {timezone: TEST_TIMEZONE}); await updateProfilePrivacy(harness, targetAccount.token, ProfilePrivacyLevels.FRIENDS_ONLY); await createSharedGuild(harness, targetAccount.token, guildMemberAccount.token); @@ -148,23 +148,32 @@ describe('User Profile Timezone Visibility', () => { expect(profile.profile_limited).toBe(true); expect(profile.timezone_offset).toBeNull(); }); - it('ignores profile timezone updates from non-staff users', async () => { + it('ignores profile timezone updates from users outside the experiment', async () => { const targetAccount = await createTestAccount(harness, {skipEmailVerification: true}); - const updated = await updateProfileTimezone(harness, targetAccount.token, {timezone: TEST_TIMEZONE}); - expect(updated).not.toHaveProperty('timezone'); - expect(updated).not.toHaveProperty('timezone_privacy_flags'); + const updated = await updateProfileTimezone(harness, targetAccount.token, { + timezone: TEST_TIMEZONE, + timezone_privacy_flags: ProfileFieldPrivacyFlags.FRIENDS, + }); + expect(updated.timezone).toBeNull(); + expect(updated.timezone_privacy_flags).toBe(ProfileFieldPrivacyFlags.EVERYONE); }); - it('hides stored profile timezone after the user no longer has the staff flag', async () => { + it('ignores profile timezone updates from users excluded from a full rollout', async () => { + const targetAccount = await createTestAccount(harness, {skipEmailVerification: true}); + await getInstanceConfigRepository().setProfileTimezoneConfig({ + ...DEFAULT_PROFILE_TIMEZONE_CONFIG, + enabled: true, + rollout_basis_points: 10000, + excluded_user_ids: [targetAccount.userId], + }); + const updated = await updateProfileTimezone(harness, targetAccount.token, {timezone: TEST_TIMEZONE}); + expect(updated.timezone).toBeNull(); + }); + it('hides stored profile timezone after the user leaves the experiment', async () => { const targetAccount = await createTestAccount(harness); const viewerAccount = await createTestAccount(harness); - await setUserFlags(harness, targetAccount.userId, UserFlags.STAFF); + await setProfileTimezoneUsers([targetAccount.userId]); await updateProfileTimezone(harness, targetAccount.token, {timezone: TEST_TIMEZONE}); - await setUserFlags(harness, targetAccount.userId, 0n); - const currentUser = await createBuilder(harness, targetAccount.token) - .get('/users/@me') - .execute(); - expect(currentUser).not.toHaveProperty('timezone'); - expect(currentUser).not.toHaveProperty('timezone_privacy_flags'); + await setProfileTimezoneUsers([]); await createFriendship(harness, targetAccount, viewerAccount); const profile = await getUserProfile(harness, viewerAccount.token, targetAccount.userId); expect(profile.timezone_offset).toBeNull(); diff --git a/fluxer_app/src/features/channel/components/channel_header_components/developer_tools/ResetOptions.ts b/fluxer_app/src/features/channel/components/channel_header_components/developer_tools/ResetOptions.ts index 31a37f5ac..4a8fd03ee 100644 --- a/fluxer_app/src/features/channel/components/channel_header_components/developer_tools/ResetOptions.ts +++ b/fluxer_app/src/features/channel/components/channel_header_components/developer_tools/ResetOptions.ts @@ -31,7 +31,6 @@ export const DEFAULT_DEVELOPER_OPTIONS = { selfHostedModeOverride: false, forceShowVanityURLDisclaimer: false, forceShowVoiceConnection: false, - showProfileTimezoneSettings: false, premiumScenarioOverride: null, premiumTypeOverride: null, premiumLifetimeSequenceOverride: null, diff --git a/fluxer_app/src/features/devtools/components/DeveloperOptionsToggleGroups.ts b/fluxer_app/src/features/devtools/components/DeveloperOptionsToggleGroups.ts index b494a6b0a..46524bf99 100644 --- a/fluxer_app/src/features/devtools/components/DeveloperOptionsToggleGroups.ts +++ b/fluxer_app/src/features/devtools/components/DeveloperOptionsToggleGroups.ts @@ -132,15 +132,6 @@ const FORCE_SHOW_VOICE_CONNECTION_DESCRIPTOR = msg({ message: 'Force show voice connection', comment: 'Developer option label for always showing the voice connection status bar.', }); -const SHOW_PROFILE_TIMEZONE_SETTINGS_DESCRIPTOR = msg({ - message: 'Show profile time zone settings', - comment: 'Developer option label for exposing the staff-only profile timezone section in profile settings.', -}); -const SHOW_PROFILE_TIMEZONE_SETTINGS_DESC_DESCRIPTOR = msg({ - message: 'Expose the staff-only time zone section in profile settings.', - comment: - 'Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle.', -}); const NO_OP_IN_APP_REPORTS_DESCRIPTOR = msg({ message: 'No-op in-app reports', comment: @@ -255,11 +246,6 @@ export const getToggleGroups = (): Array => [ label: FORCE_SHOW_VOICE_CONNECTION_DESCRIPTOR, description: ALWAYS_DISPLAY_THE_VOICE_CONNECTION_STATUS_BAR_IN_DESCRIPTOR, }, - { - key: 'showProfileTimezoneSettings', - label: SHOW_PROFILE_TIMEZONE_SETTINGS_DESCRIPTOR, - description: SHOW_PROFILE_TIMEZONE_SETTINGS_DESC_DESCRIPTOR, - }, { key: 'noOpInAppReports', label: NO_OP_IN_APP_REPORTS_DESCRIPTOR, diff --git a/fluxer_app/src/features/devtools/state/DeveloperOptions.ts b/fluxer_app/src/features/devtools/state/DeveloperOptions.ts index cd9f6e53f..df848c66a 100644 --- a/fluxer_app/src/features/devtools/state/DeveloperOptions.ts +++ b/fluxer_app/src/features/devtools/state/DeveloperOptions.ts @@ -46,7 +46,6 @@ export type DeveloperOptionsState = Readonly<{ selfHostedModeOverride: boolean; forceShowVanityURLDisclaimer: boolean; forceShowVoiceConnection: boolean; - showProfileTimezoneSettings: boolean; premiumScenarioOverride: PremiumScenarioOverride | null; premiumTypeOverride: number | null; premiumLifetimeSequenceOverride: number | null; @@ -129,7 +128,6 @@ class DeveloperOptions implements DeveloperOptionsState { selfHostedModeOverride = false; forceShowVanityURLDisclaimer = false; forceShowVoiceConnection = false; - showProfileTimezoneSettings = false; premiumScenarioOverride: PremiumScenarioOverride | null = null; premiumTypeOverride: number | null = null; premiumLifetimeSequenceOverride: number | null = null; @@ -215,7 +213,6 @@ class DeveloperOptions implements DeveloperOptionsState { 'selfHostedModeOverride', 'forceShowVanityURLDisclaimer', 'forceShowVoiceConnection', - 'showProfileTimezoneSettings', 'premiumScenarioOverride', 'premiumTypeOverride', 'premiumLifetimeSequenceOverride', diff --git a/fluxer_app/src/features/i18n/locales/ar/messages.po b/fluxer_app/src/features/i18n/locales/ar/messages.po index eff91154a..d4c3330f6 100644 --- a/fluxer_app/src/features/i18n/locales/ar/messages.po +++ b/fluxer_app/src/features/i18n/locales/ar/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "تعذّر إرسال رنين إلى هذا الشخص" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "تعذر تنفيذ هذا الأمر: {detail}" @@ -14590,11 +14590,6 @@ msgstr "تصدير بياناتي" msgid "Export themes" msgstr "تصدير السمات" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "عرض قسم المنطقة الزمنية المخصص للموظفين في إعدادات الملف الشخصي." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "لا يوجد أي ويب هوك" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "بلاغات داخل التطبيق بلا إرسال فعلي" @@ -30549,11 +30544,6 @@ msgstr "عرض كلمة المرور" msgid "Show per-device participant volume sliders in voice menus" msgstr "إظهار عناصر تحكم مستوى صوت المشاركين لكل جهاز في قوائم الصوت" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "إظهار إعدادات المنطقة الزمنية في الملف الشخصي" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "تخطي تحذير الروابط الخارجية لجميع النطاقات" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "تجاوز طلب الشبكة عند إرسال بلاغ داخل التطبيق. يستمر المسار إلى شاشة النجاح لتتمكن من تجربة الواجهة، لكن لا يُرسل أي شيء فعليًا إلى فريق السلامة." diff --git a/fluxer_app/src/features/i18n/locales/bg/messages.po b/fluxer_app/src/features/i18n/locales/bg/messages.po index 1545877c3..a628f2a5b 100644 --- a/fluxer_app/src/features/i18n/locales/bg/messages.po +++ b/fluxer_app/src/features/i18n/locales/bg/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Не успяхме да позвъним на този човек" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Командата не можа да бъде изпълнена: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Експортиране на моите данни" msgid "Export themes" msgstr "Експортиране на теми" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Показване на секцията за часова зона (само за служители) в настройките на профила." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Няма уебхукове" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Бездействие при сигнали в приложението" @@ -30549,11 +30544,6 @@ msgstr "Покажи паролата" msgid "Show per-device participant volume sliders in voice menus" msgstr "Показване на плъзгачи за силата на звука на участниците за всяко устройство в гласовите менюта" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Показване на настройките за часова зона в профила" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Пропускане на предупреждението за външни линкове за всички домейни" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Пропускане на мрежовата заявка при изпращане на сигнал в приложението. Процесът все още стига до екрана за успех, така че можеш да тестваш интерфейса, но нищо не се изпраща до екипа за безопасност." diff --git a/fluxer_app/src/features/i18n/locales/cs/messages.po b/fluxer_app/src/features/i18n/locales/cs/messages.po index c9aa249d9..558b84489 100644 --- a/fluxer_app/src/features/i18n/locales/cs/messages.po +++ b/fluxer_app/src/features/i18n/locales/cs/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Tuto osobu se nepodařilo prozvonit" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Nepodařilo se spustit příkaz: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Exportovat moje data" msgid "Export themes" msgstr "Exportovat motivy" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Zobrazit sekci časových pásem určenou pouze pro zaměstnance v nastavení profilu." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Žádné webhooky" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Nahlášení v aplikaci bez odeslání" @@ -30549,11 +30544,6 @@ msgstr "Zobrazit heslo" msgid "Show per-device participant volume sliders in voice menus" msgstr "Zobrazit posuvníky hlasitosti účastníků pro každé zařízení v nabídkách hlasu" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Zobrazit nastavení časového pásma v profilu" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Přeskočit upozornění na externí odkaz pro všechny domény" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Přeskočit síťový požadavek při odeslání nahlášení v aplikaci. Postup i tak přejde na obrazovku úspěšného odeslání, abyste mohli vyzkoušet uživatelské rozhraní, ale bezpečnostnímu týmu se nic neodešle." diff --git a/fluxer_app/src/features/i18n/locales/da/messages.po b/fluxer_app/src/features/i18n/locales/da/messages.po index e98b2a05a..67314106c 100644 --- a/fluxer_app/src/features/i18n/locales/da/messages.po +++ b/fluxer_app/src/features/i18n/locales/da/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Kunne ikke ringe til denne person" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Kunne ikke køre kommandoen: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Eksporter mine data" msgid "Export themes" msgstr "Eksporter temaer" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Vis tidszonesektionen i profilindstillingerne, som kun er tilgængelig for medarbejdere." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Ingen webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Simuler anmeldelser i appen uden at sende dem" @@ -30549,11 +30544,6 @@ msgstr "Vis adgangskode" msgid "Show per-device participant volume sliders in voice menus" msgstr "Vis lydstyrkeskydere for deltagernes enkelte enheder i talemenuer" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Vis indstillinger for profilens tidszone" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Spring advarsel om eksternt link over for alle domæner" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Spring netværkskaldet over, når en anmeldelse indsendes i appen. Forløbet fortsætter til bekræftelsesskærmen, så du kan afprøve brugerfladen, men intet sendes til sikkerhedsteamet." diff --git a/fluxer_app/src/features/i18n/locales/de/messages.po b/fluxer_app/src/features/i18n/locales/de/messages.po index be7380315..1b6d8bb57 100644 --- a/fluxer_app/src/features/i18n/locales/de/messages.po +++ b/fluxer_app/src/features/i18n/locales/de/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Diese Person konnte nicht angerufen werden" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Befehl konnte nicht ausgeführt werden: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Meine Daten exportieren" msgid "Export themes" msgstr "Designs exportieren" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Zeige den Abschnitt für Zeitzoneneinstellungen, der nur für Mitarbeiter sichtbar ist, in den Profiloptionen an." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Keine Webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "In-App-Meldungen ohne Funktion" @@ -30549,11 +30544,6 @@ msgstr "Passwort anzeigen" msgid "Show per-device participant volume sliders in voice menus" msgstr "Lautstärkeregler für einzelne Geräte der Teilnehmer in Sprachchat-Menüs anzeigen" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Einstellungen für die Zeitzone im Profil anzeigen" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Warnung für externe Links für alle Domains überspringen" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Überspringt den Netzwerkaufruf beim Absenden einer In-App-Meldung. Der Ablauf führt weiterhin zur Erfolgsseite, damit du die Oberfläche ausprobieren kannst. Es wird jedoch nichts an das Sicherheitsteam gesendet." diff --git a/fluxer_app/src/features/i18n/locales/el/messages.po b/fluxer_app/src/features/i18n/locales/el/messages.po index 4cb04ddb7..17c76a80d 100644 --- a/fluxer_app/src/features/i18n/locales/el/messages.po +++ b/fluxer_app/src/features/i18n/locales/el/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Δεν ήταν δυνατή η κλήση αυτού του ατόμου" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Δεν ήταν δυνατή η εκτέλεση της εντολής: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Εξαγωγή των δεδομένων μου" msgid "Export themes" msgstr "Εξαγωγή θεμάτων" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Εμφανίζει στις ρυθμίσεις προφίλ την ενότητα ζώνης ώρας που είναι μόνο για το προσωπικό." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Δεν υπάρχουν webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Αναφορές εντός εφαρμογής χωρίς λειτουργία" @@ -30549,11 +30544,6 @@ msgstr "Εμφάνιση κωδικού" msgid "Show per-device participant volume sliders in voice menus" msgstr "Εμφάνιση ρυθμιστικών έντασης ήχου συμμετεχόντων ανά συσκευή στα μενού φωνής" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Εμφάνιση ρυθμίσεων ζώνης ώρας προφίλ" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Παράλειψη προειδοποίησης εξωτερικού συνδέσμου για όλους τους τομείς" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Παράλειψη της κλήσης δικτύου κατά την υποβολή αναφοράς εντός εφαρμογής. Η ροή προχωρά στην οθόνη επιτυχίας, ώστε να δοκιμάσετε το UI, αλλά δεν αποστέλλεται τίποτα στην ομάδα ασφαλείας." 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 008c78e7b..e1f4d7d27 100644 --- a/fluxer_app/src/features/i18n/locales/en-GB/messages.po +++ b/fluxer_app/src/features/i18n/locales/en-GB/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Couldn't ring this person" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Couldn't run that command: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Export my data" msgid "Export themes" msgstr "Export themes" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Expose the staff-only time zone section in profile settings." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "No webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "No-op in-app reports" @@ -30549,11 +30544,6 @@ msgstr "Show password" msgid "Show per-device participant volume sliders in voice menus" msgstr "Show per-device participant volume sliders in voice menus" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Show profile time zone settings" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Skip the external link warning for all domains" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." 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 442dce18f..d16706ab4 100644 --- a/fluxer_app/src/features/i18n/locales/en-US/messages.po +++ b/fluxer_app/src/features/i18n/locales/en-US/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Couldn't ring this person" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Couldn't run that command: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Export my data" msgid "Export themes" msgstr "Export themes" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Expose the staff-only time zone section in profile settings." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "No webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "No-op in-app reports" @@ -30549,11 +30544,6 @@ msgstr "Show password" msgid "Show per-device participant volume sliders in voice menus" msgstr "Show per-device participant volume sliders in voice menus" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Show profile time zone settings" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Skip the external link warning for all domains" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." 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 21b7424de..d5703050e 100644 --- a/fluxer_app/src/features/i18n/locales/es-419/messages.po +++ b/fluxer_app/src/features/i18n/locales/es-419/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "No se pudo llamar a esta persona" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "No se pudo ejecutar el comando: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Exportar mis datos" msgid "Export themes" msgstr "Exportar temas" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Mostrar la sección de zona horaria reservada para el personal en la configuración del perfil." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "No hay webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Simular reportes en la aplicación sin enviarlos" @@ -30549,11 +30544,6 @@ msgstr "Mostrar contraseña" msgid "Show per-device participant volume sliders in voice menus" msgstr "Mostrar controles deslizantes de volumen por participante y dispositivo en los menús de voz" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Mostrar la configuración de zona horaria del perfil" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Omitir la advertencia de enlace externo para todos los dominios" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Omite la llamada de red cuando se envía un reporte en la aplicación. El flujo avanza a la pantalla de éxito para que puedas probar la interfaz de usuario, pero no se envía nada al equipo de seguridad." 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 dc05406a8..949319ee9 100644 --- a/fluxer_app/src/features/i18n/locales/es-ES/messages.po +++ b/fluxer_app/src/features/i18n/locales/es-ES/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "No se ha podido llamar a esta persona" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "No se pudo ejecutar el comando: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Exportar mis datos" msgid "Export themes" msgstr "Exportar temas" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Mostrar la sección de zona horaria reservada para el personal en los ajustes del perfil." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Ningún webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Simular denuncias en la aplicación sin enviarlas" @@ -30549,11 +30544,6 @@ msgstr "Mostrar contraseña" msgid "Show per-device participant volume sliders in voice menus" msgstr "Mostrar controles deslizantes de volumen por participante y dispositivo en los menús de voz" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Mostrar los ajustes de zona horaria del perfil" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Omitir la advertencia de enlace externo para todos los dominios" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Omite la llamada de red cuando se envía una denuncia en la aplicación. El flujo avanza a la pantalla de éxito para que puedas probar la interfaz de usuario, pero no se envía nada al equipo de seguridad." diff --git a/fluxer_app/src/features/i18n/locales/fi/messages.po b/fluxer_app/src/features/i18n/locales/fi/messages.po index 1d950089c..a37be6506 100644 --- a/fluxer_app/src/features/i18n/locales/fi/messages.po +++ b/fluxer_app/src/features/i18n/locales/fi/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Henkilölle ei voitu soittaa" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Komentoa ei voitu suorittaa: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Vie tietoni" msgid "Export themes" msgstr "Vie teemat" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Näytä henkilökunnalle tarkoitettu aikavyöhykeosio profiiliasetuksissa." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Ei webhookeja" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Ei-toimivat sovelluksen sisäiset ilmoitukset" @@ -30549,11 +30544,6 @@ msgstr "Näytä salasana" msgid "Show per-device participant volume sliders in voice menus" msgstr "Näytä laitekohtaiset osallistujien äänenvoimakkuuden liukusäätimet äänivalikoissa" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Näytä profiilin aikavyöhykeasetukset" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Ohita ulkoisen linkin varoitus kaikille verkkotunnuksille" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Ohita verkkopyyntö, kun sovelluksen sisäinen ilmoitus lähetetään. Prosessi etenee silti onnistumisnäyttöön, jotta voit kokeilla käyttöliittymää, mutta mitään ei todellisuudessa lähetetä turvallisuustiimille." diff --git a/fluxer_app/src/features/i18n/locales/fr/messages.po b/fluxer_app/src/features/i18n/locales/fr/messages.po index a6467546e..8ae7ae174 100644 --- a/fluxer_app/src/features/i18n/locales/fr/messages.po +++ b/fluxer_app/src/features/i18n/locales/fr/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Impossible d'appeler cette personne" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Impossible d'exécuter cette commande : {detail}" @@ -14590,11 +14590,6 @@ msgstr "Exporter mes données" msgid "Export themes" msgstr "Exporter les thèmes" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Afficher la section du fuseau horaire dans les paramètres du profil, réservée au personnel." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Aucun webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Désactiver l’envoi réel des signalements dans l’application" @@ -30549,11 +30544,6 @@ msgstr "Afficher le mot de passe" msgid "Show per-device participant volume sliders in voice menus" msgstr "Afficher les curseurs de volume des participants par appareil dans les menus vocaux" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Afficher les paramètres de fuseau horaire du profil" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Ignorer l'avertissement de lien externe pour tous les domaines" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "N’effectue pas l’appel réseau lors de l’envoi d’un signalement depuis l’application. Le parcours continue jusqu’à l’écran de réussite pour permettre de vérifier l’interface, mais rien n’est envoyé à l’équipe de sécurité." diff --git a/fluxer_app/src/features/i18n/locales/he/messages.po b/fluxer_app/src/features/i18n/locales/he/messages.po index 8f6bce27f..28cbcc5b3 100644 --- a/fluxer_app/src/features/i18n/locales/he/messages.po +++ b/fluxer_app/src/features/i18n/locales/he/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "לא ניתן להתקשר לאדם זה" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "לא ניתן היה להריץ את הפקודה: {detail}" @@ -14590,11 +14590,6 @@ msgstr "ייצוא הנתונים שלי" msgid "Export themes" msgstr "ייצוא ערכות נושא" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "הצג את סעיף אזור הזמן המיועד לצוות בלבד בהגדרות הפרופיל." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "אין webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "דיווחים פנימיים לא פעילים" @@ -30549,11 +30544,6 @@ msgstr "הצג סיסמה" msgid "Show per-device participant volume sliders in voice menus" msgstr "הצג פקדי עוצמת קול למשתתפים לפי מכשיר בתפריטי קול" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "הצג הגדרות אזור זמן בפרופיל" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "דלג על אזהרת הקישור החיצוני עבור כל הדומיינים" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "דלג על קריאת הרשת בעת שליחת דיווח מתוך האפליקציה. התהליך עדיין יתקדם למסך ההצלחה כדי שתוכל לבדוק את ממשק המשתמש, אך שום דבר לא יישלח בפועל לצוות הבטיחות." diff --git a/fluxer_app/src/features/i18n/locales/hi/messages.po b/fluxer_app/src/features/i18n/locales/hi/messages.po index 62e765178..0ed2349d0 100644 --- a/fluxer_app/src/features/i18n/locales/hi/messages.po +++ b/fluxer_app/src/features/i18n/locales/hi/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "इस व्यक्ति को कॉल नहीं किया जा सका" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "वह कमांड नहीं चल सका: {detail}" @@ -14590,11 +14590,6 @@ msgstr "मेरा डेटा एक्सपोर्ट करें" msgid "Export themes" msgstr "थीम एक्सपोर्ट करें" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "प्रोफ़ाइल सेटिंग्स में केवल कर्मचारियों के लिए समय क्षेत्र अनुभाग दिखाएँ।" - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "कोई वेबहुक नहीं" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "इन-ऐप रिपोर्ट को नो-ऑप करें" @@ -30549,11 +30544,6 @@ msgstr "पासवर्ड दिखाएं" msgid "Show per-device participant volume sliders in voice menus" msgstr "वॉइस मेन्यू में प्रति-डिवाइस प्रतिभागी वॉल्यूम स्लाइडर दिखाएं" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "प्रोफ़ाइल की समय क्षेत्र सेटिंग्स दिखाएं" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "सभी डोमेन के लिए बाहरी लिंक चेतावनी को छोड़ें" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "इन-ऐप रिपोर्ट सबमिट होने पर नेटवर्क कॉल छोड़ दें। फ़्लो फिर भी सफलता स्क्रीन तक बढ़ता है ताकि आप UI आज़मा सकें, लेकिन सुरक्षा टीम को असल में कुछ नहीं भेजा जाता।" diff --git a/fluxer_app/src/features/i18n/locales/hr/messages.po b/fluxer_app/src/features/i18n/locales/hr/messages.po index d95d0f644..bdddb3b19 100644 --- a/fluxer_app/src/features/i18n/locales/hr/messages.po +++ b/fluxer_app/src/features/i18n/locales/hr/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Nije moguće nazvati ovu osobu" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Nije moguće pokrenuti tu naredbu: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Izvezi moje podatke" msgid "Export themes" msgstr "Izvezi teme" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Prikaži odjeljak s vremenskom zonom rezerviran za osoblje u postavkama profila." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Nema webhookova" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Prijave u aplikaciji bez učinka" @@ -30549,11 +30544,6 @@ msgstr "Prikaži lozinku" msgid "Show per-device participant volume sliders in voice menus" msgstr "Prikaži klizače glasnoće sudionika za svaki uređaj u glasovnim izbornicima" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Prikaži postavke vremenske zone profila" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Preskoči upozorenje o vanjskim poveznicama za sve domene" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Preskoči mrežni poziv kada se pošalje prijava u aplikaciji. Tijek i dalje dolazi do zaslona uspjeha kako biste mogli isprobati sučelje, ali sigurnosnom timu zapravo se ništa ne šalje." diff --git a/fluxer_app/src/features/i18n/locales/hu/messages.po b/fluxer_app/src/features/i18n/locales/hu/messages.po index 97040ac53..608e14cb5 100644 --- a/fluxer_app/src/features/i18n/locales/hu/messages.po +++ b/fluxer_app/src/features/i18n/locales/hu/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Nem sikerült megcsörgetni ezt a felhasználót" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Nem sikerült futtatni a parancsot: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Adataim exportálása" msgid "Export themes" msgstr "Témák exportálása" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Mutassa a profilbeállításokban a csak a munkatársak számára elérhető időzóna-szekciót." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Nincsenek webhookok" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Alkalmazáson belüli bejelentések küldésének szimulálása" @@ -30549,11 +30544,6 @@ msgstr "Jelszó megjelenítése" msgid "Show per-device participant volume sliders in voice menus" msgstr "Résztvevőnként és eszközönként külön hangerőcsúszkák megjelenítése a hangmenükben" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Profil időzóna-beállításainak megjelenítése" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Külső linkre figyelmeztetés kihagyása minden domainhez" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Az alkalmazáson belüli bejelentés beküldésekor kihagyja a hálózati kérést. A folyamat így is a sikert jelző képernyőre lép, hogy kipróbálhasd a felületet, de valójában semmit sem küld el a biztonsági csapatnak." diff --git a/fluxer_app/src/features/i18n/locales/id/messages.po b/fluxer_app/src/features/i18n/locales/id/messages.po index f8b74b82c..90e87a942 100644 --- a/fluxer_app/src/features/i18n/locales/id/messages.po +++ b/fluxer_app/src/features/i18n/locales/id/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Tidak dapat memanggil orang ini" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Tidak dapat menjalankan perintah tersebut: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Ekspor data saya" msgid "Export themes" msgstr "Ekspor tema" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Tampilkan bagian zona waktu khusus staf di pengaturan profil." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Tidak ada webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Laporan dalam aplikasi no-op" @@ -30549,11 +30544,6 @@ msgstr "Tampilkan kata sandi" msgid "Show per-device participant volume sliders in voice menus" msgstr "Tampilkan penggeser volume peserta per perangkat di menu suara" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Tampilkan pengaturan zona waktu profil" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Lewati peringatan tautan eksternal untuk semua domain" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Lewati panggilan jaringan saat laporan dalam aplikasi dikirim. Alur tetap berlanjut ke layar sukses sehingga kamu dapat menguji UI, tetapi tidak ada yang benar-benar dikirim ke tim keamanan." diff --git a/fluxer_app/src/features/i18n/locales/it/messages.po b/fluxer_app/src/features/i18n/locales/it/messages.po index 79a1b9e45..4f3b4c3e9 100644 --- a/fluxer_app/src/features/i18n/locales/it/messages.po +++ b/fluxer_app/src/features/i18n/locales/it/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Impossibile chiamare questa persona" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Impossibile eseguire il comando: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Esporta i miei dati" msgid "Export themes" msgstr "Esporta temi" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Mostra la sezione del fuso orario riservata allo staff nelle impostazioni del profilo." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Nessun webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Segnalazioni in-app senza effetto" @@ -30549,11 +30544,6 @@ msgstr "Mostra password" msgid "Show per-device participant volume sliders in voice menus" msgstr "Mostra i controlli del volume dei partecipanti per dispositivo nei menu vocali" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Mostra le impostazioni del fuso orario del profilo" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Salta l'avviso per i link esterni per tutti i domini" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Salta la chiamata di rete quando viene inviata una segnalazione in-app. Il flusso avanza comunque alla schermata di successo, così puoi testare l'interfaccia utente, ma nulla viene effettivamente inviato al team di sicurezza." diff --git a/fluxer_app/src/features/i18n/locales/ja/messages.po b/fluxer_app/src/features/i18n/locales/ja/messages.po index 310b3ec8a..590385e31 100644 --- a/fluxer_app/src/features/i18n/locales/ja/messages.po +++ b/fluxer_app/src/features/i18n/locales/ja/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "このユーザーを呼び出せませんでした" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "コマンドを実行できませんでした: {detail}" @@ -14590,11 +14590,6 @@ msgstr "データをエクスポート" msgid "Export themes" msgstr "テーマをエクスポート" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "プロフィール設定で、スタッフ専用のタイムゾーンセクションを表示します。" - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "ウェブフックなし" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "アプリ内報告を無効化" @@ -30549,11 +30544,6 @@ msgstr "パスワードを表示" msgid "Show per-device participant volume sliders in voice menus" msgstr "デバイスごとの参加者の音量スライダーを音声メニューに表示" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "プロフィールのタイムゾーン設定を表示" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "すべてのドメインで外部リンクの警告をスキップ" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "アプリ内報告が送信されてもネットワーク呼び出しをスキップします。フローは成功画面に進むためUIを試すことはできますが、実際には安全チームには何も送信されません。" diff --git a/fluxer_app/src/features/i18n/locales/ko/messages.po b/fluxer_app/src/features/i18n/locales/ko/messages.po index 5d7a4d728..fe9992468 100644 --- a/fluxer_app/src/features/i18n/locales/ko/messages.po +++ b/fluxer_app/src/features/i18n/locales/ko/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "이 사람에게 전화를 걸 수 없습니다" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "명령을 실행할 수 없습니다: {detail}" @@ -14590,11 +14590,6 @@ msgstr "내 데이터 내보내기" msgid "Export themes" msgstr "테마 내보내기" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "프로필 설정에서 직원 전용 시간대 섹션을 표시합니다." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "웹훅 없음" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "앱 내 신고 무효화" @@ -30549,11 +30544,6 @@ msgstr "비밀번호 표시" msgid "Show per-device participant volume sliders in voice menus" msgstr "음성 메뉴에서 기기별 참여자 볼륨 슬라이더 표시" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "프로필 시간대 설정 표시" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "모든 도메인에서 외부 링크 경고 건너뛰기" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "앱 내 신고 제출 시 네트워크 호출을 건너뜁니다. UI를 테스트할 수 있도록 성공 화면으로 계속 진행되지만, 안전팀에는 아무것도 전송되지 않습니다." diff --git a/fluxer_app/src/features/i18n/locales/lt/messages.po b/fluxer_app/src/features/i18n/locales/lt/messages.po index 96bbe7789..7a7833c63 100644 --- a/fluxer_app/src/features/i18n/locales/lt/messages.po +++ b/fluxer_app/src/features/i18n/locales/lt/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Nepavyko paskambinti šiam asmeniui" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Nepavyko įvykdyti komandos: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Eksportuoti mano duomenis" msgid "Export themes" msgstr "Eksportuoti temas" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Rodyti tik personalui skirtą laiko juostos skiltį profilio nustatymuose." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Nėra webhookų" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Pranešimai programoje be poveikio" @@ -30549,11 +30544,6 @@ msgstr "Rodyti slaptažodį" msgid "Show per-device participant volume sliders in voice menus" msgstr "Rodyti atskirų įrenginių dalyvių garsumo slankiklius balso meniu" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Rodyti profilio laiko juostos nustatymus" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Praleisti išorinių nuorodų įspėjimą visiems domenams" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Praleisti tinklo užklausą, kai programoje pateikiamas pranešimas. Eiga vis tiek pereina į sėkmės ekraną, kad galėtumėte išbandyti sąsają, bet saugos komandai iš tikrųjų nieko nesiunčiama." diff --git a/fluxer_app/src/features/i18n/locales/nl/messages.po b/fluxer_app/src/features/i18n/locales/nl/messages.po index 2531a5310..5e6cbcf95 100644 --- a/fluxer_app/src/features/i18n/locales/nl/messages.po +++ b/fluxer_app/src/features/i18n/locales/nl/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Deze persoon kon niet worden gebeld" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Kon die opdracht niet uitvoeren: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Mijn gegevens exporteren" msgid "Export themes" msgstr "Thema's exporteren" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Toon het tijdzonegedeelte voor medewerkers in de profielinstellingen." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Geen webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "In-app-meldingen zonder verwerking" @@ -30549,11 +30544,6 @@ msgstr "Wachtwoord tonen" msgid "Show per-device participant volume sliders in voice menus" msgstr "Volumeregelaars voor deelnemers per apparaat tonen in spraakmenu’s" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Instellingen voor tijdzone in profiel tonen" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Externe linkwaarschuwing overslaan voor alle domeinen" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Sla de netwerkaanroep over wanneer een melding in de app wordt ingediend. Het proces gaat wel door naar het bevestigingsscherm, zodat je de interface kunt uitproberen, maar er wordt niets naar het veiligheidsteam verstuurd." diff --git a/fluxer_app/src/features/i18n/locales/no/messages.po b/fluxer_app/src/features/i18n/locales/no/messages.po index fdfda7133..e74b726b3 100644 --- a/fluxer_app/src/features/i18n/locales/no/messages.po +++ b/fluxer_app/src/features/i18n/locales/no/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Kunne ikke ringe denne personen" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Kunne ikke kjøre kommandoen: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Eksporter dataene mine" msgid "Export themes" msgstr "Eksporter temaer" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Vis seksjonen for tidssone i profilinnstillingene som bare er tilgjengelig for ansatte." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Ingen webhooker" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Simuler rapportering i appen uten å sende" @@ -30549,11 +30544,6 @@ msgstr "Vis passord" msgid "Show per-device participant volume sliders in voice menus" msgstr "Vis skyveknapper for deltakervolum per enhet i talemenyer" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Vis innstillinger for tidssone i profil" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Hopp over advarsel om ekstern lenke for alle domener" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Hopp over nettverkskallet når en rapport i appen sendes inn. Flyten går fortsatt videre til bekreftelsesskjermen slik at du kan prøve brukergrensesnittet, men ingenting sendes til sikkerhetsteamet." diff --git a/fluxer_app/src/features/i18n/locales/pl/messages.po b/fluxer_app/src/features/i18n/locales/pl/messages.po index 5dab391ff..4c302fcdc 100644 --- a/fluxer_app/src/features/i18n/locales/pl/messages.po +++ b/fluxer_app/src/features/i18n/locales/pl/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Nie udało się zadzwonić do tej osoby" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Nie udało się uruchomić polecenia: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Eksportuj moje dane" msgid "Export themes" msgstr "Eksportuj motywy" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Pokaż w ustawieniach profilu sekcję strefy czasowej dostępną tylko dla zespołu." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Brak webhooków" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Symuluj zgłoszenia w aplikacji bez ich wysyłania" @@ -30549,11 +30544,6 @@ msgstr "Pokaż hasło" msgid "Show per-device participant volume sliders in voice menus" msgstr "Pokaż suwaki głośności uczestników dla poszczególnych urządzeń w menu głosowych" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Pokaż ustawienia strefy czasowej profilu" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Pomiń ostrzeżenie o linkach zewnętrznych dla wszystkich domen" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Pomiń żądanie sieciowe przy wysyłaniu zgłoszenia w aplikacji. Nadal pojawi się ekran powodzenia, aby umożliwić sprawdzenie interfejsu, ale nic nie zostanie wysłane do zespołu ds. bezpieczeństwa." 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 bc866142e..669b6765b 100644 --- a/fluxer_app/src/features/i18n/locales/pt-BR/messages.po +++ b/fluxer_app/src/features/i18n/locales/pt-BR/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Não foi possível chamar esta pessoa" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Não foi possível executar o comando: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Exportar meus dados" msgid "Export themes" msgstr "Exportar temas" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Mostrar a seção de fuso horário restrita à equipe nas configurações do perfil." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Nenhum webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Simular denúncias no aplicativo sem enviá-las" @@ -30549,11 +30544,6 @@ msgstr "Mostrar senha" msgid "Show per-device participant volume sliders in voice menus" msgstr "Mostrar controles deslizantes de volume por participante por dispositivo nos menus de voz" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Mostrar configurações de fuso horário do perfil" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Pular o aviso de link externo para todos os domínios" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Não faz a chamada de rede ao enviar uma denúncia pelo aplicativo. O fluxo ainda avança até a tela de sucesso para permitir testar a interface, mas nada é enviado à equipe de segurança." diff --git a/fluxer_app/src/features/i18n/locales/ro/messages.po b/fluxer_app/src/features/i18n/locales/ro/messages.po index efa77c086..83cd2fe7d 100644 --- a/fluxer_app/src/features/i18n/locales/ro/messages.po +++ b/fluxer_app/src/features/i18n/locales/ro/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Nu s-a putut apela această persoană" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Nu s-a putut rula comanda: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Exportă-mi datele" msgid "Export themes" msgstr "Exportă teme" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Afișează în setările de profil secțiunea de fus orar disponibilă doar pentru personal." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Niciun webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Rapoarte în aplicație fără efect" @@ -30549,11 +30544,6 @@ msgstr "Afișează parola" msgid "Show per-device participant volume sliders in voice menus" msgstr "Afișează glisoare de volum per dispozitiv pentru participanți în meniurile vocale" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Afișează setările pentru fusul orar al profilului" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Omite avertismentul de link extern pentru toate domeniile" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Omite apelul de rețea la trimiterea unui raport din aplicație. Fluxul avansează în continuare la ecranul de succes, ca să poți testa interfața, dar nu se trimite nimic echipei de siguranță." diff --git a/fluxer_app/src/features/i18n/locales/ru/messages.po b/fluxer_app/src/features/i18n/locales/ru/messages.po index e4df49c86..1e468c182 100644 --- a/fluxer_app/src/features/i18n/locales/ru/messages.po +++ b/fluxer_app/src/features/i18n/locales/ru/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Не удалось позвонить этому пользователю" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Не удалось выполнить команду: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Экспорт моих данных" msgid "Export themes" msgstr "Экспорт тем" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Показывать раздел с настройками часового пояса, доступный только сотрудникам." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Вебхуков нет" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Жалобы в приложении без отправки" @@ -30549,11 +30544,6 @@ msgstr "Показать пароль" msgid "Show per-device participant volume sliders in voice menus" msgstr "Показывать ползунки громкости участников для каждого устройства в голосовых меню" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Показать настройки часового пояса в профиле" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Пропускать предупреждение о внешних ссылках для всех доменов" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." 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 15f6290ec..0f2b71500 100644 --- a/fluxer_app/src/features/i18n/locales/sv-SE/messages.po +++ b/fluxer_app/src/features/i18n/locales/sv-SE/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Kunde inte ringa den här personen" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Kunde inte köra kommandot: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Exportera mina data" msgid "Export themes" msgstr "Exportera teman" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Visa tidszonsinställningarna för personal i profilinställningarna." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Inga webhooks" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Låt anmälningar i appen inte göra något" @@ -30549,11 +30544,6 @@ msgstr "Visa lösenord" msgid "Show per-device participant volume sliders in voice menus" msgstr "Visa volymreglage för deltagare per enhet i röstmenyer" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Visa inställningar för profilens tidszon" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Hoppa över varningen för externa länkar för alla domäner" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Hoppa över nätverksanropet när en anmälan skickas i appen. Flödet går ändå vidare till bekräftelsen så att du kan prova gränssnittet, men inget skickas till säkerhetsteamet." diff --git a/fluxer_app/src/features/i18n/locales/th/messages.po b/fluxer_app/src/features/i18n/locales/th/messages.po index 2c49e175b..41ab01e03 100644 --- a/fluxer_app/src/features/i18n/locales/th/messages.po +++ b/fluxer_app/src/features/i18n/locales/th/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "ไม่สามารถโทรหาบุคคลนี้ได้" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "ไม่สามารถเรียกใช้คำสั่งนั้นได้: {detail}" @@ -14590,11 +14590,6 @@ msgstr "ส่งออกข้อมูลของฉัน" msgid "Export themes" msgstr "ส่งออกธีม" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "แสดงส่วนเขตเวลาสำหรับพนักงานเท่านั้นในการตั้งค่าโปรไฟล์" - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "ไม่มี webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "รายงานในแอปแบบไม่ดำเนินการ" @@ -30549,11 +30544,6 @@ msgstr "แสดงรหัสผ่าน" msgid "Show per-device participant volume sliders in voice menus" msgstr "แสดงแถบเลื่อนระดับเสียงของผู้เข้าร่วมตามอุปกรณ์ในเมนูเสียง" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "แสดงการตั้งค่าเขตเวลาในโปรไฟล์" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "ข้ามคำเตือนลิงก์ภายนอกสำหรับทุกโดเมน" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "ข้ามการเรียกเครือข่ายเมื่อส่งรายงานในแอป โฟลว์จะยังคงดำเนินการต่อไปยังหน้าจอสำเร็จเพื่อให้คุณสามารถทดสอบ UI ได้ แต่จะไม่มีการส่งสิ่งใดไปยังทีมความปลอดภัยจริง" diff --git a/fluxer_app/src/features/i18n/locales/tr/messages.po b/fluxer_app/src/features/i18n/locales/tr/messages.po index 7aa9f59da..4dbfc28eb 100644 --- a/fluxer_app/src/features/i18n/locales/tr/messages.po +++ b/fluxer_app/src/features/i18n/locales/tr/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Bu kişi aranamadı" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Bu komut çalıştırılamadı: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Verilerimi dışa aktar" msgid "Export themes" msgstr "Temaları dışa aktar" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Profil ayarlarında yalnızca çalışanlara özel zaman dilimi bölümünü göster." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Webhook yok" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Uygulama içi raporları atla" @@ -30549,11 +30544,6 @@ msgstr "Parolayı göster" msgid "Show per-device participant volume sliders in voice menus" msgstr "Ses menülerinde cihaza özel katılımcı ses seviyesi kaydırıcılarını göster" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Profil zaman dilimi ayarlarını göster" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Tüm alan adları için harici bağlantı uyarısını atla" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Uygulama içi bir rapor gönderildiğinde ağ çağrısını atla. Akış yine de başarı ekranına ilerler, böylece kullanıcı arayüzünü kullanabilirsiniz, ancak güvenlik ekibine hiçbir şey gönderilmez." diff --git a/fluxer_app/src/features/i18n/locales/uk/messages.po b/fluxer_app/src/features/i18n/locales/uk/messages.po index 281b566f1..232c33e7a 100644 --- a/fluxer_app/src/features/i18n/locales/uk/messages.po +++ b/fluxer_app/src/features/i18n/locales/uk/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Не вдалося зателефонувати цій особі" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Не вдалося виконати команду: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Експортувати мої дані" msgid "Export themes" msgstr "Експортувати теми" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Показувати в налаштуваннях профілю розділ часового поясу, доступний лише для співробітників." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Вебхуків немає" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Скарги в застосунку без дії" @@ -30549,11 +30544,6 @@ msgstr "Показати пароль" msgid "Show per-device participant volume sliders in voice menus" msgstr "Показувати повзунки гучності учасників для кожного пристрою в голосових меню" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Показувати налаштування часового поясу профілю" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Пропускати попередження про зовнішні посилання для всіх доменів" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Пропускати мережевий виклик під час надсилання скарги в застосунку. Процес усе одно переходить до екрана успішного виконання, щоб ви могли перевірити інтерфейс, але команді безпеки нічого не надсилається." diff --git a/fluxer_app/src/features/i18n/locales/vi/messages.po b/fluxer_app/src/features/i18n/locales/vi/messages.po index 869b392c2..d34c4e6f9 100644 --- a/fluxer_app/src/features/i18n/locales/vi/messages.po +++ b/fluxer_app/src/features/i18n/locales/vi/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "Không thể gọi người này" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "Không thể chạy lệnh đó: {detail}" @@ -14590,11 +14590,6 @@ msgstr "Xuất dữ liệu của tôi" msgid "Export themes" msgstr "Xuất chủ đề" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "Hiển thị phần cài đặt múi giờ dành riêng cho nhân viên trong cài đặt hồ sơ." - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "Không có webhook nào" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "Bỏ qua gửi báo cáo trong ứng dụng" @@ -30549,11 +30544,6 @@ msgstr "Hiện mật khẩu" msgid "Show per-device participant volume sliders in voice menus" msgstr "Hiển thị thanh trượt âm lượng người tham gia theo từng thiết bị trong menu thoại" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "Hiển thị cài đặt múi giờ hồ sơ" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "Bỏ qua cảnh báo liên kết ngoài cho tất cả các miền" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "Bỏ qua lệnh gọi mạng khi gửi báo cáo trong ứng dụng. Luồng vẫn chuyển sang màn hình thành công để bạn thử giao diện, nhưng không có gì thực sự được gửi đến đội ngũ an toàn." 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 569e83c2a..54abead7a 100644 --- a/fluxer_app/src/features/i18n/locales/zh-CN/messages.po +++ b/fluxer_app/src/features/i18n/locales/zh-CN/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "无法呼叫此人" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "无法运行该命令:{detail}" @@ -14590,11 +14590,6 @@ msgstr "导出我的数据" msgid "Export themes" msgstr "导出主题" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "在个人资料设置中显示仅供员工访问的时区部分。" - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "无 Webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "空操作应用内举报" @@ -30549,11 +30544,6 @@ msgstr "显示密码" msgid "Show per-device participant volume sliders in voice menus" msgstr "在语音菜单中显示每个设备的参与者音量滑块" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "显示个人资料时区设置" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "跳过所有域名的外部链接警告" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "提交应用内举报时跳过网络请求。流程仍会进入成功界面,方便你测试 UI,但实际不会向安全团队发送任何内容。" 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 2fe2a9dbf..8a34ba842 100644 --- a/fluxer_app/src/features/i18n/locales/zh-TW/messages.po +++ b/fluxer_app/src/features/i18n/locales/zh-TW/messages.po @@ -10084,7 +10084,7 @@ msgid "Couldn't ring this person" msgstr "無法撥打給此人" #. Error message in the use textarea submit hook. Preserve {detail}; it is inserted by code. -#: src/features/messaging/hooks/useTextareaSubmit.ts:48 +#: src/features/messaging/hooks/useTextareaSubmit.ts:53 msgid "Couldn't run that command: {detail}" msgstr "無法執行該指令:{detail}" @@ -14590,11 +14590,6 @@ msgstr "匯出我的資料" msgid "Export themes" msgstr "匯出主題" -#. Developer / debug surface — keep terse and technical. Tooltip / description for the profile timezone settings toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:139 -msgid "Expose the staff-only time zone section in profile settings." -msgstr "在個人檔案設定中顯示僅限員工可見的時區區塊。" - #. Settings search entry label. Names the settings search entry in the settings UI. #. 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/ChatSettingsIndex.ts:343 @@ -22951,7 +22946,7 @@ msgid "No webhooks" msgstr "沒有 Webhook" #. Developer option label for short-circuiting the in-app reporting (IAR) flow. When enabled, submitting a report skips the network call. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:144 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 msgid "No-op in-app reports" msgstr "停用應用程式內檢舉" @@ -30549,11 +30544,6 @@ msgstr "顯示密碼" msgid "Show per-device participant volume sliders in voice menus" msgstr "在語音選單中顯示個別裝置的參與者音量滑桿" -#. Developer option label for exposing the staff-only profile timezone section in profile settings. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:135 -msgid "Show profile time zone settings" -msgstr "顯示個人檔案時區設定" - #. Short label for an advanced message action bar preference. #: src/features/user/components/modals/tabs/advanced_settings_tab/AdvancedChatControls.tsx:56 msgid "Show quick reactions" @@ -30932,7 +30922,7 @@ msgid "Skip the external link warning for all domains" msgstr "略過所有網域的外部連結警告" #. Developer / debug surface — keep terse and technical. Tooltip / description for the no-op in-app reports toggle. -#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:149 +#: src/features/devtools/components/DeveloperOptionsToggleGroups.ts:140 msgid "Skip the network call when an in-app report is submitted. The flow still advances to the success screen so you can exercise the UI, but nothing is actually sent to the safety team." msgstr "提交應用程式內檢舉時,略過網路呼叫。流程仍會進展到成功畫面,讓您可以操作使用者介面,但實際上不會傳送任何內容給安全團隊。" diff --git a/fluxer_app/src/features/user/components/modals/tabs/MyProfileTab.tsx b/fluxer_app/src/features/user/components/modals/tabs/MyProfileTab.tsx index 9ab29228e..bb8dd07f7 100644 --- a/fluxer_app/src/features/user/components/modals/tabs/MyProfileTab.tsx +++ b/fluxer_app/src/features/user/components/modals/tabs/MyProfileTab.tsx @@ -8,7 +8,6 @@ import {PREMIUM_PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstant import {useFormSubmit} from '@app/features/app/hooks/useFormSubmit'; import {LimitResolver} from '@app/features/app/utils/LimitResolverAdapter'; import {isLimitToggleEnabled} from '@app/features/app/utils/LimitUtils'; -import DeveloperOptions from '@app/features/devtools/state/DeveloperOptions'; import type {FlatEmoji} from '@app/features/emoji/types/EmojiTypes'; import {ExpressionPickerSheet} from '@app/features/expressions/components/modals/ExpressionPickerSheet'; import Guilds from '@app/features/guild/state/Guilds'; @@ -54,6 +53,7 @@ import {ProfileTypeSelector} from '@app/features/user/components/modals/tabs/my_ import {TimezoneProfileSettings} from '@app/features/user/components/modals/tabs/my_profile_tab/TimezoneProfileSettings'; import {ProfilePreview} from '@app/features/user/components/profile/ProfilePreview'; import type {Profile} from '@app/features/user/models/Profile'; +import ProfileTimezoneRollout from '@app/features/user/state/ProfileTimezoneRollout'; import Users from '@app/features/user/state/Users'; import * as NicknameUtils from '@app/features/user/utils/NicknameUtils'; import {setMeaningfulFormValue} from '@app/lib/forms/MeaningfulFormValue'; @@ -411,7 +411,7 @@ const MyProfileTabComponent = observer(function MyProfileTabComponent({ ); const showPremiumFeatures = shouldShowPremiumFeatures(); const hasPremium = useMemo(() => showPremiumFeatures && (user?.isPremium() ?? false), [showPremiumFeatures, user]); - const hasProfileTimezoneAccess = (user?.isStaff() ?? false) && DeveloperOptions.showProfileTimezoneSettings; + const hasProfileTimezoneAccess = ProfileTimezoneRollout.enabled; const hasPerGuildProfiles = useMemo( () => isLimitToggleEnabled( diff --git a/fluxer_app/src/features/user/models/User.ts b/fluxer_app/src/features/user/models/User.ts index 132ce803e..5ae3d744c 100644 --- a/fluxer_app/src/features/user/models/User.ts +++ b/fluxer_app/src/features/user/models/User.ts @@ -203,12 +203,10 @@ export class User { this.bannerColor = hasKey(user, 'banner_color') ? (user.banner_color ?? null) : undefined; this.pronouns = hasKey(user, 'pronouns') ? (user.pronouns ?? null) : undefined; this.accentColor = hasKey(user, 'accent_color') ? (user.accent_color ?? null) : undefined; - const hasProfileTimezoneAccess = this._isStaff ?? (this.flags & PublicUserFlags.STAFF) !== 0; - this.timezone = hasProfileTimezoneAccess && hasKey(user, 'timezone') ? (user.timezone ?? null) : undefined; - this.timezonePrivacyFlags = - hasProfileTimezoneAccess && hasKey(user, 'timezone_privacy_flags') - ? (user.timezone_privacy_flags ?? ProfileFieldPrivacyFlags.EVERYONE) - : undefined; + this.timezone = hasKey(user, 'timezone') ? (user.timezone ?? null) : undefined; + this.timezonePrivacyFlags = hasKey(user, 'timezone_privacy_flags') + ? (user.timezone_privacy_flags ?? ProfileFieldPrivacyFlags.EVERYONE) + : undefined; this.mfaEnabled = hasKey(user, 'mfa_enabled') ? user.mfa_enabled : undefined; this.hasVerifiedPhone = hasKey(user, 'has_verified_phone') ? user.has_verified_phone : undefined; this.authenticatorTypes = hasKey(user, 'authenticator_types') @@ -420,13 +418,10 @@ export class User { if (pronouns !== undefined) result.pronouns = pronouns; const accentColor = pickField(this.accentColor, u, 'accent_color', opts); if (accentColor !== undefined) result.accent_color = accentColor; - const hasProfileTimezoneAccess = isStaff ?? (result.flags & PublicUserFlags.STAFF) !== 0; - if (hasProfileTimezoneAccess) { - const timezone = pickField(this.timezone, u, 'timezone', opts); - if (timezone !== undefined) result.timezone = timezone; - const timezonePrivacyFlags = pickField(this.timezonePrivacyFlags, u, 'timezone_privacy_flags', opts); - if (timezonePrivacyFlags !== undefined) result.timezone_privacy_flags = timezonePrivacyFlags; - } + const timezone = pickField(this.timezone, u, 'timezone', opts); + if (timezone !== undefined) result.timezone = timezone; + const timezonePrivacyFlags = pickField(this.timezonePrivacyFlags, u, 'timezone_privacy_flags', opts); + if (timezonePrivacyFlags !== undefined) result.timezone_privacy_flags = timezonePrivacyFlags; const mfaEnabled = pickField(this.mfaEnabled, u, 'mfa_enabled', opts); if (mfaEnabled !== undefined) result.mfa_enabled = mfaEnabled; const hasVerifiedPhone = pickField(this.hasVerifiedPhone, u, 'has_verified_phone', opts); diff --git a/fluxer_app/src/features/user/state/ProfileTimezoneRollout.ts b/fluxer_app/src/features/user/state/ProfileTimezoneRollout.ts new file mode 100644 index 000000000..fc4fb1be6 --- /dev/null +++ b/fluxer_app/src/features/user/state/ProfileTimezoneRollout.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 {readProfileTimezoneAssignment} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas'; + +const logger = new Logger('ProfileTimezoneRollout'); + +class ProfileTimezoneRolloutSelector { + get enabled(): boolean { + try { + return readProfileTimezoneAssignment(ExperimentAssignments.response).enabled; + } catch (err) { + logger.warn('Failed to resolve profile timezone assignment:', err); + return false; + } + } +} + +export const ProfileTimezoneRollout = new ProfileTimezoneRolloutSelector(); + +export default ProfileTimezoneRollout; diff --git a/fluxer_app/src/features/user/utils/ProfileUtils.ts b/fluxer_app/src/features/user/utils/ProfileUtils.ts index f248d208a..d93406e79 100644 --- a/fluxer_app/src/features/user/utils/ProfileUtils.ts +++ b/fluxer_app/src/features/user/utils/ProfileUtils.ts @@ -2,6 +2,7 @@ import {Profile} from '@app/features/user/models/Profile'; import type {User} from '@app/features/user/models/User'; +import ProfileTimezoneRollout from '@app/features/user/state/ProfileTimezoneRollout'; import {ProfileFieldPrivacyFlags, UserPremiumTypes} from '@fluxer/constants/src/UserConstants'; import {getCurrentTimeZoneOffsetMinutes} from '@fluxer/date_utils/src/TimeZoneUtils'; @@ -96,7 +97,7 @@ export function createMockProfile( timezone_offset: options?.previewTimezoneOffset !== undefined ? options.previewTimezoneOffset - : user.isStaff() && (user.timezonePrivacyFlags ?? ProfileFieldPrivacyFlags.EVERYONE) !== 0 + : ProfileTimezoneRollout.enabled && (user.timezonePrivacyFlags ?? ProfileFieldPrivacyFlags.EVERYONE) !== 0 ? getCurrentTimeZoneOffsetMinutes(user.timezone) : null, premium_type: visiblePremiumData.premiumType ?? undefined, diff --git a/fluxer_docs/src/content/docs/admin-api/instance.mdx b/fluxer_docs/src/content/docs/admin-api/instance.mdx index dd08f301d..e2f4505d6 100644 --- a/fluxer_docs/src/content/docs/admin-api/instance.mdx +++ b/fluxer_docs/src/content/docs/admin-api/instance.mdx @@ -38,6 +38,7 @@ Missing settings use the defaults documented below. Invalid stored configuration | push_relay | [push relay configuration](#push-relay-configuration-object) object | Operator consent to the Fluxer-run push relay | | domain_migration | [domain migration configuration](#domain-migration-configuration-object) object | Web domain migration rollout | | altcha_captcha | [ALTCHA captcha configuration](#altcha-captcha-configuration-object) object | ALTCHA proof-of-work captcha rollout | +| profile_timezone | [profile timezone configuration](#profile-timezone-configuration-object) object | Profile timezone rollout | | experiment_delivery | [experiment delivery configuration](#experiment-delivery-configuration-object) object | Cadence every client polls the experiments route on | | registration | [registration configuration](#registration-configuration-object) object | Registration policy, issued URLs, and pending registrations | | self_hosted | boolean | Whether the deployment runs in self-hosted mode | @@ -194,6 +195,27 @@ Each challenge hides its counter between half of `max_counter` and `max_counter` The rollout only changes which provider answers a captcha that is already required. While the instance captcha provider is `none`, it has no effect. +## Profile timezone configuration object + +The instance rollout that lets an account set a profile timezone and show its local time on its profile. [Experiments](/http-api/experiments/#profile-timezone-assignment-object) defines what a client resolves from it. + +### Structure + +| Field | Type | Description | +| --- | --- | --- | +| enabled | boolean | Whether the rollout runs at all (default false) | +| config_version | integer | Revision counter, raised by Fluxer and never accepted from a request | +| rollout_basis_points | integer | Share of accounts the rollout selects, in basis points (0-10000, default 0) | +| rollout_salt | string | Salt of the sampling hash (1-64 printable ASCII characters, default `profile-timezone-v1`) | +| included_user_ids | array[snowflake] | Accounts the rollout always selects, up to 1000 entries (default empty) | +| excluded_user_ids | array[snowflake] | Accounts the rollout never selects, up to 1000 entries (default empty) | + +Every field is present on read. An absent document or missing field uses the defaults above. + +`excluded_user_ids` is applied before `included_user_ids`, so the rollout never selects an account in both. + +An account that leaves the rollout keeps its stored timezone, but Fluxer stops showing it on the profile and refuses changes to it until the account is selected again. + ## Experiment delivery configuration object How often a client polls [Get experiment assignments](/http-api/experiments/#get-experiment-assignments), and how widely those polls are spread. The setting is instance-wide and applies to every experiment at once, so adding an experiment adds no second cadence to tune. @@ -605,6 +627,7 @@ The body has one optional object for each section. Fluxer leaves an absent secti | push_relay? | object | `relay_consent_accepted` from the [push relay configuration](#push-relay-configuration-object) | | domain_migration? | object | Any subset of the [domain migration](#domain-migration-configuration-object) fields | | altcha_captcha? | object | Any subset of the [ALTCHA captcha](#altcha-captcha-configuration-object) fields | +| profile_timezone? | object | Any subset of the [profile timezone](#profile-timezone-configuration-object) fields | | experiment_delivery? | object | Any subset of the [experiment delivery](#experiment-delivery-configuration-object) fields | | registration? | object | `mode` and `admin_registration_urls_enabled` | | app_public?2 | object | `branding`, `setup`, `legal`, and `registration` sub-objects, each merged field by field | @@ -624,6 +647,8 @@ The body has one optional object for each section. Fluxer leaves an absent secti `altcha_captcha` works the same way, over the [ALTCHA captcha configuration](#altcha-captcha-configuration-object) fields and its own `config_version`. +`profile_timezone` works the same way, over the [profile timezone configuration](#profile-timezone-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 @@ -660,7 +685,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_relay`, `domain_migration`, `altcha_captcha`, `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_relay`, `domain_migration`, `altcha_captcha`, `profile_timezone`, `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/experiments.mdx b/fluxer_docs/src/content/docs/http-api/experiments.mdx index de70d753a..2ad142f10 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, `domain_migration` and `altcha_captcha`. +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, `domain_migration`, `altcha_captcha` and `profile_timezone`. 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. @@ -35,6 +35,7 @@ One entry per experiment. The envelope reports this object even when it is empty | 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 | | altcha_captcha? | [ALTCHA captcha assignment](#altcha-captcha-assignment-object) object | The caller's captcha provider assignment | +| profile_timezone? | [profile timezone assignment](#profile-timezone-assignment-object) object | The caller's profile timezone assignment | Ignore unknown experiments and treat a missing experiment as off. @@ -131,6 +132,20 @@ A caller is drawn either by the operator's allowlist or by the sampled share of The assignment is informational. The server applies the same resolution to every request that needs a captcha and names the provider in the [captcha error](/topics/captcha/#altcha-proof-of-work), so a client needs no copy of this value to answer a challenge. +## Profile timezone assignment object + +One resolution of the instance profile timezone rollout against one account. This server version writes the key on every response. + +### Structure + +| Field | Type | Description | +| --- | --- | --- | +| enabled | boolean | Whether the caller can set a profile timezone and show it on their profile | + +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. + +The server applies the same resolution on its own. [Modify current user](/http-api/users/current-user/#modify-current-user) drops `timezone` and `timezone_privacy_flags` for an account outside the rollout, and a [user profile](/http-api/users/#get-user-profile) reports `timezone_offset` as null for a target outside it. + ## Get experiment assignments diff --git a/fluxer_docs/src/content/docs/http-api/users.mdx b/fluxer_docs/src/content/docs/http-api/users.mdx index a850d15f5..c1c650fd0 100644 --- a/fluxer_docs/src/content/docs/http-api/users.mdx +++ b/fluxer_docs/src/content/docs/http-api/users.mdx @@ -161,7 +161,7 @@ The private representation of the current account, returned by [Get current user 6 Always null -7 The pair is present only while the account has the staff flag, and it is absent for every other account +7 This server version writes the pair for every account. Whether the account can change it is decided by the [profile timezone assignment](/http-api/experiments/#profile-timezone-assignment-object) 8 The banner hash is not returned at all while the account lacks the animated banner entitlement, which every profile banner requires @@ -565,7 +565,7 @@ The profile read returned by [Get user profile](#get-user-profile) for one targe 4 The array is empty when the profile is restricted. It otherwise holds only verified connections whose [profile field privacy flags](#profile-field-privacy-flags) admit the caller -5 The value is null unless the profile is unrestricted, the target has the staff flag, the target has stored a timezone, and its [profile field privacy flags](#profile-field-privacy-flags) admit the caller +5 The value is null unless the profile is unrestricted, the [profile timezone assignment](/http-api/experiments/#profile-timezone-assignment-object) of the target is enabled, the target has stored a timezone, and its [profile field privacy flags](#profile-field-privacy-flags) admit the caller 6 The field is present only on a restricted read diff --git a/fluxer_docs/src/content/docs/http-api/users/current-user.mdx b/fluxer_docs/src/content/docs/http-api/users/current-user.mdx index 3ee97fe2b..859921321 100644 --- a/fluxer_docs/src/content/docs/http-api/users/current-user.mdx +++ b/fluxer_docs/src/content/docs/http-api/users/current-user.mdx @@ -108,7 +108,7 @@ Modifies the current account and returns the resulting [user](/http-api/users/#u 9 The value is matched against the instance profile substring blocklist like `username`, `global_name`, and `bio` -10 The pair is staff-only. A request from an account without the [`STAFF`](/http-api/users/#public-user-flags) flag has both fields dropped before the update runs +10 A request from an account whose [profile timezone assignment](/http-api/experiments/#profile-timezone-assignment-object) is not enabled has both fields dropped before the update runs 11 An unrecognised identifier is rejected with `INVALID_TIMEZONE_IDENTIFIER`. Setting a first timezone without `timezone_privacy_flags` sets those flags to `EVERYONE` diff --git a/packages/schema/src/domains/admin/AdminSchemas.ts b/packages/schema/src/domains/admin/AdminSchemas.ts index b554377fe..8909c6a3b 100644 --- a/packages/schema/src/domains/admin/AdminSchemas.ts +++ b/packages/schema/src/domains/admin/AdminSchemas.ts @@ -23,6 +23,10 @@ import { GatewayRolloutConfigResponse, GatewayRolloutConfigUpdateRequest, } from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas'; +import { + ProfileTimezoneConfigResponse, + ProfileTimezoneConfigUpdateRequest, +} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; import {PushRelayConfigResponse, PushRelayConfigUpdateRequest} from '@fluxer/schema/src/domains/admin/PushRelaySchemas'; import { VoiceNoiseSuppressionConfigResponse, @@ -656,6 +660,7 @@ export const InstanceConfigResponse = z.object({ push_relay: PushRelayConfigResponse, domain_migration: DomainMigrationConfigResponse, altcha_captcha: AltchaCaptchaConfigResponse, + profile_timezone: ProfileTimezoneConfigResponse, experiment_delivery: ExperimentDeliveryConfigResponse, registration: InstanceRegistrationResponse, self_hosted: z.boolean(), @@ -695,6 +700,7 @@ export const InstanceConfigUpdateRequest = z.object({ push_relay: PushRelayConfigUpdateRequest.nullish(), domain_migration: DomainMigrationConfigUpdateRequest.nullish(), altcha_captcha: AltchaCaptchaConfigUpdateRequest.nullish(), + profile_timezone: ProfileTimezoneConfigUpdateRequest.nullish(), experiment_delivery: ExperimentDeliveryConfigUpdateRequest.nullish(), registration: z .object({ diff --git a/packages/schema/src/domains/admin/ProfileTimezoneSchemas.test.ts b/packages/schema/src/domains/admin/ProfileTimezoneSchemas.test.ts new file mode 100644 index 000000000..2683fa951 --- /dev/null +++ b/packages/schema/src/domains/admin/ProfileTimezoneSchemas.test.ts @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + DEFAULT_PROFILE_TIMEZONE_CONFIG, + type ProfileTimezoneConfig, + ProfileTimezoneConfigSchema, + ProfileTimezoneConfigUpdateRequest, + resolveProfileTimezoneAssignment, +} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; +import {experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket'; +import {describe, expect, test} from 'vitest'; + +const TARGETED_USER_ID = '1000000000000000001'; + +function createConfig(overrides: Partial = {}): ProfileTimezoneConfig { + return {...DEFAULT_PROFILE_TIMEZONE_CONFIG, included_user_ids: [], excluded_user_ids: [], ...overrides}; +} + +function syntheticUserIds(count: number): Array { + return Array.from({length: count}, (_, index) => (1400000000000000000n + BigInt(index)).toString()); +} + +describe('profile timezone configuration', () => { + test('defaults to disabled', () => { + expect(ProfileTimezoneConfigSchema.parse({})).toEqual({ + enabled: false, + config_version: 0, + rollout_basis_points: 0, + rollout_salt: 'profile-timezone-v1', + included_user_ids: [], + excluded_user_ids: [], + }); + }); + + test('strips config_version from admin updates', () => { + expect(ProfileTimezoneConfigUpdateRequest.safeParse({config_version: 3}).data).toEqual({}); + expect(ProfileTimezoneConfigUpdateRequest.safeParse({rollout_basis_points: 10001}).success).toBe(false); + }); +}); + +describe('resolveProfileTimezoneAssignment', () => { + test('serves nobody while disabled, even included users', () => { + const config = createConfig({rollout_basis_points: 10000, included_user_ids: [TARGETED_USER_ID]}); + expect(resolveProfileTimezoneAssignment(config, TARGETED_USER_ID)).toEqual({enabled: false}); + }); + + test('applies exclusions before inclusions', () => { + const config = createConfig({ + enabled: true, + included_user_ids: [TARGETED_USER_ID], + excluded_user_ids: [TARGETED_USER_ID], + }); + expect(resolveProfileTimezoneAssignment(config, TARGETED_USER_ID)).toEqual({enabled: false}); + }); + + test('serves included users at zero rollout', () => { + const config = createConfig({enabled: true, included_user_ids: [TARGETED_USER_ID]}); + expect(resolveProfileTimezoneAssignment(config, TARGETED_USER_ID)).toEqual({enabled: true}); + }); + + test('buckets the rollout by salt and user id', () => { + const config = createConfig({enabled: true, rollout_basis_points: 2500}); + for (const userId of syntheticUserIds(200)) { + expect(resolveProfileTimezoneAssignment(config, userId).enabled).toBe( + experimentBucket(userId, config.rollout_salt) < 2500, + ); + } + }); +}); diff --git a/packages/schema/src/domains/admin/ProfileTimezoneSchemas.ts b/packages/schema/src/domains/admin/ProfileTimezoneSchemas.ts new file mode 100644 index 000000000..6c0de1466 --- /dev/null +++ b/packages/schema/src/domains/admin/ProfileTimezoneSchemas.ts @@ -0,0 +1,68 @@ +// 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 PROFILE_TIMEZONE_ROLLOUT_BASIS_POINTS_MAX = EXPERIMENT_BUCKET_RESOLUTION; +const PROFILE_TIMEZONE_MAX_TARGETED_USERS = 1000; +const DEFAULT_PROFILE_TIMEZONE_SALT = 'profile-timezone-v1'; + +const PROFILE_TIMEZONE_SALT_PATTERN = /^[\x20-\x7e]+$/u; + +const ProfileTimezoneTargetIdSchema = z.string().regex(/^\d{1,20}$/u); +const ProfileTimezoneTargetedUserIdsSchema = z + .array(ProfileTimezoneTargetIdSchema) + .max(PROFILE_TIMEZONE_MAX_TARGETED_USERS); + +const profileTimezoneConfigFields = { + enabled: z.boolean(), + config_version: z.number().int().min(0), + rollout_basis_points: z.number().int().min(0).max(PROFILE_TIMEZONE_ROLLOUT_BASIS_POINTS_MAX), + rollout_salt: z.string().trim().min(1).max(64).regex(PROFILE_TIMEZONE_SALT_PATTERN), + included_user_ids: ProfileTimezoneTargetedUserIdsSchema, + excluded_user_ids: ProfileTimezoneTargetedUserIdsSchema, +}; + +export const ProfileTimezoneConfigSchema = z.object({ + enabled: profileTimezoneConfigFields.enabled.default(false), + config_version: profileTimezoneConfigFields.config_version.default(0), + rollout_basis_points: profileTimezoneConfigFields.rollout_basis_points.default(0), + rollout_salt: profileTimezoneConfigFields.rollout_salt.default(DEFAULT_PROFILE_TIMEZONE_SALT), + included_user_ids: profileTimezoneConfigFields.included_user_ids.default([]), + excluded_user_ids: profileTimezoneConfigFields.excluded_user_ids.default([]), +}); + +export type ProfileTimezoneConfig = z.infer; + +export const DEFAULT_PROFILE_TIMEZONE_CONFIG: ProfileTimezoneConfig = ProfileTimezoneConfigSchema.parse({}); + +export const ProfileTimezoneConfigUpdateRequest = z + .object(profileTimezoneConfigFields) + .omit({config_version: true}) + .partial(); + +export type ProfileTimezoneConfigUpdateRequest = z.infer; + +export const ProfileTimezoneConfigResponse = ProfileTimezoneConfigSchema; + +export type ProfileTimezoneConfigResponse = z.infer; + +export const ProfileTimezoneAssignmentResponse = z.object({ + enabled: profileTimezoneConfigFields.enabled, +}); + +export type ProfileTimezoneAssignmentResponse = z.infer; + +export const INERT_PROFILE_TIMEZONE_ASSIGNMENT: ProfileTimezoneAssignmentResponse = { + enabled: false, +}; + +export function resolveProfileTimezoneAssignment( + config: ProfileTimezoneConfig, + userId: string, +): ProfileTimezoneAssignmentResponse { + if (!config.enabled) return {...INERT_PROFILE_TIMEZONE_ASSIGNMENT}; + if (config.excluded_user_ids.includes(userId)) return {...INERT_PROFILE_TIMEZONE_ASSIGNMENT}; + if (config.included_user_ids.includes(userId)) return {enabled: true}; + return {enabled: experimentBucket(userId, config.rollout_salt) < config.rollout_basis_points}; +} diff --git a/packages/schema/src/domains/experiment/ExperimentSchemas.ts b/packages/schema/src/domains/experiment/ExperimentSchemas.ts index 80b4b6fac..c5d31a183 100644 --- a/packages/schema/src/domains/experiment/ExperimentSchemas.ts +++ b/packages/schema/src/domains/experiment/ExperimentSchemas.ts @@ -5,6 +5,10 @@ import { DomainMigrationAssignmentResponse, INERT_DOMAIN_MIGRATION_ASSIGNMENT, } from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas'; +import { + INERT_PROFILE_TIMEZONE_ASSIGNMENT, + ProfileTimezoneAssignmentResponse, +} from '@fluxer/schema/src/domains/admin/ProfileTimezoneSchemas'; import { INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT, VoiceNoiseSuppressionAssignmentResponse, @@ -49,6 +53,7 @@ const ExperimentAssignmentsSchema = z.object({ voice_noise_suppression: VoiceNoiseSuppressionAssignmentResponse.optional(), domain_migration: DomainMigrationAssignmentResponse.optional(), altcha_captcha: AltchaCaptchaAssignmentResponse.optional(), + profile_timezone: ProfileTimezoneAssignmentResponse.optional(), }); export const ExperimentAssignmentsResponse = z.object({ @@ -76,3 +81,9 @@ export function readDomainMigrationAssignment( ): DomainMigrationAssignmentResponse { return response.assignments.domain_migration ?? INERT_DOMAIN_MIGRATION_ASSIGNMENT; } + +export function readProfileTimezoneAssignment( + response: ExperimentAssignmentsResponse, +): ProfileTimezoneAssignmentResponse { + return response.assignments.profile_timezone ?? INERT_PROFILE_TIMEZONE_ASSIGNMENT; +} diff --git a/packages/schema/src/domains/user/UserRequestSchemas.ts b/packages/schema/src/domains/user/UserRequestSchemas.ts index f0c1d6e8a..2230f2351 100644 --- a/packages/schema/src/domains/user/UserRequestSchemas.ts +++ b/packages/schema/src/domains/user/UserRequestSchemas.ts @@ -77,13 +77,17 @@ export const UserUpdateRequest = z accent_color: ColorType.nullish().describe('Profile accent color as integer'), timezone: createStringType(1, 128) .nullish() - .describe('Staff-only IANA timezone identifier saved for profile local time. Ignored for non-staff users.'), + .describe( + 'IANA timezone identifier saved for profile local time. Ignored unless the profile_timezone experiment serves the user.', + ), timezone_privacy_flags: createBitflagInt32Type( ProfileFieldPrivacyFlags, ProfileFieldPrivacyFlagsDescriptions, 'Bitfield controlling who can see the profile timezone', 'ProfileFieldPrivacyFlags', - ).describe('Staff-only bitfield controlling who can see the profile timezone. Ignored for non-staff users.'), + ).describe( + 'Bitfield controlling who can see the profile timezone. Ignored unless the profile_timezone experiment serves the user.', + ), premium_badge_hidden: z.boolean().describe('Whether to hide the premium badge'), premium_badge_masked: z.boolean().describe('Whether to mask the premium badge'), premium_badge_timestamp_hidden: z.boolean().describe('Whether to hide premium badge timestamp'), diff --git a/packages/schema/src/domains/user/UserResponseSchemas.ts b/packages/schema/src/domains/user/UserResponseSchemas.ts index 5de803b20..89326c59e 100644 --- a/packages/schema/src/domains/user/UserResponseSchemas.ts +++ b/packages/schema/src/domains/user/UserResponseSchemas.ts @@ -90,11 +90,7 @@ export const UserPrivateResponse = UserPartialResponse.extend({ bio: z.string().nullable().describe('The user biography text'), pronouns: z.string().nullable().describe('The preferred pronouns of the user'), accent_color: Int32Type.nullable().describe('The user-selected accent color as an integer'), - timezone: z - .string() - .nullable() - .optional() - .describe('The IANA timezone identifier saved by the user. Omitted unless the user has staff access.'), + timezone: z.string().nullable().optional().describe('The IANA timezone identifier saved by the user'), timezone_privacy_flags: createBitflagInt32Type( ProfileFieldPrivacyFlags, ProfileFieldPrivacyFlagsDescriptions, @@ -102,7 +98,7 @@ export const UserPrivateResponse = UserPartialResponse.extend({ 'ProfileFieldPrivacyFlags', ) .optional() - .describe('Bitfield controlling who can see the profile timezone. Omitted unless the user has staff access.'), + .describe('Bitfield controlling who can see the profile timezone'), banner: z.string().nullable().describe('The hash of the user profile banner image'), banner_color: Int32Type.nullable().describe('The default banner color if no custom banner is set'), mfa_enabled: z.boolean().describe('Whether multi-factor authentication is enabled'),