forge
ExploreLog in

Tahinli/forge

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