forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
27.4 KiB
1//! Strings for this page group. See `docs` in `super` for the pattern.
2//!
3//! Covers the notifications inbox, the repo security page, the appearance settings, the new
4//! repository form and the repo header tab helpers. Shared strings (Save, Security, Notifications,
5//! New repository) come from `super::Key`.
6
7use std::fmt::Display;
8
9use super::Lang;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum K {
13 // Appearance settings.
14 Appearance,
15 ThemeLabel,
16 LanguageLabel,
17 ThemeAndLanguage,
18 MatchSystemHelp,
19 MatchSystem,
20 Light,
21 Dark,
22 MatchBrowser,
23 // Top-bar theme button: what it switches to next.
24 SwitchToLight,
25 SwitchToDark,
26 SwitchToMatchSystem,
27 AppearanceSaved,
28 ChooseTheme,
29 ChooseLanguage,
30 // Notifications: row kinds.
31 KindOpenIssue,
32 KindClosedIssue,
33 KindOpenPull,
34 KindClosedPull,
35 KindDiscussion,
36 KindSecurityAlerts,
37 // Notifications: reason badges.
38 ReasonMentioned,
39 ReasonAssigned,
40 ReasonReviewRequested,
41 ReasonAuthor,
42 ReasonCommented,
43 ReasonStateChange,
44 ReasonSubscribed,
45 ReasonWatching,
46 ReasonReplied,
47 ReasonSecurity,
48 // Notifications: activity verbs (`activity_line` adds the actor).
49 VerbOpened,
50 VerbCommented,
51 VerbClosed,
52 VerbReopened,
53 VerbMerged,
54 VerbReviewed,
55 VerbAssigned,
56 VerbMentionedYou,
57 VerbPushedTo,
58 VerbUpdated,
59 VerbDiscussionReply,
60 // Notifications: why the viewer is subscribed (`receiving_because` wraps them).
61 WhyMentioned,
62 WhyAssigned,
63 WhyReviewRequested,
64 WhyAuthor,
65 WhyCommented,
66 WhySubscribed,
67 WhyWatching,
68 NotReceivingRepo,
69 NotReceivingThread,
70 // Notifications: inbox page.
71 MarkAllAsRead,
72 MarkAllRead,
73 MarkRead,
74 NoNotificationsYet,
75 NoUnreadNotifications,
76 NotificationFilter,
77 Unread,
78 AllTab,
79 Older,
80 // Notifications: subscription box and watch control.
81 Subscribe,
82 Unsubscribe,
83 NotificationLevel,
84 OptParticipating,
85 OptWatching,
86 OptIgnoring,
87 // Repo security page.
88 SecurityOff,
89 LockfilesRead,
90 NoAlerts,
91 ColAdvisory,
92 ColPackage,
93 ColVersion,
94 ColSeverity,
95 ColSummary,
96 ColState,
97 ColOpened,
98 StateOpen,
99 StateDismissed,
100 StateFixed,
101 Dismiss,
102 Reopen,
103 // New repository form.
104 NewOwner,
105 RepoName,
106 DescriptionOptional,
107 VisibilityLegend,
108 PrivateHelp,
109 PublicHelp,
110 DefaultBranch,
111 AddDefaultLabels,
112 MirrorLegend,
113 MirrorIntro,
114 SourceUrl,
115 UsernameOptional,
116 PasswordOrToken,
117 SyncInterval,
118 CreateRepository,
119 OwnerRefused,
120 InvalidRepoName,
121 // Dashboard contribution calendar (`views::heatmap`).
122 ContributionsTitle,
123 ContributionsLastYear,
124 CurrentStreak,
125 LongestStreak,
126 BestDay,
127 Less,
128 More,
129 MonLabel,
130 WedLabel,
131 FriLabel,
132 // Dashboard greeting and "Needs your attention" column.
133 GoodMorning,
134 GoodAfternoon,
135 GoodEvening,
136 NeedsAttention,
137 NothingNeedsYou,
138 ViewAllNotifications,
139 // Shell chrome (`views::mod`): breadcrumb landmark, theme picker, ref selector.
140 BreadcrumbNav,
141 ApplyTheme,
142 RefLabel,
143}
144
145/// Every variant, for the coverage test below.
146#[cfg(test)]
147const ALL: &[K] = &[
148 K::Appearance,
149 K::ThemeLabel,
150 K::LanguageLabel,
151 K::ThemeAndLanguage,
152 K::MatchSystemHelp,
153 K::MatchSystem,
154 K::Light,
155 K::Dark,
156 K::MatchBrowser,
157 K::SwitchToLight,
158 K::SwitchToDark,
159 K::SwitchToMatchSystem,
160 K::AppearanceSaved,
161 K::ChooseTheme,
162 K::ChooseLanguage,
163 K::KindOpenIssue,
164 K::KindClosedIssue,
165 K::KindOpenPull,
166 K::KindClosedPull,
167 K::KindDiscussion,
168 K::KindSecurityAlerts,
169 K::ReasonMentioned,
170 K::ReasonAssigned,
171 K::ReasonReviewRequested,
172 K::ReasonAuthor,
173 K::ReasonCommented,
174 K::ReasonStateChange,
175 K::ReasonSubscribed,
176 K::ReasonWatching,
177 K::ReasonReplied,
178 K::ReasonSecurity,
179 K::VerbOpened,
180 K::VerbCommented,
181 K::VerbClosed,
182 K::VerbReopened,
183 K::VerbMerged,
184 K::VerbReviewed,
185 K::VerbAssigned,
186 K::VerbMentionedYou,
187 K::VerbPushedTo,
188 K::VerbUpdated,
189 K::VerbDiscussionReply,
190 K::WhyMentioned,
191 K::WhyAssigned,
192 K::WhyReviewRequested,
193 K::WhyAuthor,
194 K::WhyCommented,
195 K::WhySubscribed,
196 K::WhyWatching,
197 K::NotReceivingRepo,
198 K::NotReceivingThread,
199 K::MarkAllAsRead,
200 K::MarkAllRead,
201 K::MarkRead,
202 K::NoNotificationsYet,
203 K::NoUnreadNotifications,
204 K::NotificationFilter,
205 K::Unread,
206 K::AllTab,
207 K::Older,
208 K::Subscribe,
209 K::Unsubscribe,
210 K::NotificationLevel,
211 K::OptParticipating,
212 K::OptWatching,
213 K::OptIgnoring,
214 K::SecurityOff,
215 K::LockfilesRead,
216 K::NoAlerts,
217 K::ColAdvisory,
218 K::ColPackage,
219 K::ColVersion,
220 K::ColSeverity,
221 K::ColSummary,
222 K::ColState,
223 K::ColOpened,
224 K::StateOpen,
225 K::StateDismissed,
226 K::StateFixed,
227 K::Dismiss,
228 K::Reopen,
229 K::NewOwner,
230 K::RepoName,
231 K::DescriptionOptional,
232 K::VisibilityLegend,
233 K::PrivateHelp,
234 K::PublicHelp,
235 K::DefaultBranch,
236 K::AddDefaultLabels,
237 K::MirrorLegend,
238 K::MirrorIntro,
239 K::SourceUrl,
240 K::UsernameOptional,
241 K::PasswordOrToken,
242 K::SyncInterval,
243 K::CreateRepository,
244 K::OwnerRefused,
245 K::InvalidRepoName,
246 K::ContributionsTitle,
247 K::ContributionsLastYear,
248 K::CurrentStreak,
249 K::LongestStreak,
250 K::BestDay,
251 K::Less,
252 K::More,
253 K::MonLabel,
254 K::WedLabel,
255 K::FriLabel,
256 K::GoodMorning,
257 K::GoodAfternoon,
258 K::GoodEvening,
259 K::NeedsAttention,
260 K::NothingNeedsYou,
261 K::ViewAllNotifications,
262 K::BreadcrumbNav,
263 K::ApplyTheme,
264 K::RefLabel,
265];
266
267pub fn t(lang: Lang, key: K) -> &'static str {
268 use K::*;
269 use Lang::*;
270 match (key, lang) {
271 (Appearance, En) => "Appearance",
272 (Appearance, Tr) => "Görünüm",
273 (ThemeLabel, En) => "Theme",
274 (ThemeLabel, Tr) => "Tema",
275 (LanguageLabel, En) => "Language",
276 (LanguageLabel, Tr) => "Dil",
277 (ThemeAndLanguage, En) => "Theme and language",
278 (ThemeAndLanguage, Tr) => "Tema ve dil",
279 (MatchSystemHelp, En) => {
280 "Match the system follows your operating system's light or dark setting."
281 }
282 (MatchSystemHelp, Tr) => {
283 "Sistem ile eşleştir, işletim sisteminizin açık veya koyu ayarını izler."
284 }
285 (MatchSystem, En) => "Match the system",
286 (MatchSystem, Tr) => "Sistem ile eşleştir",
287 (Light, En) => "Light",
288 (Light, Tr) => "Açık",
289 (Dark, En) => "Dark",
290 (Dark, Tr) => "Koyu",
291 (MatchBrowser, En) => "Match the browser",
292 (MatchBrowser, Tr) => "Tarayıcı ile eşleştir",
293 (SwitchToLight, En) => "Switch to Light",
294 (SwitchToLight, Tr) => "Açık temaya geç",
295 (SwitchToDark, En) => "Switch to Dark",
296 (SwitchToDark, Tr) => "Koyu temaya geç",
297 (SwitchToMatchSystem, En) => "Switch to match the system",
298 (SwitchToMatchSystem, Tr) => "Sistem ile eşleştir moduna geç",
299 (AppearanceSaved, En) => "Appearance saved.",
300 (AppearanceSaved, Tr) => "Görünüm kaydedildi.",
301 (ChooseTheme, En) => "Choose a theme from the list.",
302 (ChooseTheme, Tr) => "Listeden bir tema seçin.",
303 (ChooseLanguage, En) => "Choose a language from the list.",
304 (ChooseLanguage, Tr) => "Listeden bir dil seçin.",
305 (KindOpenIssue, En) => "Open issue",
306 (KindOpenIssue, Tr) => "Açık sorun",
307 (KindClosedIssue, En) => "Closed issue",
308 (KindClosedIssue, Tr) => "Kapalı sorun",
309 (KindOpenPull, En) => "Open pull request",
310 (KindOpenPull, Tr) => "Açık çekme isteği",
311 (KindClosedPull, En) => "Closed pull request",
312 (KindClosedPull, Tr) => "Kapalı çekme isteği",
313 (KindDiscussion, En) => "Discussion",
314 (KindDiscussion, Tr) => "Tartışma",
315 (KindSecurityAlerts, En) => "Security alerts",
316 (KindSecurityAlerts, Tr) => "Güvenlik uyarıları",
317 (ReasonMentioned, En) => "mentioned",
318 (ReasonMentioned, Tr) => "bahsedildi",
319 (ReasonAssigned, En) => "assigned",
320 (ReasonAssigned, Tr) => "atandı",
321 (ReasonReviewRequested, En) => "review requested",
322 (ReasonReviewRequested, Tr) => "inceleme istendi",
323 (ReasonAuthor, En) => "author",
324 (ReasonAuthor, Tr) => "yazar",
325 (ReasonCommented, En) => "commented",
326 (ReasonCommented, Tr) => "yorumladı",
327 (ReasonStateChange, En) => "state change",
328 (ReasonStateChange, Tr) => "durum değişikliği",
329 (ReasonSubscribed, En) => "subscribed",
330 (ReasonSubscribed, Tr) => "abone",
331 (ReasonWatching, En) => "watching",
332 (ReasonWatching, Tr) => "izleniyor",
333 (ReasonReplied, En) => "replied",
334 (ReasonReplied, Tr) => "yanıtladı",
335 (ReasonSecurity, En) => "security",
336 (ReasonSecurity, Tr) => "güvenlik",
337 (VerbOpened, En) => "opened",
338 (VerbOpened, Tr) => "açıldı",
339 (VerbCommented, En) => "commented",
340 (VerbCommented, Tr) => "yorumlandı",
341 (VerbClosed, En) => "closed",
342 (VerbClosed, Tr) => "kapatıldı",
343 (VerbReopened, En) => "reopened",
344 (VerbReopened, Tr) => "yeniden açıldı",
345 (VerbMerged, En) => "merged",
346 (VerbMerged, Tr) => "birleştirildi",
347 (VerbReviewed, En) => "reviewed",
348 (VerbReviewed, Tr) => "incelendi",
349 (VerbAssigned, En) => "assigned",
350 (VerbAssigned, Tr) => "atandı",
351 (VerbMentionedYou, En) => "mentioned you",
352 (VerbMentionedYou, Tr) => "sizden bahsedildi",
353 (VerbPushedTo, En) => "pushed to",
354 (VerbPushedTo, Tr) => "gönderim yapıldı",
355 (VerbUpdated, En) => "updated",
356 (VerbUpdated, Tr) => "güncellendi",
357 (VerbDiscussionReply, En) => "commented on your discussion",
358 (VerbDiscussionReply, Tr) => "tartışmanıza yorum yapıldı",
359 (WhyMentioned, En) => "you were mentioned",
360 (WhyMentioned, Tr) => "sizden bahsedildiği için",
361 (WhyAssigned, En) => "you were assigned",
362 (WhyAssigned, Tr) => "size atandığınız için",
363 (WhyReviewRequested, En) => "you were asked to review",
364 (WhyReviewRequested, Tr) => "incelemeniz istendiği için",
365 (WhyAuthor, En) => "you authored this",
366 (WhyAuthor, Tr) => "bunu siz yazdığınız için",
367 (WhyCommented, En) => "you commented on this",
368 (WhyCommented, Tr) => "buna yorum yaptığınız için",
369 (WhySubscribed, En) => "you subscribed to this",
370 (WhySubscribed, Tr) => "buna abone olduğunuz için",
371 (WhyWatching, En) => "you are watching this repository",
372 (WhyWatching, Tr) => "bu depoyu izlediğiniz için",
373 (NotReceivingRepo, En) => {
374 "You're not receiving notifications from this repository. Mentions still notify you."
375 }
376 (NotReceivingRepo, Tr) => {
377 "Bu depodan bildirim almıyorsunuz. Yine de bahsedildiğinizde bildirim alırsınız."
378 }
379 (NotReceivingThread, En) => "You're not receiving notifications from this thread.",
380 (NotReceivingThread, Tr) => "Bu konudan bildirim almıyorsunuz.",
381 (MarkAllAsRead, En) => "Mark all as read",
382 (MarkAllAsRead, Tr) => "Tümünü okundu olarak işaretle",
383 (MarkAllRead, En) => "Mark all read",
384 (MarkAllRead, Tr) => "Tümünü okundu işaretle",
385 (MarkRead, En) => "Mark read",
386 (MarkRead, Tr) => "Okundu işaretle",
387 (NoNotificationsYet, En) => "No notifications yet.",
388 (NoNotificationsYet, Tr) => "Henüz bildirim yok.",
389 (NoUnreadNotifications, En) => "You have no unread notifications.",
390 (NoUnreadNotifications, Tr) => "Okunmamış bildiriminiz yok.",
391 (NotificationFilter, En) => "Notification filter",
392 (NotificationFilter, Tr) => "Bildirim filtresi",
393 (Unread, En) => "Unread",
394 (Unread, Tr) => "Okunmamış",
395 (AllTab, En) => "All",
396 (AllTab, Tr) => "Tümü",
397 (Older, En) => "Older",
398 (Older, Tr) => "Daha eski",
399 (Subscribe, En) => "Subscribe",
400 (Subscribe, Tr) => "Abone ol",
401 (Unsubscribe, En) => "Unsubscribe",
402 (Unsubscribe, Tr) => "Abonelikten çık",
403 (NotificationLevel, En) => "Notification level",
404 (NotificationLevel, Tr) => "Bildirim düzeyi",
405 (OptParticipating, En) => "Participating",
406 (OptParticipating, Tr) => "Katılımcı",
407 (OptWatching, En) => "Watching",
408 (OptWatching, Tr) => "İzleniyor",
409 (OptIgnoring, En) => "Ignoring",
410 (OptIgnoring, Tr) => "Yok sayılıyor",
411 (SecurityOff, En) => {
412 "Security advisories are off on this instance, so no lockfile is scanned. An administrator can turn them on with the [advisories] section of the config."
413 }
414 (SecurityOff, Tr) => {
415 "Bu sunucuda güvenlik tavsiyeleri kapalı, bu yüzden hiçbir kilit dosyası taranmıyor. Bir yönetici bunları yapılandırma dosyasındaki [advisories] bölümünden açabilir."
416 }
417 (LockfilesRead, En) => {
418 "Lockfiles read: Cargo.lock, package-lock.json (v2 and v3) and go.sum, up to three directories deep. Alerts open after a push to the default branch, and close when the dependency moves past the fix."
419 }
420 (LockfilesRead, Tr) => {
421 "Okunan kilit dosyaları: Cargo.lock, package-lock.json (v2 ve v3) ve go.sum; en fazla üç dizin derinliğine kadar. Uyarılar varsayılan dala gönderim yapıldıktan sonra açılır ve bağımlılık düzeltme sürümünü geçtiğinde kapanır."
422 }
423 (NoAlerts, En) => "No alerts.",
424 (NoAlerts, Tr) => "Uyarı yok.",
425 (ColAdvisory, En) => "Advisory",
426 (ColAdvisory, Tr) => "Güvenlik tavsiyesi",
427 (ColPackage, En) => "Package",
428 (ColPackage, Tr) => "Paket",
429 (ColVersion, En) => "Version",
430 (ColVersion, Tr) => "Sürüm",
431 (ColSeverity, En) => "Severity",
432 (ColSeverity, Tr) => "Önem",
433 (ColSummary, En) => "Summary",
434 (ColSummary, Tr) => "Özet",
435 (ColState, En) => "State",
436 (ColState, Tr) => "Durum",
437 (ColOpened, En) => "Opened",
438 (ColOpened, Tr) => "Açılış",
439 (StateOpen, En) => "Open",
440 (StateOpen, Tr) => "Açık",
441 (StateDismissed, En) => "Dismissed",
442 (StateDismissed, Tr) => "Yoksayıldı",
443 (StateFixed, En) => "Fixed",
444 (StateFixed, Tr) => "Düzeltildi",
445 (Dismiss, En) => "Dismiss",
446 (Dismiss, Tr) => "Yoksay",
447 (Reopen, En) => "Reopen",
448 (Reopen, Tr) => "Yeniden aç",
449 (NewOwner, En) => "Owner",
450 (NewOwner, Tr) => "Sahip",
451 (RepoName, En) => "Repository name",
452 (RepoName, Tr) => "Depo adı",
453 (DescriptionOptional, En) => "Description (optional)",
454 (DescriptionOptional, Tr) => "Açıklama (isteğe bağlı)",
455 (VisibilityLegend, En) => "Visibility",
456 (VisibilityLegend, Tr) => "Görünürlük",
457 (PrivateHelp, En) => "Private: only you and collaborators can see it",
458 (PrivateHelp, Tr) => "Özel: yalnızca siz ve ortak çalışanlar görebilir",
459 (PublicHelp, En) => "Public: anyone can read it",
460 (PublicHelp, Tr) => "Herkese açık: herkes okuyabilir",
461 (DefaultBranch, En) => "Default branch",
462 (DefaultBranch, Tr) => "Varsayılan dal",
463 (AddDefaultLabels, En) => {
464 "Add default labels (bug, documentation, enhancement and the rest)"
465 }
466 (AddDefaultLabels, Tr) => {
467 "Varsayılan etiketleri ekle (bug, documentation, enhancement ve diğerleri)"
468 }
469 (MirrorLegend, En) => "Mirror an existing repository (optional)",
470 (MirrorLegend, Tr) => "Mevcut bir depoyu yansıt (isteğe bağlı)",
471 (MirrorIntro, En) => {
472 "The new repository starts empty and is filled from this URL: branches and tags are fetched now and on the interval. Pushes to it are refused."
473 }
474 (MirrorIntro, Tr) => {
475 "Yeni depo boş başlar ve bu URL'den doldurulur: dallar ve etiketler şimdi ve belirli aralıklarla çekilir. Bu depoya yapılan gönderimler reddedilir."
476 }
477 (SourceUrl, En) => "Source URL",
478 (SourceUrl, Tr) => "Kaynak URL",
479 (UsernameOptional, En) => "Username (optional)",
480 (UsernameOptional, Tr) => "Kullanıcı adı (isteğe bağlı)",
481 (PasswordOrToken, En) => "Password or token (optional)",
482 (PasswordOrToken, Tr) => "Parola veya belirteç (isteğe bağlı)",
483 (SyncInterval, En) => "Sync interval in minutes (0 = manual only, at least 10)",
484 (SyncInterval, Tr) => "Eşitleme aralığı (dakika; 0 = yalnızca elle, en az 10)",
485 (CreateRepository, En) => "Create repository",
486 (CreateRepository, Tr) => "Depo oluştur",
487 (OwnerRefused, En) => "you cannot create repositories for that owner",
488 (OwnerRefused, Tr) => "bu sahip için depo oluşturamazsınız",
489 (InvalidRepoName, En) => "invalid repository name",
490 (InvalidRepoName, Tr) => "geçersiz depo adı",
491 (ContributionsTitle, En) => "Contributions",
492 (ContributionsTitle, Tr) => "Katkılar",
493 (ContributionsLastYear, En) => "contributions in the last year",
494 (ContributionsLastYear, Tr) => "son bir yılda katkı",
495 (CurrentStreak, En) => "Current streak",
496 (CurrentStreak, Tr) => "Mevcut seri",
497 (LongestStreak, En) => "Longest streak",
498 (LongestStreak, Tr) => "En uzun seri",
499 (BestDay, En) => "Best day",
500 (BestDay, Tr) => "En iyi gün",
501 (Less, En) => "Less",
502 (Less, Tr) => "Az",
503 (More, En) => "More",
504 (More, Tr) => "Çok",
505 (MonLabel, En) => "Mon",
506 (MonLabel, Tr) => "Pzt",
507 (WedLabel, En) => "Wed",
508 (WedLabel, Tr) => "Çar",
509 (FriLabel, En) => "Fri",
510 (FriLabel, Tr) => "Cum",
511 (GoodMorning, En) => "Good morning",
512 (GoodMorning, Tr) => "Günaydın",
513 (GoodAfternoon, En) => "Good afternoon",
514 (GoodAfternoon, Tr) => "İyi günler",
515 (GoodEvening, En) => "Good evening",
516 (GoodEvening, Tr) => "İyi akşamlar",
517 (NeedsAttention, En) => "Needs your attention",
518 (NeedsAttention, Tr) => "Dikkatinizi bekleyenler",
519 (NothingNeedsYou, En) => "Nothing needs you right now.",
520 (NothingNeedsYou, Tr) => "Şu an dikkatinizi bekleyen bir şey yok.",
521 (ViewAllNotifications, En) => "View all notifications",
522 (ViewAllNotifications, Tr) => "Tüm bildirimleri gör",
523 (BreadcrumbNav, En) => "Path",
524 (BreadcrumbNav, Tr) => "Yol",
525 (ApplyTheme, En) => "Apply",
526 (ApplyTheme, Tr) => "Uygula",
527 (RefLabel, En) => "Ref",
528 (RefLabel, Tr) => "Referans",
529 }
530}
531
532/// `1,284` in English, `1.284` in Turkish.
533pub fn thousands(lang: Lang, n: u64) -> String {
534 let sep = match lang {
535 Lang::En => ',',
536 Lang::Tr => '.',
537 };
538 let digits = n.to_string();
539 let mut out = String::with_capacity(digits.len() + digits.len() / 3);
540 for (i, ch) in digits.chars().enumerate() {
541 if i > 0 && (digits.len() - i).is_multiple_of(3) {
542 out.push(sep);
543 }
544 out.push(ch);
545 }
546 out
547}
548
549/// `12 days` / `12 gün`, for a streak length.
550pub fn streak_days(lang: Lang, n: u64) -> String {
551 match lang {
552 Lang::En => format!("{n} day{}", if n == 1 { "" } else { "s" }),
553 Lang::Tr => format!("{n} gün"),
554 }
555}
556
557/// `1 contribution` / `3 contributions`, and `3 katkı` in Turkish.
558pub fn contributions_count(lang: Lang, n: u64) -> String {
559 match lang {
560 Lang::En => format!("{n} contribution{}", if n == 1 { "" } else { "s" }),
561 Lang::Tr => format!("{n} katkı"),
562 }
563}
564
565/// Short month name, `1..=12`. Turkish uses the usual three-letter forms.
566pub fn month_abbrev(lang: Lang, month: i64) -> &'static str {
567 const EN: [&str; 12] = [
568 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
569 ];
570 const TR: [&str; 12] = [
571 "Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara",
572 ];
573 let index = (month.clamp(1, 12) - 1) as usize;
574 match lang {
575 Lang::En => EN[index],
576 Lang::Tr => TR[index],
577 }
578}
579
580/// `16 Oct 2026` in English, `16 Eki 2026` in Turkish.
581pub fn date_text(lang: Lang, year: i64, month: i64, day: i64) -> String {
582 format!("{day} {} {year}", month_abbrev(lang, month))
583}
584
585/// `16 Oct 2025 to 16 Oct 2026` in English; Turkish joins the two dates with a dash.
586pub fn date_range(lang: Lang, from: &str, to: &str) -> String {
587 match lang {
588 Lang::En => format!("{from} to {to}"),
589 Lang::Tr => format!("{from} – {to}"),
590 }
591}
592
593/// The contribution calendar's motivating line. `today` is today's count, `streak` the current
594/// streak (which counts today when `today > 0`).
595pub fn heatmap_motivation(lang: Lang, today: u64, streak: u64) -> String {
596 match (lang, today > 0, streak > 0) {
597 (Lang::En, true, _) => format!("Nice \u{2014} that's a {streak}-day streak."),
598 (Lang::En, false, true) => format!(
599 "Keep it going: one commit today makes it a {}-day streak.",
600 streak + 1
601 ),
602 (Lang::En, false, false) => "Start a new streak today.".to_owned(),
603 (Lang::Tr, true, _) => format!("Harika \u{2014} {streak} günlük bir seri!"),
604 (Lang::Tr, false, true) => format!(
605 "Devam edin: bugün bir katkı, seriyi {} güne çıkarır.",
606 streak + 1
607 ),
608 (Lang::Tr, false, false) => "Bugün yeni bir seri başlatın.".to_owned(),
609 }
610}
611
612/// The calendar's accessible summary, for the grid's `aria-label`.
613pub fn heatmap_summary(lang: Lang, total: u64, current: u64, longest: u64) -> String {
614 let total = thousands(lang, total);
615 let current = streak_days(lang, current);
616 let longest = streak_days(lang, longest);
617 match lang {
618 Lang::En => format!(
619 "Contribution calendar: {total} contributions in the last year. Current streak {current}, longest streak {longest}."
620 ),
621 Lang::Tr => format!(
622 "Katkı takvimi: son bir yılda {total} katkı. Mevcut seri {current}, en uzun seri {longest}."
623 ),
624 }
625}
626
627/// A calendar cell's hover text: `3 contributions on 16 Oct 2026`.
628pub fn contributions_on(lang: Lang, n: u64, date: &str) -> String {
629 match lang {
630 Lang::En => format!("{} on {date}", contributions_count(lang, n)),
631 Lang::Tr => format!("{date}: {}", contributions_count(lang, n)),
632 }
633}
634
635/// The activity line for an inbox row: the verb, then the actor when there is one. English reads
636/// "opened by alice"; Turkish reads "alice tarafından açıldı", or "Açıldı" with no actor.
637pub fn activity_line(lang: Lang, verb: K, actor: Option<&str>) -> String {
638 let verb = t(lang, verb);
639 match (lang, actor) {
640 (Lang::En, Some(actor)) => format!("{verb} by {actor}"),
641 (Lang::En, None) => verb.to_owned(),
642 (Lang::Tr, Some(actor)) => format!("{actor} tarafından {verb}"),
643 (Lang::Tr, None) => capitalize(verb),
644 }
645}
646
647/// "You're receiving notifications because ..." for a reason fragment such as `WhyMentioned`.
648pub fn receiving_because(lang: Lang, why: K) -> String {
649 let why = t(lang, why);
650 match lang {
651 Lang::En => format!("You're receiving notifications because {why}."),
652 Lang::Tr => format!("{} bildirim alıyorsunuz.", capitalize(why)),
653 }
654}
655
656/// "3 watching" / "3 izleyici", the watch control's count.
657pub fn watchers_count(lang: Lang, watchers: u64) -> String {
658 match lang {
659 Lang::En => format!("{watchers} watching"),
660 Lang::Tr => format!("{watchers} izleyici"),
661 }
662}
663
664/// "3 new alerts" / "3 yeni uyarı".
665pub fn new_alerts(lang: Lang, count: u64) -> String {
666 match lang {
667 Lang::En if count == 1 => format!("{count} new alert"),
668 Lang::En => format!("{count} new alerts"),
669 Lang::Tr => format!("{count} yeni uyarı"),
670 }
671}
672
673/// The security page's summary of how many dependencies were read and how many alerts are open.
674pub fn dependencies_summary(lang: Lang, dependencies: u64, open: usize) -> String {
675 match lang {
676 Lang::En => format!(
677 "{dependencies} dependencies were read from the lockfiles on the default branch. {open} open alerts."
678 ),
679 Lang::Tr => format!(
680 "Varsayılan daldaki kilit dosyalarından {dependencies} bağımlılık okundu. {open} açık uyarı var."
681 ),
682 }
683}
684
685/// The new-repo error for a description over the limit.
686pub fn description_too_long(lang: Lang, max: usize) -> String {
687 match lang {
688 Lang::En => format!("description must be at most {max} characters"),
689 Lang::Tr => format!("açıklama en fazla {max} karakter olabilir"),
690 }
691}
692
693/// The new-repo error when the git repository could not be created.
694pub fn git_init_failed(lang: Lang, error: impl Display) -> String {
695 match lang {
696 Lang::En => format!("could not create the git repository: {error}"),
697 Lang::Tr => format!("git deposu oluşturulamadı: {error}"),
698 }
699}
700
701/// Upper-cases the first character. Turkish verbs here start with a plain letter, so the
702/// default mapping is right.
703/// "Good morning, Ahmet" for an hour before noon, "Good afternoon" before 18:00 and "Good
704/// evening" otherwise. `hour` is 0-23 in UTC.
705pub fn greeting(lang: Lang, hour: u8, name: &str) -> String {
706 let phrase = match hour {
707 0..12 => K::GoodMorning,
708 12..18 => K::GoodAfternoon,
709 _ => K::GoodEvening,
710 };
711 format!("{}, {name}", t(lang, phrase))
712}
713
714/// `1 repository` / `3 repositories`, and `3 depo` in Turkish.
715pub fn repositories_count(lang: Lang, n: u64) -> String {
716 match lang {
717 Lang::En => format!("{n} repositor{}", if n == 1 { "y" } else { "ies" }),
718 Lang::Tr => format!("{n} depo"),
719 }
720}
721
722fn capitalize(text: &str) -> String {
723 let mut chars = text.chars();
724 match chars.next() {
725 Some(first) => first.to_uppercase().chain(chars).collect(),
726 None => String::new(),
727 }
728}
729
730#[cfg(test)]
731mod tests {
732 use super::*;
733
734 #[test]
735 fn every_variant_has_both_languages() {
736 for key in ALL {
737 assert!(!t(Lang::En, *key).is_empty(), "{key:?} has no English");
738 assert!(!t(Lang::Tr, *key).is_empty(), "{key:?} has no Turkish");
739 }
740 }
741
742 #[test]
743 fn turkish_differs_from_english_for_translated_phrases() {
744 let differing = ALL
745 .iter()
746 .filter(|key| t(Lang::En, **key) != t(Lang::Tr, **key))
747 .count();
748 assert!(differing >= 3, "only {differing} phrases are translated");
749 assert_eq!(t(Lang::Tr, K::Appearance), "Görünüm");
750 }
751
752 #[test]
753 fn activity_lines_keep_the_english_wording() {
754 assert_eq!(
755 activity_line(Lang::En, K::VerbOpened, Some("alice")),
756 "opened by alice"
757 );
758 assert_eq!(activity_line(Lang::En, K::VerbUpdated, None), "updated");
759 assert_eq!(
760 activity_line(Lang::Tr, K::VerbOpened, Some("alice")),
761 "alice tarafından açıldı"
762 );
763 assert_eq!(activity_line(Lang::Tr, K::VerbUpdated, None), "Güncellendi");
764 }
765
766 #[test]
767 fn plural_helpers_match_english() {
768 assert_eq!(new_alerts(Lang::En, 1), "1 new alert");
769 assert_eq!(new_alerts(Lang::En, 2), "2 new alerts");
770 assert_eq!(new_alerts(Lang::Tr, 3), "3 yeni uyarı");
771 assert_eq!(
772 receiving_because(Lang::En, K::WhySubscribed),
773 "You're receiving notifications because you subscribed to this."
774 );
775 }
776}