//! Strings for this page group. See `docs` in `super` for the pattern. //! //! Covers the notifications inbox, the repo security page, the appearance settings, the new //! repository form and the repo header tab helpers. Shared strings (Save, Security, Notifications, //! New repository) come from `super::Key`. use std::fmt::Display; use super::Lang; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum K { // Appearance settings. Appearance, ThemeLabel, LanguageLabel, ThemeAndLanguage, MatchSystemHelp, MatchSystem, Light, Dark, MatchBrowser, // Top-bar theme button: what it switches to next. SwitchToLight, SwitchToDark, SwitchToMatchSystem, AppearanceSaved, ChooseTheme, ChooseLanguage, // Notifications: row kinds. KindOpenIssue, KindClosedIssue, KindOpenPull, KindClosedPull, KindDiscussion, KindSecurityAlerts, // Notifications: reason badges. ReasonMentioned, ReasonAssigned, ReasonReviewRequested, ReasonAuthor, ReasonCommented, ReasonStateChange, ReasonSubscribed, ReasonWatching, ReasonReplied, ReasonSecurity, // Notifications: activity verbs (`activity_line` adds the actor). VerbOpened, VerbCommented, VerbClosed, VerbReopened, VerbMerged, VerbReviewed, VerbAssigned, VerbMentionedYou, VerbPushedTo, VerbUpdated, VerbDiscussionReply, // Notifications: why the viewer is subscribed (`receiving_because` wraps them). WhyMentioned, WhyAssigned, WhyReviewRequested, WhyAuthor, WhyCommented, WhySubscribed, WhyWatching, NotReceivingRepo, NotReceivingThread, // Notifications: inbox page. MarkAllAsRead, MarkAllRead, MarkRead, NoNotificationsYet, NoUnreadNotifications, NotificationFilter, Unread, AllTab, Older, // Notifications: subscription box and watch control. Subscribe, Unsubscribe, OptParticipating, OptWatching, OptIgnoring, WatchDescParticipating, WatchDescWatching, WatchDescIgnoring, // Repo security page. SecurityOff, LockfilesRead, NoAlerts, ColAdvisory, ColPackage, ColVersion, ColSeverity, ColSummary, ColState, ColOpened, StateOpen, StateDismissed, StateFixed, Dismiss, Reopen, // New repository form. NewOwner, RepoName, DescriptionOptional, VisibilityLegend, PrivateHelp, PublicHelp, DefaultBranch, AddDefaultLabels, MirrorLegend, MirrorIntro, SourceUrl, UsernameOptional, PasswordOrToken, SyncInterval, CreateRepository, OwnerRefused, InvalidRepoName, // Dashboard contribution calendar (`views::heatmap`). ContributionsTitle, ContributionsLastYear, CurrentStreak, LongestStreak, BestDay, Less, More, MonLabel, WedLabel, FriLabel, // Dashboard greeting and "Needs your attention" column. GoodMorning, GoodAfternoon, GoodEvening, NeedsAttention, NothingNeedsYou, ViewAllNotifications, // Shell chrome (`views::mod`): breadcrumb landmark, theme picker, ref selector. BreadcrumbNav, ApplyTheme, RefLabel, // Top-bar user menu: the viewer's public profile and the settings editor. YourProfile, EditProfile, } /// Every variant, for the coverage test below. #[cfg(test)] const ALL: &[K] = &[ K::Appearance, K::ThemeLabel, K::LanguageLabel, K::ThemeAndLanguage, K::MatchSystemHelp, K::MatchSystem, K::Light, K::Dark, K::MatchBrowser, K::SwitchToLight, K::SwitchToDark, K::SwitchToMatchSystem, K::AppearanceSaved, K::ChooseTheme, K::ChooseLanguage, K::KindOpenIssue, K::KindClosedIssue, K::KindOpenPull, K::KindClosedPull, K::KindDiscussion, K::KindSecurityAlerts, K::ReasonMentioned, K::ReasonAssigned, K::ReasonReviewRequested, K::ReasonAuthor, K::ReasonCommented, K::ReasonStateChange, K::ReasonSubscribed, K::ReasonWatching, K::ReasonReplied, K::ReasonSecurity, K::VerbOpened, K::VerbCommented, K::VerbClosed, K::VerbReopened, K::VerbMerged, K::VerbReviewed, K::VerbAssigned, K::VerbMentionedYou, K::VerbPushedTo, K::VerbUpdated, K::VerbDiscussionReply, K::WhyMentioned, K::WhyAssigned, K::WhyReviewRequested, K::WhyAuthor, K::WhyCommented, K::WhySubscribed, K::WhyWatching, K::NotReceivingRepo, K::NotReceivingThread, K::MarkAllAsRead, K::MarkAllRead, K::MarkRead, K::NoNotificationsYet, K::NoUnreadNotifications, K::NotificationFilter, K::Unread, K::AllTab, K::Older, K::Subscribe, K::Unsubscribe, K::OptParticipating, K::OptWatching, K::OptIgnoring, K::WatchDescParticipating, K::WatchDescWatching, K::WatchDescIgnoring, K::SecurityOff, K::LockfilesRead, K::NoAlerts, K::ColAdvisory, K::ColPackage, K::ColVersion, K::ColSeverity, K::ColSummary, K::ColState, K::ColOpened, K::StateOpen, K::StateDismissed, K::StateFixed, K::Dismiss, K::Reopen, K::NewOwner, K::RepoName, K::DescriptionOptional, K::VisibilityLegend, K::PrivateHelp, K::PublicHelp, K::DefaultBranch, K::AddDefaultLabels, K::MirrorLegend, K::MirrorIntro, K::SourceUrl, K::UsernameOptional, K::PasswordOrToken, K::SyncInterval, K::CreateRepository, K::OwnerRefused, K::InvalidRepoName, K::ContributionsTitle, K::ContributionsLastYear, K::CurrentStreak, K::LongestStreak, K::BestDay, K::Less, K::More, K::MonLabel, K::WedLabel, K::FriLabel, K::GoodMorning, K::GoodAfternoon, K::GoodEvening, K::NeedsAttention, K::NothingNeedsYou, K::ViewAllNotifications, K::BreadcrumbNav, K::ApplyTheme, K::RefLabel, K::YourProfile, K::EditProfile, ]; pub fn t(lang: Lang, key: K) -> &'static str { use K::*; use Lang::*; match (key, lang) { (Appearance, En) => "Appearance", (Appearance, Tr) => "Görünüm", (ThemeLabel, En) => "Theme", (ThemeLabel, Tr) => "Tema", (LanguageLabel, En) => "Language", (LanguageLabel, Tr) => "Dil", (ThemeAndLanguage, En) => "Theme and language", (ThemeAndLanguage, Tr) => "Tema ve dil", (MatchSystemHelp, En) => { "Match the system follows your operating system's light or dark setting." } (MatchSystemHelp, Tr) => { "Sistem ile eşleştir, işletim sisteminizin açık veya koyu ayarını izler." } (MatchSystem, En) => "Match the system", (MatchSystem, Tr) => "Sistem ile eşleştir", (Light, En) => "Light", (Light, Tr) => "Açık", (Dark, En) => "Dark", (Dark, Tr) => "Koyu", (MatchBrowser, En) => "Match the browser", (MatchBrowser, Tr) => "Tarayıcı ile eşleştir", (SwitchToLight, En) => "Switch to Light", (SwitchToLight, Tr) => "Açık temaya geç", (SwitchToDark, En) => "Switch to Dark", (SwitchToDark, Tr) => "Koyu temaya geç", (SwitchToMatchSystem, En) => "Switch to match the system", (SwitchToMatchSystem, Tr) => "Sistem ile eşleştir moduna geç", (AppearanceSaved, En) => "Appearance saved.", (AppearanceSaved, Tr) => "Görünüm kaydedildi.", (ChooseTheme, En) => "Choose a theme from the list.", (ChooseTheme, Tr) => "Listeden bir tema seçin.", (ChooseLanguage, En) => "Choose a language from the list.", (ChooseLanguage, Tr) => "Listeden bir dil seçin.", (KindOpenIssue, En) => "Open issue", (KindOpenIssue, Tr) => "Açık sorun", (KindClosedIssue, En) => "Closed issue", (KindClosedIssue, Tr) => "Kapalı sorun", (KindOpenPull, En) => "Open pull request", (KindOpenPull, Tr) => "Açık çekme isteği", (KindClosedPull, En) => "Closed pull request", (KindClosedPull, Tr) => "Kapalı çekme isteği", (KindDiscussion, En) => "Discussion", (KindDiscussion, Tr) => "Tartışma", (KindSecurityAlerts, En) => "Security alerts", (KindSecurityAlerts, Tr) => "Güvenlik uyarıları", (ReasonMentioned, En) => "mentioned", (ReasonMentioned, Tr) => "bahsedildi", (ReasonAssigned, En) => "assigned", (ReasonAssigned, Tr) => "atandı", (ReasonReviewRequested, En) => "review requested", (ReasonReviewRequested, Tr) => "inceleme istendi", (ReasonAuthor, En) => "author", (ReasonAuthor, Tr) => "yazar", (ReasonCommented, En) => "commented", (ReasonCommented, Tr) => "yorumladı", (ReasonStateChange, En) => "state change", (ReasonStateChange, Tr) => "durum değişikliği", (ReasonSubscribed, En) => "subscribed", (ReasonSubscribed, Tr) => "abone", (ReasonWatching, En) => "watching", (ReasonWatching, Tr) => "izleniyor", (ReasonReplied, En) => "replied", (ReasonReplied, Tr) => "yanıtladı", (ReasonSecurity, En) => "security", (ReasonSecurity, Tr) => "güvenlik", (VerbOpened, En) => "opened", (VerbOpened, Tr) => "açıldı", (VerbCommented, En) => "commented", (VerbCommented, Tr) => "yorumlandı", (VerbClosed, En) => "closed", (VerbClosed, Tr) => "kapatıldı", (VerbReopened, En) => "reopened", (VerbReopened, Tr) => "yeniden açıldı", (VerbMerged, En) => "merged", (VerbMerged, Tr) => "birleştirildi", (VerbReviewed, En) => "reviewed", (VerbReviewed, Tr) => "incelendi", (VerbAssigned, En) => "assigned", (VerbAssigned, Tr) => "atandı", (VerbMentionedYou, En) => "mentioned you", (VerbMentionedYou, Tr) => "sizden bahsedildi", (VerbPushedTo, En) => "pushed to", (VerbPushedTo, Tr) => "gönderim yapıldı", (VerbUpdated, En) => "updated", (VerbUpdated, Tr) => "güncellendi", (VerbDiscussionReply, En) => "commented on your discussion", (VerbDiscussionReply, Tr) => "tartışmanıza yorum yapıldı", (WhyMentioned, En) => "you were mentioned", (WhyMentioned, Tr) => "sizden bahsedildiği için", (WhyAssigned, En) => "you were assigned", (WhyAssigned, Tr) => "size atandığınız için", (WhyReviewRequested, En) => "you were asked to review", (WhyReviewRequested, Tr) => "incelemeniz istendiği için", (WhyAuthor, En) => "you authored this", (WhyAuthor, Tr) => "bunu siz yazdığınız için", (WhyCommented, En) => "you commented on this", (WhyCommented, Tr) => "buna yorum yaptığınız için", (WhySubscribed, En) => "you subscribed to this", (WhySubscribed, Tr) => "buna abone olduğunuz için", (WhyWatching, En) => "you are watching this repository", (WhyWatching, Tr) => "bu depoyu izlediğiniz için", (NotReceivingRepo, En) => { "You're not receiving notifications from this repository. Mentions still notify you." } (NotReceivingRepo, Tr) => { "Bu depodan bildirim almıyorsunuz. Yine de bahsedildiğinizde bildirim alırsınız." } (NotReceivingThread, En) => "You're not receiving notifications from this thread.", (NotReceivingThread, Tr) => "Bu konudan bildirim almıyorsunuz.", (MarkAllAsRead, En) => "Mark all as read", (MarkAllAsRead, Tr) => "Tümünü okundu olarak işaretle", (MarkAllRead, En) => "Mark all read", (MarkAllRead, Tr) => "Tümünü okundu işaretle", (MarkRead, En) => "Mark read", (MarkRead, Tr) => "Okundu işaretle", (NoNotificationsYet, En) => "No notifications yet.", (NoNotificationsYet, Tr) => "Henüz bildirim yok.", (NoUnreadNotifications, En) => "You have no unread notifications.", (NoUnreadNotifications, Tr) => "Okunmamış bildiriminiz yok.", (NotificationFilter, En) => "Notification filter", (NotificationFilter, Tr) => "Bildirim filtresi", (Unread, En) => "Unread", (Unread, Tr) => "Okunmamış", (AllTab, En) => "All", (AllTab, Tr) => "Tümü", (Older, En) => "Older", (Older, Tr) => "Daha eski", (Subscribe, En) => "Subscribe", (Subscribe, Tr) => "Abone ol", (Unsubscribe, En) => "Unsubscribe", (Unsubscribe, Tr) => "Abonelikten çık", (OptParticipating, En) => "Participating", (OptParticipating, Tr) => "Katılımcı", (OptWatching, En) => "Watching", (OptWatching, Tr) => "İzleniyor", (OptIgnoring, En) => "Ignoring", (OptIgnoring, Tr) => "Yok sayılıyor", (WatchDescParticipating, En) => "Only threads you start, join or are mentioned in.", (WatchDescParticipating, Tr) => { "Yalnızca başlattığın, katıldığın veya adının geçtiği konular." } (WatchDescWatching, En) => "All activity in this repository.", (WatchDescWatching, Tr) => "Bu depodaki tüm etkinlik.", (WatchDescIgnoring, En) => "Nothing from this repository, except mentions.", (WatchDescIgnoring, Tr) => "Bu depodan hiçbir şey, yalnızca adınızın geçtikleri hariç.", (SecurityOff, En) => { "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." } (SecurityOff, Tr) => { "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." } (LockfilesRead, En) => { "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." } (LockfilesRead, Tr) => { "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." } (NoAlerts, En) => "No alerts.", (NoAlerts, Tr) => "Uyarı yok.", (ColAdvisory, En) => "Advisory", (ColAdvisory, Tr) => "Güvenlik tavsiyesi", (ColPackage, En) => "Package", (ColPackage, Tr) => "Paket", (ColVersion, En) => "Version", (ColVersion, Tr) => "Sürüm", (ColSeverity, En) => "Severity", (ColSeverity, Tr) => "Önem", (ColSummary, En) => "Summary", (ColSummary, Tr) => "Özet", (ColState, En) => "State", (ColState, Tr) => "Durum", (ColOpened, En) => "Opened", (ColOpened, Tr) => "Açılış", (StateOpen, En) => "Open", (StateOpen, Tr) => "Açık", (StateDismissed, En) => "Dismissed", (StateDismissed, Tr) => "Yoksayıldı", (StateFixed, En) => "Fixed", (StateFixed, Tr) => "Düzeltildi", (Dismiss, En) => "Dismiss", (Dismiss, Tr) => "Yoksay", (Reopen, En) => "Reopen", (Reopen, Tr) => "Yeniden aç", (NewOwner, En) => "Owner", (NewOwner, Tr) => "Sahip", (RepoName, En) => "Repository name", (RepoName, Tr) => "Depo adı", (DescriptionOptional, En) => "Description (optional)", (DescriptionOptional, Tr) => "Açıklama (isteğe bağlı)", (VisibilityLegend, En) => "Visibility", (VisibilityLegend, Tr) => "Görünürlük", (PrivateHelp, En) => "Private: only you and collaborators can see it", (PrivateHelp, Tr) => "Özel: yalnızca siz ve ortak çalışanlar görebilir", (PublicHelp, En) => "Public: anyone can read it", (PublicHelp, Tr) => "Herkese açık: herkes okuyabilir", (DefaultBranch, En) => "Default branch", (DefaultBranch, Tr) => "Varsayılan dal", (AddDefaultLabels, En) => { "Add default labels (bug, documentation, enhancement and the rest)" } (AddDefaultLabels, Tr) => { "Varsayılan etiketleri ekle (bug, documentation, enhancement ve diğerleri)" } (MirrorLegend, En) => "Mirror an existing repository (optional)", (MirrorLegend, Tr) => "Mevcut bir depoyu yansıt (isteğe bağlı)", (MirrorIntro, En) => { "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." } (MirrorIntro, Tr) => { "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." } (SourceUrl, En) => "Source URL", (SourceUrl, Tr) => "Kaynak URL", (UsernameOptional, En) => "Username (optional)", (UsernameOptional, Tr) => "Kullanıcı adı (isteğe bağlı)", (PasswordOrToken, En) => "Password or token (optional)", (PasswordOrToken, Tr) => "Parola veya belirteç (isteğe bağlı)", (SyncInterval, En) => "Sync interval in minutes (0 = manual only, at least 10)", (SyncInterval, Tr) => "Eşitleme aralığı (dakika; 0 = yalnızca elle, en az 10)", (CreateRepository, En) => "Create repository", (CreateRepository, Tr) => "Depo oluştur", (OwnerRefused, En) => "you cannot create repositories for that owner", (OwnerRefused, Tr) => "bu sahip için depo oluşturamazsınız", (InvalidRepoName, En) => "invalid repository name", (InvalidRepoName, Tr) => "geçersiz depo adı", (ContributionsTitle, En) => "Contributions", (ContributionsTitle, Tr) => "Katkılar", (ContributionsLastYear, En) => "contributions in the last year", (ContributionsLastYear, Tr) => "son bir yılda katkı", (CurrentStreak, En) => "Current streak", (CurrentStreak, Tr) => "Mevcut seri", (LongestStreak, En) => "Longest streak", (LongestStreak, Tr) => "En uzun seri", (BestDay, En) => "Best day", (BestDay, Tr) => "En iyi gün", (Less, En) => "Less", (Less, Tr) => "Az", (More, En) => "More", (More, Tr) => "Çok", (MonLabel, En) => "Mon", (MonLabel, Tr) => "Pzt", (WedLabel, En) => "Wed", (WedLabel, Tr) => "Çar", (FriLabel, En) => "Fri", (FriLabel, Tr) => "Cum", (GoodMorning, En) => "Good morning", (GoodMorning, Tr) => "Günaydın", (GoodAfternoon, En) => "Good afternoon", (GoodAfternoon, Tr) => "İyi günler", (GoodEvening, En) => "Good evening", (GoodEvening, Tr) => "İyi akşamlar", (NeedsAttention, En) => "Needs your attention", (NeedsAttention, Tr) => "Dikkatinizi bekleyenler", (NothingNeedsYou, En) => "Nothing needs you right now.", (NothingNeedsYou, Tr) => "Şu an dikkatinizi bekleyen bir şey yok.", (ViewAllNotifications, En) => "View all notifications", (ViewAllNotifications, Tr) => "Tüm bildirimleri gör", (BreadcrumbNav, En) => "Path", (BreadcrumbNav, Tr) => "Yol", (ApplyTheme, En) => "Apply", (ApplyTheme, Tr) => "Uygula", (RefLabel, En) => "Ref", (RefLabel, Tr) => "Referans", (YourProfile, En) => "Your profile", (YourProfile, Tr) => "Profilin", (EditProfile, En) => "Edit profile", (EditProfile, Tr) => "Profili düzenle", } } /// `1,284` in English, `1.284` in Turkish. pub fn thousands(lang: Lang, n: u64) -> String { let sep = match lang { Lang::En => ',', Lang::Tr => '.', }; let digits = n.to_string(); let mut out = String::with_capacity(digits.len() + digits.len() / 3); for (i, ch) in digits.chars().enumerate() { if i > 0 && (digits.len() - i).is_multiple_of(3) { out.push(sep); } out.push(ch); } out } /// `12 days` / `12 gün`, for a streak length. pub fn streak_days(lang: Lang, n: u64) -> String { match lang { Lang::En => format!("{n} day{}", if n == 1 { "" } else { "s" }), Lang::Tr => format!("{n} gün"), } } /// `1 contribution` / `3 contributions`, and `3 katkı` in Turkish. pub fn contributions_count(lang: Lang, n: u64) -> String { match lang { Lang::En => format!("{n} contribution{}", if n == 1 { "" } else { "s" }), Lang::Tr => format!("{n} katkı"), } } /// Short month name, `1..=12`. Turkish uses the usual three-letter forms. pub fn month_abbrev(lang: Lang, month: i64) -> &'static str { const EN: [&str; 12] = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; const TR: [&str; 12] = [ "Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara", ]; let index = (month.clamp(1, 12) - 1) as usize; match lang { Lang::En => EN[index], Lang::Tr => TR[index], } } /// `16 Oct 2026` in English, `16 Eki 2026` in Turkish. pub fn date_text(lang: Lang, year: i64, month: i64, day: i64) -> String { format!("{day} {} {year}", month_abbrev(lang, month)) } /// `16 Oct 2025 to 16 Oct 2026` in English; Turkish joins the two dates with a dash. pub fn date_range(lang: Lang, from: &str, to: &str) -> String { match lang { Lang::En => format!("{from} to {to}"), Lang::Tr => format!("{from} – {to}"), } } /// The contribution calendar's motivating line. `today` is today's count, `streak` the current /// streak (which counts today when `today > 0`). pub fn heatmap_motivation(lang: Lang, today: u64, streak: u64) -> String { match (lang, today > 0, streak > 0) { (Lang::En, true, _) => format!("Nice \u{2014} that's a {streak}-day streak."), (Lang::En, false, true) => format!( "Keep it going: one commit today makes it a {}-day streak.", streak + 1 ), (Lang::En, false, false) => "Start a new streak today.".to_owned(), (Lang::Tr, true, _) => format!("Harika \u{2014} {streak} günlük bir seri!"), (Lang::Tr, false, true) => format!( "Devam edin: bugün bir katkı, seriyi {} güne çıkarır.", streak + 1 ), (Lang::Tr, false, false) => "Bugün yeni bir seri başlatın.".to_owned(), } } /// The calendar's accessible summary, for the grid's `aria-label`. pub fn heatmap_summary(lang: Lang, total: u64, current: u64, longest: u64) -> String { let total = thousands(lang, total); let current = streak_days(lang, current); let longest = streak_days(lang, longest); match lang { Lang::En => format!( "Contribution calendar: {total} contributions in the last year. Current streak {current}, longest streak {longest}." ), Lang::Tr => format!( "Katkı takvimi: son bir yılda {total} katkı. Mevcut seri {current}, en uzun seri {longest}." ), } } /// A calendar cell's hover text: `3 contributions on 16 Oct 2026`. pub fn contributions_on(lang: Lang, n: u64, date: &str) -> String { match lang { Lang::En => format!("{} on {date}", contributions_count(lang, n)), Lang::Tr => format!("{date}: {}", contributions_count(lang, n)), } } /// The activity line for an inbox row: the verb, then the actor when there is one. English reads /// "opened by alice"; Turkish reads "alice tarafından açıldı", or "Açıldı" with no actor. pub fn activity_line(lang: Lang, verb: K, actor: Option<&str>) -> String { let verb = t(lang, verb); match (lang, actor) { (Lang::En, Some(actor)) => format!("{verb} by {actor}"), (Lang::En, None) => verb.to_owned(), (Lang::Tr, Some(actor)) => format!("{actor} tarafından {verb}"), (Lang::Tr, None) => capitalize(verb), } } /// "You're receiving notifications because ..." for a reason fragment such as `WhyMentioned`. pub fn receiving_because(lang: Lang, why: K) -> String { let why = t(lang, why); match lang { Lang::En => format!("You're receiving notifications because {why}."), Lang::Tr => format!("{} bildirim alıyorsunuz.", capitalize(why)), } } /// "3 watching" / "3 izleyici", the watch control's count. pub fn watchers_count(lang: Lang, watchers: u64) -> String { match lang { Lang::En => format!("{watchers} watching"), Lang::Tr => format!("{watchers} izleyici"), } } /// "3 new alerts" / "3 yeni uyarı". pub fn new_alerts(lang: Lang, count: u64) -> String { match lang { Lang::En if count == 1 => format!("{count} new alert"), Lang::En => format!("{count} new alerts"), Lang::Tr => format!("{count} yeni uyarı"), } } /// The security page's summary of how many dependencies were read and how many alerts are open. pub fn dependencies_summary(lang: Lang, dependencies: u64, open: usize) -> String { match lang { Lang::En => format!( "{dependencies} dependencies were read from the lockfiles on the default branch. {open} open alerts." ), Lang::Tr => format!( "Varsayılan daldaki kilit dosyalarından {dependencies} bağımlılık okundu. {open} açık uyarı var." ), } } /// The new-repo error for a description over the limit. pub fn description_too_long(lang: Lang, max: usize) -> String { match lang { Lang::En => format!("description must be at most {max} characters"), Lang::Tr => format!("açıklama en fazla {max} karakter olabilir"), } } /// The new-repo error when the git repository could not be created. pub fn git_init_failed(lang: Lang, error: impl Display) -> String { match lang { Lang::En => format!("could not create the git repository: {error}"), Lang::Tr => format!("git deposu oluşturulamadı: {error}"), } } /// Upper-cases the first character. Turkish verbs here start with a plain letter, so the /// default mapping is right. /// "Good morning, Ahmet" for an hour before noon, "Good afternoon" before 18:00 and "Good /// evening" otherwise. `hour` is 0-23 in UTC. pub fn greeting(lang: Lang, hour: u8, name: &str) -> String { let phrase = match hour { 0..12 => K::GoodMorning, 12..18 => K::GoodAfternoon, _ => K::GoodEvening, }; format!("{}, {name}", t(lang, phrase)) } /// `1 repository` / `3 repositories`, and `3 depo` in Turkish. pub fn repositories_count(lang: Lang, n: u64) -> String { match lang { Lang::En => format!("{n} repositor{}", if n == 1 { "y" } else { "ies" }), Lang::Tr => format!("{n} depo"), } } fn capitalize(text: &str) -> String { let mut chars = text.chars(); match chars.next() { Some(first) => first.to_uppercase().chain(chars).collect(), None => String::new(), } } #[cfg(test)] mod tests { use super::*; #[test] fn every_variant_has_both_languages() { for key in ALL { assert!(!t(Lang::En, *key).is_empty(), "{key:?} has no English"); assert!(!t(Lang::Tr, *key).is_empty(), "{key:?} has no Turkish"); } } #[test] fn turkish_differs_from_english_for_translated_phrases() { let differing = ALL .iter() .filter(|key| t(Lang::En, **key) != t(Lang::Tr, **key)) .count(); assert!(differing >= 3, "only {differing} phrases are translated"); assert_eq!(t(Lang::Tr, K::Appearance), "Görünüm"); } #[test] fn activity_lines_keep_the_english_wording() { assert_eq!( activity_line(Lang::En, K::VerbOpened, Some("alice")), "opened by alice" ); assert_eq!(activity_line(Lang::En, K::VerbUpdated, None), "updated"); assert_eq!( activity_line(Lang::Tr, K::VerbOpened, Some("alice")), "alice tarafından açıldı" ); assert_eq!(activity_line(Lang::Tr, K::VerbUpdated, None), "Güncellendi"); } #[test] fn plural_helpers_match_english() { assert_eq!(new_alerts(Lang::En, 1), "1 new alert"); assert_eq!(new_alerts(Lang::En, 2), "2 new alerts"); assert_eq!(new_alerts(Lang::Tr, 3), "3 yeni uyarı"); assert_eq!( receiving_because(Lang::En, K::WhySubscribed), "You're receiving notifications because you subscribed to this." ); } }