-7
i18n/en-us/actions.ftl
-7
i18n/en-us/actions.ftl
···
61
61
# Status options for events
62
62
status-active = Active
63
63
64
-
# Status options for RSVPs
65
-
status-going = Going
66
-
status-interested = Interested
67
-
status-not-going = Not Going
68
-
69
-
# Event modes
70
-
71
64
# Location types
72
65
location-type-venue = Venue
73
66
location-type-coordinates = Coordinates
+8
i18n/en-us/ui.ftl
+8
i18n/en-us/ui.ftl
···
333
333
community-lexicon-calendar-event-status-postponed = Postponed
334
334
community-lexicon-calendar-event-status-planned = Planned
335
335
336
+
# User Status Labels (for RSVP and role indicators)
337
+
status-organizer = Organizer
338
+
status-legacy = Legacy
339
+
status-going = Going
340
+
status-interested = Interested
341
+
status-not-going = Not Going
342
+
status-unknown = Unknown
343
+
336
344
# Distance and Location
337
345
distance-km = {$distance} km away
338
346
location-unknown = Location not specified
-6
i18n/fr-ca/actions.ftl
-6
i18n/fr-ca/actions.ftl
···
61
61
# Options de statut pour les événements
62
62
status-active = Actif
63
63
64
-
# Options de statut pour les RSVP
65
-
status-going = J'y vais
66
-
status-interested = Intéressé
67
-
status-not-going = Je n'y vais pas
68
-
69
-
# Modes d'événement
70
64
71
65
# Types d'emplacement
72
66
location-type-venue = Lieu
+8
i18n/fr-ca/ui.ftl
+8
i18n/fr-ca/ui.ftl
···
333
333
community-lexicon-calendar-event-status-postponed = Reporté
334
334
community-lexicon-calendar-event-status-planned = Planifié
335
335
336
+
# Étiquettes de statut utilisateur (pour les indicateurs de RSVP et de rôle)
337
+
status-organizer = Organisateur
338
+
status-legacy = Ancienne version
339
+
status-going = Je participe
340
+
status-interested = Intéressé
341
+
status-not-going = Je n'y vais pas
342
+
status-unknown = Inconnu
343
+
336
344
# Distance et lieu
337
345
distance-km = {$distance} km de distance
338
346
location-unknown = Lieu non spécifié
+17
-103
src/filtering/facets.rs
+17
-103
src/filtering/facets.rs
···
344
344
// Define date ranges to calculate
345
345
let date_ranges = vec![
346
346
("today", "Today"),
347
-
("this_week", "This Week"),
348
-
("this_month", "This Month"),
349
-
("next_week", "Next Week"),
350
-
("next_month", "Next Month"),
347
+
("this-week", "This Week"),
348
+
("this-month", "This Month"),
349
+
("next-week", "Next Week"),
350
+
("next-month", "Next Month"),
351
351
];
352
352
353
353
for (key, _label) in date_ranges {
···
376
376
// Define date ranges to calculate
377
377
let date_ranges = vec![
378
378
("today", "Today"),
379
-
("this_week", "This Week"),
380
-
("this_month", "This Month"),
381
-
("next_week", "Next Week"),
382
-
("next_month", "Next Month"),
379
+
("this-week", "This Week"),
380
+
("this-month", "This Month"),
381
+
("next-week", "Next Week"),
382
+
("next-month", "Next Month"),
383
383
];
384
384
385
385
for (key, _label) in date_ranges {
···
412
412
let end = now.date_naive().and_hms_opt(23, 59, 59).unwrap().and_utc();
413
413
(start, end)
414
414
}
415
-
"this_week" => {
415
+
"this_week" | "this-week" => {
416
416
let days_since_monday = now.weekday().num_days_from_monday();
417
417
let start = (now - chrono::Duration::days(days_since_monday as i64))
418
418
.date_naive().and_hms_opt(0, 0, 0).unwrap().and_utc();
419
419
let end = start + chrono::Duration::days(6);
420
420
(start, end)
421
421
}
422
-
"this_month" => {
422
+
"this_month" | "this-month" => {
423
423
let start = now.date_naive().with_day(1).unwrap().and_hms_opt(0, 0, 0).unwrap().and_utc();
424
424
let end = if now.month() == 12 {
425
425
chrono::NaiveDate::from_ymd_opt(now.year() + 1, 1, 1).unwrap()
···
428
428
}.and_hms_opt(0, 0, 0).unwrap().and_utc();
429
429
(start, end)
430
430
}
431
-
"next_week" => {
431
+
"next_week" | "next-week" => {
432
432
let days_until_next_monday = 7 - now.weekday().num_days_from_monday();
433
433
let start = (now + chrono::Duration::days(days_until_next_monday as i64))
434
434
.date_naive().and_hms_opt(0, 0, 0).unwrap().and_utc();
435
435
let end = start + chrono::Duration::days(6);
436
436
(start, end)
437
437
}
438
-
"next_month" => {
438
+
"next_month" | "next-month" => {
439
439
let start = if now.month() == 12 {
440
440
chrono::NaiveDate::from_ymd_opt(now.year() + 1, 1, 1).unwrap()
441
441
} else {
···
588
588
/// Generate i18n key for mode facets
589
589
pub fn generate_mode_i18n_key(mode: &str) -> String {
590
590
match mode {
591
-
"community.lexicon.calendar.event#inperson" => "mode-inperson".to_string(),
591
+
"community.lexicon.calendar.event#inperson" => "mode-in-person".to_string(),
592
592
"community.lexicon.calendar.event#virtual" => "mode-virtual".to_string(),
593
593
"community.lexicon.calendar.event#hybrid" => "mode-hybrid".to_string(),
594
594
_ => format!("mode-{}", mode.to_lowercase().replace('#', "-").replace('.', "-")),
···
607
607
}
608
608
}
609
609
610
-
/// Get display name for mode facet value (locale-aware)
611
-
#[allow(dead_code)]
612
-
fn mode_display_name(mode: &str, locale: &LanguageIdentifier) -> String {
613
-
let lang_str = locale.language.as_str();
614
-
match mode {
615
-
"community.lexicon.calendar.event#inperson" => match lang_str {
616
-
"en" => "In-Person".to_string(),
617
-
"fr" => "En personne".to_string(),
618
-
_ => "In-Person".to_string(),
619
-
},
620
-
"community.lexicon.calendar.event#virtual" => match lang_str {
621
-
"en" => "Virtual".to_string(),
622
-
"fr" => "Virtuel".to_string(),
623
-
_ => "Virtual".to_string(),
624
-
},
625
-
"community.lexicon.calendar.event#hybrid" => match lang_str {
626
-
"en" => "Hybrid".to_string(),
627
-
"fr" => "Hybride".to_string(),
628
-
_ => "Hybrid".to_string(),
629
-
},
630
-
_ => mode.to_string(),
631
-
}
632
-
}
610
+
633
611
634
-
/// Get display name for status facet value (locale-aware)
635
-
#[allow(dead_code)]
636
-
fn status_display_name(status: &str, locale: &LanguageIdentifier) -> String {
637
-
let lang_str = locale.language.as_str();
638
-
match status {
639
-
"community.lexicon.calendar.event#scheduled" => match lang_str {
640
-
"en" => "Scheduled".to_string(),
641
-
"fr" => "Planifié".to_string(),
642
-
_ => "Scheduled".to_string(),
643
-
},
644
-
"community.lexicon.calendar.event#rescheduled" => match lang_str {
645
-
"en" => "Rescheduled".to_string(),
646
-
"fr" => "Reporté".to_string(),
647
-
_ => "Rescheduled".to_string(),
648
-
},
649
-
"community.lexicon.calendar.event#cancelled" => match lang_str {
650
-
"en" => "Cancelled".to_string(),
651
-
"fr" => "Annulé".to_string(),
652
-
_ => "Cancelled".to_string(),
653
-
},
654
-
"community.lexicon.calendar.event#postponed" => match lang_str {
655
-
"en" => "Postponed".to_string(),
656
-
"fr" => "Reporté".to_string(),
657
-
_ => "Postponed".to_string(),
658
-
},
659
-
"community.lexicon.calendar.event#planned" => match lang_str {
660
-
"en" => "Planned".to_string(),
661
-
"fr" => "Planifié".to_string(),
662
-
_ => "Planned".to_string(),
663
-
},
664
-
_ => status.to_string(),
665
-
}
666
-
}
612
+
667
613
668
-
/// Get display name for date range facet (locale-aware)
669
-
#[allow(dead_code)]
670
-
fn date_range_display_name(range_key: &str, locale: &LanguageIdentifier) -> String {
671
-
let lang_str = locale.language.as_str();
672
-
match range_key {
673
-
"today" => match lang_str {
674
-
"en" => "Today".to_string(),
675
-
"fr" => "Aujourd'hui".to_string(),
676
-
_ => "Today".to_string(),
677
-
},
678
-
"this_week" => match lang_str {
679
-
"en" => "This Week".to_string(),
680
-
"fr" => "Cette semaine".to_string(),
681
-
_ => "This Week".to_string(),
682
-
},
683
-
"this_month" => match lang_str {
684
-
"en" => "This Month".to_string(),
685
-
"fr" => "Ce mois".to_string(),
686
-
_ => "This Month".to_string(),
687
-
},
688
-
"next_week" => match lang_str {
689
-
"en" => "Next Week".to_string(),
690
-
"fr" => "La semaine prochaine".to_string(),
691
-
_ => "Next Week".to_string(),
692
-
},
693
-
"next_month" => match lang_str {
694
-
"en" => "Next Month".to_string(),
695
-
"fr" => "Le mois prochain".to_string(),
696
-
_ => "Next Month".to_string(),
697
-
},
698
-
_ => range_key.to_string(),
699
-
}
700
-
}
614
+
701
615
702
616
/// Get translated facet name with fallback to formatted readable name
703
617
pub fn get_translated_facet_name(&self, i18n_key: &str, locale: &LanguageIdentifier) -> String {
···
749
663
fn test_generate_mode_i18n_key() {
750
664
assert_eq!(
751
665
FacetCalculator::generate_mode_i18n_key("community.lexicon.calendar.event#inperson"),
752
-
"mode-inperson"
666
+
"mode-in-person"
753
667
);
754
668
755
669
assert_eq!(
+25
-3
src/filtering/hydration.rs
+25
-3
src/filtering/hydration.rs
···
199
199
format!("Failed to parse AT-URI: {}", event.aturi)
200
200
))?;
201
201
202
-
// Get organizer display name (fallback to DID prefix if not available)
203
-
let organizer_display_name = format!("did:{}...", &event.did[4..12]);
202
+
// Get organizer handle for URL generation
203
+
let organizer_handle = handle_for_did(&self.pool, &event.did).await
204
+
.map(|h| h.handle)
205
+
.unwrap_or_else(|_| event.did.clone());
206
+
207
+
// Generate the correct URL format: /{handle_slug}/{event_rkey}
208
+
let handle_slug = crate::http::utils::slug_from_handle(&organizer_handle);
209
+
let rkey = parsed_uri.2; // The rkey is the third component of the parsed AT-URI
210
+
let site_url = format!("/{}/{}", handle_slug, rkey);
211
+
212
+
// Get organizer display name using the same logic as in handle_filter_events.rs
213
+
let organizer_display_name = {
214
+
// Only use the handle if it looks like a proper handle (contains a dot and doesn't start with "did:")
215
+
if organizer_handle.contains('.') && !organizer_handle.starts_with("did:") {
216
+
organizer_handle.clone()
217
+
} else {
218
+
// Fallback to a shortened DID if no proper handle is available
219
+
if event.did.len() > 20 {
220
+
format!("{}...", &event.did[..20])
221
+
} else {
222
+
event.did.clone()
223
+
}
224
+
}
225
+
};
204
226
205
227
let event_view = EventView {
206
-
site_url: "https://smokesignal.events".to_string(),
228
+
site_url,
207
229
aturi: event.aturi.clone(),
208
230
cid: event.cid.clone(),
209
231
repository: parsed_uri.0,
+2
-2
src/filtering/i18n_integration_test.rs
+2
-2
src/filtering/i18n_integration_test.rs
···
12
12
fn test_i18n_translation_functions() {
13
13
// Test mode key generation
14
14
let mode_key = FacetCalculator::generate_mode_i18n_key("community.lexicon.calendar.event#inperson");
15
-
assert_eq!(mode_key, "mode-inperson");
15
+
assert_eq!(mode_key, "mode-in-person");
16
16
17
17
println!("Mode i18n key generation test:");
18
18
println!(" inperson -> {}", mode_key);
···
49
49
// Test the i18n key generation functions
50
50
assert_eq!(
51
51
FacetCalculator::generate_mode_i18n_key("community.lexicon.calendar.event#inperson"),
52
-
"mode-inperson"
52
+
"mode-in-person"
53
53
);
54
54
55
55
assert_eq!(
+28
-4
src/http/handle_filter_events.rs
+28
-4
src/http/handle_filter_events.rs
···
346
346
}),
347
347
348
348
organizer_did: event.did.clone(),
349
-
organizer_display_name: hydrated.creator_handle
350
-
.as_ref()
351
-
.map(|h| h.handle.clone())
352
-
.unwrap_or_else(|| event.did.clone()),
349
+
organizer_display_name: {
350
+
let display_name = hydrated.creator_handle
351
+
.as_ref()
352
+
.and_then(|h| {
353
+
// Log what we received for debugging
354
+
tracing::debug!("Handle for DID {}: '{}'", event.did, h.handle);
355
+
356
+
// Only use the handle if it looks like a proper handle (contains a dot and doesn't start with "did:")
357
+
if h.handle.contains('.') && !h.handle.starts_with("did:") {
358
+
Some(h.handle.clone())
359
+
} else {
360
+
tracing::debug!("Rejecting handle '{}' as it doesn't look like a proper handle", h.handle);
361
+
None
362
+
}
363
+
})
364
+
.unwrap_or_else(|| {
365
+
// Fallback to a shortened DID if no proper handle is available
366
+
tracing::debug!("Using fallback display name for DID: {}", event.did);
367
+
if event.did.len() > 20 {
368
+
format!("{}...", &event.did[..20])
369
+
} else {
370
+
event.did.clone()
371
+
}
372
+
});
373
+
374
+
tracing::debug!("Final organizer_display_name for DID {}: '{}'", event.did, display_name);
375
+
display_name
376
+
},
353
377
354
378
starts_at_machine: event_details.starts_at.as_ref().map(|dt| dt.to_rfc3339()),
355
379
starts_at_human: event_details.starts_at.as_ref().map(|dt| {
+34
src/http/utils.rs
+34
src/http/utils.rs
···
156
156
}
157
157
ret.to_string()
158
158
}
159
+
160
+
/// Convert a handle to a URL-safe slug format
161
+
///
162
+
/// This function takes a handle (which may be in various formats like `example.com`,
163
+
/// `@example.com`, `did:web:example.com`, or `did:plc:abc123`) and converts it to
164
+
/// a URL-safe slug that can be used in URL paths.
165
+
///
166
+
/// # Arguments
167
+
/// * `handle` - The handle to convert to a slug
168
+
///
169
+
/// # Returns
170
+
/// * A URL-safe slug string
171
+
pub fn slug_from_handle(handle: &str) -> String {
172
+
// Strip common prefixes to get the core handle
173
+
let trimmed = if let Some(value) = handle.strip_prefix("at://") {
174
+
value
175
+
} else if let Some(value) = handle.strip_prefix('@') {
176
+
value
177
+
} else {
178
+
handle
179
+
};
180
+
181
+
// For DID formats, we need to handle them specially
182
+
if let Some(web_handle) = trimmed.strip_prefix("did:web:") {
183
+
// For did:web: format, use the domain part
184
+
web_handle.to_string()
185
+
} else if trimmed.starts_with("did:plc:") {
186
+
// For did:plc: format, use the full DID as the slug
187
+
trimmed.to_string()
188
+
} else {
189
+
// For regular handles, use as-is (they're already domain-like)
190
+
trimmed.to_string()
191
+
}
192
+
}