This commit is contained in:
Mike Cao committed 2026-09-10 21:19:44 -07:00
commit fe19a4fbb4
11 files changed
+903 -8

No files matched your search

+184
View File
@@ -0,0 +1,184 @@
{
"ab": "Abxaz dili",
"aa": "Afar dili",
"af": "Afrikaans dili",
"ak": "Akan dili",
"sq": "Alban dili",
"am": "Amhar dili",
"ar": "Ərəb dili",
"an": "Araqon dili",
"hy": "Erməni dili",
"as": "Assam dili",
"av": "Avar dili",
"ae": "Avesta dili",
"ay": "Aymara dili",
"az": "Azərbaycan dili",
"bm": "Bambara dili",
"ba": "Başqırd dili",
"eu": "Bask dili",
"be": "Belarus dili",
"bn": "Benqal dili",
"bh": "Bihari dili",
"bi": "Bislama dili",
"bs": "Bosniya dili",
"br": "Breton dili",
"bg": "Bolqar dili",
"my": "Birma dili",
"ca": "Katalan dili",
"ch": "Çamorro dili",
"ce": "Çeçen dili",
"ny": "Çiçeva dili",
"zh": "Çin dili",
"cv": "Çuvaş dili",
"kw": "Korn dili",
"co": "Korsika dili",
"cr": "Kri dili",
"hr": "Xorvat dili",
"cs": "Çex dili",
"da": "Danimarka dili",
"dv": "Divehi dili",
"nl": "Holland dili",
"en": "İngilis dili",
"eo": "Esperanto dili",
"et": "Eston dili",
"ee": "Eve dili",
"fo": "Farer dili",
"fj": "Fici dili",
"fi": "Fin dili",
"fr": "Fransız dili",
"ff": "Fula dili",
"gl": "Qalisiya dili",
"ka": "Gürcü dili",
"de": "Alman dili",
"el": "Yunan dili (müasir)",
"gn": "Qvarani dili",
"gu": "Qucarat dili",
"ht": "Haiti dili",
"ha": "Hausa dili",
"he": "İvrit dili (müasir)",
"hz": "Herero dili",
"hi": "Hind dili",
"ho": "Hiri Motu dili",
"hu": "Macar dili",
"ia": "İnterlingua dili",
"id": "İndoneziya dili",
"ie": "İnterlingue dili",
"ga": "İrland dili",
"ig": "İqbo dili",
"ik": "İnupiak dili",
"io": "İdo dili",
"is": "İsland dili",
"it": "İtalyan dili",
"iu": "İnuktitut dili",
"ja": "Yapon dili",
"jv": "Yava dili",
"kl": "Qrenland dili",
"kn": "Kannada dili",
"kr": "Kanuri dili",
"ks": "Kəşmir dili",
"kk": "Qazax dili",
"km": "Kxmer dili",
"ki": "Kikuyu dili",
"rw": "Kinyaruanda dili",
"ky": "Qırğız dili",
"kv": "Komi dili",
"kg": "Konqo dili",
"ko": "Koreya dili",
"ku": "Kürd dili",
"kj": "Kvanyama dili",
"la": "Latın dili",
"lb": "Lüksemburq dili",
"lg": "Luqanda dili",
"li": "Limburq dili",
"ln": "Lingala dili",
"lo": "Lao dili",
"lt": "Litva dili",
"lu": "Luba-Katanqa dili",
"lv": "Latış dili",
"gv": "Men dili",
"mk": "Makedon dili",
"mg": "Malaqasiya dili",
"ms": "Malay dili",
"ml": "Malayalam dili",
"mt": "Malta dili",
"mi": "Maori dili",
"mr": "Marathi dili",
"mh": "Marşal dili",
"mn": "Monqol dili",
"na": "Nauru dili",
"nv": "Navaho dili",
"nb": "Norveç bokmal dili",
"nd": "Şimal ndebele dili",
"ne": "Nepal dili",
"ng": "Ndonqa dili",
"nn": "Norveç nynorsk dili",
"no": "Norveç dili",
"ii": "Nuosu dili",
"nr": "Cənub ndebele dili",
"oc": "Oksitan dili",
"oj": "Ocibve dili",
"cu": "Qədim kilsə slavyan dili",
"om": "Oromo dili",
"or": "Oriya dili",
"os": "Osetin dili",
"pa": "Pəncab dili",
"pi": "Pali dili",
"fa": "Fars dili",
"pl": "Polyak dili",
"ps": "Puştu dili",
"pt": "Portuqal dili",
"qu": "Keçua dili",
"rm": "Romanş dili",
"rn": "Kirundi dili",
"ro": "Rumın dili",
"ru": "Rus dili",
"sa": "Sanskrit dili",
"sc": "Sardiniya dili",
"sd": "Sindhi dili",
"se": "Şimal sami dili",
"sm": "Samoa dili",
"sg": "Sanqo dili",
"sr": "Serb dili",
"gd": "Şotland qael dili",
"sn": "Şona dili",
"si": "Sinhal dili",
"sk": "Slovak dili",
"sl": "Sloven dili",
"so": "Somali dili",
"st": "Cənub soto dili",
"es": "İspan dili",
"su": "Sunda dili",
"sw": "Suahili dili",
"ss": "Svati dili",
"sv": "İsveç dili",
"ta": "Tamil dili",
"te": "Teluqu dili",
"tg": "Tacik dili",
"th": "Tay dili",
"ti": "Tiqrinya dili",
"bo": "Tibet dili",
"tk": "Türkmən dili",
"tl": "Taqaloq dili",
"tn": "Tsvana dili",
"to": "Tonqa dili",
"tr": "Türk dili",
"ts": "Tsonqa dili",
"tt": "Tatar dili",
"tw": "Tvi dili",
"ty": "Tahiti dili",
"ug": "Uyğur dili",
"uk": "Ukrayna dili",
"ur": "Urdu dili",
"uz": "Özbək dili",
"ve": "Venda dili",
"vi": "Vyetnam dili",
"vo": "Volapük dili",
"wa": "Valon dili",
"cy": "Uels dili",
"wo": "Volof dili",
"fy": "Qərbi friz dili",
"xh": "Xosa dili",
"yi": "İdiş dili",
"yo": "Yoruba dili",
"za": "Çjuan dili"
}
+535
View File
@@ -0,0 +1,535 @@
{
"label": {
"access-code": "Giriş kodu",
"account": "Hesab",
"action": "Əməliyyat",
"actions": "Əməliyyatlar",
"activity": "Fəaliyyət",
"add": "Əlavə et",
"add-board": "Lövhə əlavə et",
"add-description": "Təsvir əlavə et",
"add-link": "Keçid əlavə et",
"add-member": "Üzv əlavə et",
"add-pixel": "Piksel əlavə et",
"add-step": "Addım əlavə et",
"add-website": "Veb sayt əlavə et",
"admin": "Admin",
"affiliate": "Tərəfdaş",
"after": "Sonra",
"all": "Hamısı",
"all-time": "Bütün vaxt",
"analysis": "Təhlil",
"analytics": "Analitika",
"application": "Tətbiq",
"apply": "Tətbiq et",
"attribution": "Atribusiya",
"attribution-description": "İstifadəçilərin marketinqinizlə necə əlaqə qurduğunu və nəyin çevrilmələrə səbəb olduğunu görün.",
"audience": "Auditoriya",
"average": "Orta",
"back": "Geri",
"before": "Əvvəl",
"behavior": "Davranış",
"block-selector": "Blok seçici",
"boards": "Lövhələr",
"board-type": "Lövhə növü",
"bounce-rate": "Səkmə dərəcəsi",
"breakdown": "Bölgü",
"browser": "Brauzer",
"browsers": "Brauzerlər",
"campaign": "Kampaniya",
"campaigns": "Kampaniyalar",
"cancel": "Ləğv et",
"close": "Bağla",
"change-password": "Parolu dəyiş",
"channel": "Kanal",
"channels": "Kanallar",
"chart": "Qrafik",
"cities": "Şəhərlər",
"city": "Şəhər",
"clear-all": "Hamısını təmizlə",
"cls": "CLS",
"cohort": "Koqorta",
"cohorts": "Koqortalar",
"compare": "Müqayisə et",
"compare-dates": "Tarixləri müqayisə et",
"confirm": "Təsdiqlə",
"confirm-password": "Parolu təsdiqlə",
"contains": "Ehtiva edir",
"regex-match": "Regex-ə uyğun gəlir",
"regex-not-match": "Regex-ə uyğun gəlmir",
"content": "Məzmun",
"continue": "Davam et",
"conversion": "Çevrilmə",
"conversion-rate": "Çevrilmə dərəcəsi",
"conversion-step": "Çevrilmə addımı",
"count": "Say",
"countries": "Ölkələr",
"country": "Ölkə",
"create": "Yarat",
"create-report": "Hesabat yarat",
"create-team": "Komanda yarat",
"create-user": "İstifadəçi yarat",
"created": "Yaradılıb",
"created-by": "Yaradan",
"criteria": "Meyar",
"cumulative": "Kumulyativ",
"currency": "Valyuta",
"current": "Cari",
"current-password": "Cari parol",
"custom-range": "Fərdi aralıq",
"dashboard": "İdarə paneli",
"data": "Məlumat",
"date": "Tarix",
"date-range": "Tarix aralığı",
"day": "Gün",
"default-date-range": "Standart tarix aralığı",
"delete": "Sil",
"delete-report": "Hesabatı sil",
"delete-team": "Komandanı sil",
"delete-user": "İstifadəçini sil",
"delete-website": "Veb saytı sil",
"description": "Təsvir",
"desktop": "Masaüstü",
"destination-url": "Təyinat URL-i",
"details": "Detallar",
"device": "Cihaz",
"devices": "Cihazlar",
"direct": "Birbaşa",
"dismiss": "İmtina et",
"distinct-id": "Unikal ID",
"documentation": "Sənədləşdirmə",
"does-not-contain": "Ehtiva etmir",
"does-not-include": "Daxil deyil",
"doest-not-exist": "Mövcud deyil",
"domain": "Domen",
"download": "Endir",
"dropoff": "Tərk etmə",
"duration": "Müddət",
"min-duration-seconds": "Minimum müddət (saniyə)",
"edit": "Redaktə et",
"design": "Dizayn",
"edit-member": "Üzvü redaktə et",
"email": "E-poçt",
"enable-share-url": "Paylaşım URL-ini aktivləşdir",
"end-step": "Son addım",
"entry": "Giriş səhifəsi",
"environment": "Mühit",
"event": "Hadisə",
"event-data": "Hadisə məlumatları",
"event-properties": "Hadisə xüsusiyyətləri",
"event-name": "Hadisə adı",
"events": "Hadisələr",
"exclude-bounce": "Səkməni çıxart",
"exists": "Mövcuddur",
"exit": "Çıxış səhifəsi",
"false": "Yalan",
"fcp": "FCP",
"field": "Sahə",
"fields": "Sahələr",
"filter": "Süzgəc",
"filter-combined": "Birləşdirilmiş",
"filter-raw": "Xam",
"filters": "Süzgəclər",
"filters-enabled": "Süzgəclər aktivdir",
"property-filter": "Xüsusiyyət süzgəci",
"activity-by-property": "Xüsusiyyətə görə fəaliyyət",
"add-note": "Qeyd əlavə et",
"first-click": "İlk klik",
"first-seen": "İlk görülmə",
"funnel": "Huni",
"funnel-description": "İstifadəçilərin çevrilmə və tərk etmə dərəcəsini anlayın.",
"funnels": "Hunilər",
"goal": "Hədəf",
"goals": "Hədəflər",
"good": "Yaxşı",
"goals-description": "Səhifə baxışları və hadisələr üçün hədəflərinizi izləyin.",
"greater-than": "Böyükdür",
"greater-than-equals": "Böyük və ya bərabərdir",
"grouped": "Qruplaşdırılıb",
"growth": "Artım",
"heatmap": "İstilik xəritəsi",
"heatmap-description": "Ziyarətçilərin səhifələrinizdə harada kliklədiyini və nə qədər aşağı sürüşdürdüyünü vizuallaşdırın.",
"heatmaps": "İstilik xəritələri",
"hostname": "Host adı",
"hour": "Saat",
"includes": "Daxildir",
"insight": "Dərketmə",
"insights": "Dərketmələr",
"insights-description": "Seqmentlərdən və süzgəclərdən istifadə edərək məlumatınıza daha dərindən nəzər salın.",
"inp": "INP",
"invalid-url": "Yanlış URL",
"is": "Bərabərdir",
"is-false": "Yalandır",
"is-not": "Bərabər deyil",
"is-not-set": "Təyin edilməyib",
"is-set": "Təyin edilib",
"is-true": "Doğrudur",
"join": "Qoşul",
"join-team": "Komandaya qoşul",
"journey": "Səyahət",
"journey-description": "İstifadəçilərin veb saytınızda necə hərəkət etdiyini anlayın.",
"journeys": "Səyahətlər",
"language": "Dil",
"languages": "Dillər",
"laptop": "Noutbuk",
"last-click": "Son klik",
"last-days": "Son {x} gün",
"last-hours": "Son {x} saat",
"last-months": "Son {x} ay",
"last-seen": "Son görülmə",
"lcp": "LCP",
"leave": "Tərk et",
"leave-team": "Komandanı tərk et",
"less-than": "Kiçikdir",
"less-than-equals": "Kiçik və ya bərabərdir",
"link": "Keçid",
"links": "Keçidlər",
"location": "Məkan",
"login": "Giriş",
"llm": "LLM",
"logout": "Çıxış",
"manage": "İdarə et",
"manager": "Menecer",
"mask-level": "Maskalama səviyyəsi",
"max": "Maks",
"max-duration": "Maksimum müddət",
"match": "Uyğunluq",
"match-all": "Hamısı",
"match-any": "İstənilən",
"maximize": "Böyüt",
"medium": "Vasitə",
"member": "Üzv",
"members": "Üzvlər",
"min": "Min",
"minute": "Dəqiqə",
"mobile": "Mobil",
"model": "Model",
"month": "Ay",
"more": "Daha çox",
"my-account": "Mənim hesabım",
"my-websites": "Mənim veb saytlarım",
"name": "Ad",
"needs-improvement": "Təkmilləşdirmə tələb edir",
"new-password": "Yeni parol",
"none": "Yoxdur",
"number-of-records": "{x} {x, plural, one {qeyd} other {qeyd}}",
"ok": "OK",
"online": "Onlayn",
"open": "Qarışıq",
"organic-search": "Üzvi axtarış",
"organic-shopping": "Üzvi alış-veriş",
"organic-social": "Üzvi sosial",
"organic-video": "Üzvi video",
"os": "OS",
"other": "Digər",
"overview": "İcmal",
"owner": "Sahib",
"page": "Səhifə",
"page-of": "Səhifə {current} / {total}",
"page-views": "Səhifə baxışları",
"pageTitle": "Səhifə başlığı",
"period": "Dövr",
"pages": "Səhifələr",
"paid-ads": "Ödənişli reklamlar",
"paid-search": "Ödənişli axtarış",
"paid-shopping": "Ödənişli alış-veriş",
"paid-social": "Ödənişli sosial",
"paid-video": "Ödənişli video",
"password": "Parol",
"path": "Yol",
"paths": "Yollar",
"performance": "Performans",
"pixel": "Piksel",
"pixels": "Piksellər",
"poor": "Zəif",
"powered-by": "{name} tərəfindən işə salınıb",
"preferences": "Tərcihlər",
"previous": "Əvvəlki",
"previous-period": "Əvvəlki dövr",
"previous-year": "Əvvəlki il",
"profile": "Profil",
"profiles": "Profillər",
"properties": "Xüsusiyyətlər",
"property": "Xüsusiyyət",
"queries": "Sorğular",
"query": "Sorğu",
"query-parameters": "Sorğu parametrləri",
"realtime": "Real vaxt",
"recorded": "Qeydə alınıb",
"referral": "İstinad",
"referrer": "Yönləndirən sayt",
"referrers": "Yönləndirən saytlar",
"refresh": "Təzələ",
"regenerate": "Yenidən yarat",
"region": "Bölgə",
"regions": "Bölgələr",
"remaining": "Qalıb",
"remove": "Çıxar",
"remove-member": "Üzvü çıxar",
"play": "Oynat",
"replay": "Təkrar baxış",
"replay-id": "Təkrar baxış ID-si",
"recorder-code": "Qeydiyyat kodu",
"replay-enabled": "Təkrar baxış aktivdir",
"replays": "Təkrar baxışlar",
"reports": "Hesabatlar",
"required": "Məcburi",
"reset": "Sıfırla",
"reset-website": "Veb saytı sıfırla",
"retention": "Saxlanma",
"retention-days": "Saxlanma günləri",
"retention-description": "Veb saytınızın istifadəçi sadiqliyini onların nə qədər tez-tez qayıtdığını izləyərək ölçün.",
"revenue": "Gəlir",
"revenue-description": "Gəlir məlumatınıza və istifadəçilərin xərcləmə tərzinə nəzər salın.",
"role": "Rol",
"run-query": "Sorğunu işə sal",
"save": "Yadda saxla",
"save-cohort": "Koqortanı yadda saxla",
"save-replay": "Təkrar baxışı yadda saxla",
"save-segment": "Seqmenti yadda saxla",
"sample-rate": "Nümunə tezliyi",
"sample-size": "Nümunə ölçüsü",
"screen": "Ekran",
"screens": "Ekranlar",
"search": "Axtarış",
"segment": "Seqment",
"segments": "Seqmentlər",
"select": "Seç",
"select-link": "Keçid seç",
"select-pixel": "Piksel seç",
"select-date": "Tarix seç",
"select-filter": "Süzgəc seç",
"select-role": "Rol seç",
"select-component": "Komponent seç",
"select-event": "Hadisə seç",
"select-website": "Veb sayt seç",
"session": "Sessiya",
"session-data": "Sessiya xüsusiyyətləri",
"sessions": "Sessiyalar",
"settings": "Parametrlər",
"share": "Paylaş",
"share-url": "Paylaşım URL-i",
"single-day": "Tək gün",
"slug": "Slug",
"sms": "SMS",
"source": "Mənbə",
"sources": "Mənbələr",
"start-step": "Başlanğıc addım",
"steps": "Addımlar",
"sum": "Cəm",
"support": "Dəstək",
"switch-account": "Hesabı dəyiş",
"switch-to-card-view": "Kart görünüşünə keç",
"switch-to-table-view": "Cədvəl görünüşünə keç",
"table": "Cədvəl",
"tablet": "Planşet",
"tag": "Etiket",
"tags": "Etiketlər",
"team": "Komanda",
"team-id": "Komanda ID-si",
"team-manager": "Komanda meneceri",
"team-member": "Komanda üzvü",
"team-name": "Komanda adı",
"team-owner": "Komanda sahibi",
"team-settings": "Komanda parametrləri",
"team-view-only": "Komanda yalnız baxış",
"team-websites": "Komanda veb saytları",
"teams": "Komandalar",
"term": "Termin",
"terms": "Terminlər",
"theme": "Tema",
"this-month": "Bu ay",
"this-week": "Bu həftə",
"this-year": "Bu il",
"timezone": "Saat qurşağı",
"title": "Başlıq",
"today": "Bu gün",
"toggle-charts": "Qrafikləri aç/bağla",
"total": "Cəmi",
"total-records": "Ümumi qeydlər",
"tracking-code": "İzləmə kodu",
"traffic": "Trafik",
"transactions": "Tranzaksiyalar",
"transfer": "Köçür",
"transfer-website": "Veb saytı köçür",
"true": "Doğru",
"ttfb": "TTFB",
"type": "Növ",
"unique": "Unikal",
"unique-events": "Unikal hadisələr",
"unique-visitors": "Unikal ziyarətçilər",
"aov": "AOV",
"arpu": "ARPU",
"uniqueCustomers": "Unikal müştərilər",
"customers": "Müştərilər",
"note": "Qeyd",
"notes": "Qeydlər",
"orders": "Sifarişlər",
"saved": "Yadda saxlanıldı",
"unknown": "Naməlum",
"untitled": "Adsız",
"upgrade": "Təkmilləşdir",
"update": "Yenilə",
"url": "URL",
"user": "İstifadəçi",
"username": "İstifadəçi adı",
"users": "İstifadəçilər",
"utm": "UTM",
"utm-campaign": "UTM kampaniyası",
"utm-content": "UTM məzmunu",
"utm-description": "Kampaniyalarınızı UTM parametrləri vasitəsilə izləyin.",
"utm-medium": "UTM vasitəsi",
"utm-source": "UTM mənbəyi",
"utm-term": "UTM termini",
"value": "Dəyər",
"version": "Versiya",
"view": "Bax",
"view-details": "Ətraflı bax",
"view-only": "Yalnız baxış",
"views": "Baxışlar",
"views-per-visit": "Ziyarət başına baxış",
"visit-duration": "Ziyarət müddəti",
"visitors": "Ziyarətçilər",
"visits": "Ziyarətlər",
"website": "Veb sayt",
"website-id": "Veb sayt ID-si",
"websites": "Veb saytlar",
"window": "Pəncərə",
"yesterday": "Dünən",
"security": "Təhlükəsizlik",
"two-factor-auth": "İkifaktorlu doğrulama",
"two-factor-setup-title": "İkifaktorlu doğrulamanı qur",
"two-factor-step1": "1-ci addım",
"two-factor-step2": "2-ci addım",
"two-factor-scan-qr": "QR kodu skan et",
"two-factor-cant-scan": "QR kodu skan edə bilmirsiniz?",
"two-factor-manual-entry": "Bunun əvəzinə bu məxfi açarı daxil edin:",
"two-factor-copy-code": "Kodu kopyala",
"two-factor-code-copied": "Kopyalandı!",
"two-factor-get-code": "Doğrulama kodu al",
"two-factor-enter-code": "Doğrulama kodunu daxil et",
"two-factor-use-backup": "Bunun əvəzinə ehtiyat kodundan istifadə et",
"two-factor-save-backup-codes": "Ehtiyat kodlarınızı yadda saxlayın",
"two-factor-download-codes": "Kodları endir",
"two-factor-saved-codes": "Ehtiyat kodlarımı yadda saxladım",
"two-factor-backup-file-title": "Umami - İkifaktorlu doğrulama ehtiyat kodları",
"two-factor-success-title": "İkifaktorlu doğrulama aktivləşdirildi",
"two-factor-enable": "2FA-nı aktivləşdir",
"two-factor-disable": "2FA-nı söndür",
"two-factor-disable-title": "İkifaktorlu doğrulamanı söndür",
"two-factor-status-not-configured": "Konfiqurasiya edilməyib",
"two-factor-active": "Aktiv",
"two-factor-require-global": "Bütün istifadəçilər üçün 2FA tələb et",
"two-factor-require-user": "Bu istifadəçi üçün 2FA tələb et",
"two-factor-require-team": "Bu komandanın bütün üzvləri üçün 2FA tələb et",
"two-factor-reset": "2FA-nı sıfırla",
"two-factor-global-active-tooltip": "Qlobal 2FA aktivdir",
"two-factor-verify": "Təsdiqlə",
"two-factor-login": "İkifaktorlu doğrulama",
"api-keys": "API açarları",
"api-key": "API açarı",
"create-api-key": "API açarı yarat",
"last-used": "Son istifadə",
"never": "Heç vaxt",
"copy": "Kopyala",
"authorize": "İcazə ver",
"deny": "Rədd et"
},
"message": {
"action-confirmation": "Təsdiqləmək üçün aşağıdakı xanaya {confirmation} yazın.",
"active-users": "{x} cari {x, plural, one {ziyarətçi} other {ziyarətçi}}",
"bad-request": "Yanlış sorğu",
"collected-data": "Toplanmış məlumat",
"confirm-delete": "{target} silmək istədiyinizə əminsiniz?",
"confirm-leave": "<b>{target}</b> tərk etmək istədiyinizə əminsiniz?",
"confirm-remove": "<b>{target}</b> çıxarmaq istədiyinizə əminsiniz?",
"confirm-reset": "{target} sıfırlamaq istədiyinizə əminsiniz?",
"delete-team-warning": "Komandanı silmək onun bütün veb saytlarını da siləcək.",
"delete-website-warning": "Veb saytın bütün məlumatları silinəcək.",
"error": "Nəsə səhv getdi.",
"empty-dashboard": "İdarə paneliniz boşdur. İlk komponentinizi əlavə etmək üçün Dizayn üzərinə klikləyin.",
"event-log": "<a>{url}</a> ünvanında <b>{event}</b>",
"forbidden": "Qadağandır",
"go-to-settings": "Parametrlərə keç",
"incorrect-username-password": "İstifadəçi adı və/və ya parol yanlışdır.",
"invalid-domain": "Yanlış domen. http/https daxil etməyin.",
"invalid-value": "Yanlış dəyər",
"max-length": "Maksimum {n} simvol uzunluğunda",
"min-password-length": "Minimum {n} simvol uzunluğunda",
"new-version-available": "Umami-nin yeni versiyası {version} mövcuddur!",
"no-data-available": "Məlumat yoxdur.",
"no-event-data": "Hadisə məlumatı yoxdur.",
"no-match-password": "Parollar uyğun gəlmir.",
"no-results-found": "Nəticə tapılmadı.",
"no-team-websites": "Bu komandanın heç bir veb saytı yoxdur.",
"no-teams": "Siz heç bir komanda yaratmamısınız.",
"no-users": "İstifadəçi yoxdur.",
"no-websites-configured": "Sizin heç bir konfiqurasiya edilmiş veb saytınız yoxdur.",
"not-found": "Tapılmadı",
"nothing-selected": "Heç nə seçilməyib.",
"page-not-found": "Səhifə tapılmadı",
"reset-website": "Bu veb saytı sıfırlamaq üçün təsdiqləmək məqsədilə aşağıdakı xanaya {confirmation} yazın.",
"reset-website-warning": "Bu veb saytın bütün statistikası silinəcək, lakin parametrləriniz qalacaq.",
"saved": "Yadda saxlanıldı.",
"select-board-entity-first": "Əvvəlcə veb sayt, piksel və ya keçid seçin.",
"select-component-preview": "Önbaxış üçün komponent seçin",
"select-website-first": "Əvvəlcə veb sayt seçin",
"sever-error": "Server xətası",
"share-url": "Veb saytınızın statistikası aşağıdakı URL-də hər kəsə açıqdır:",
"team-already-member": "Siz artıq bu komandanın üzvüsünüz.",
"team-not-found": "Komanda tapılmadı.",
"team-websites-info": "Veb saytları komandadakı hər kəs görə bilər.",
"tracking-code": "Bu veb saytın statistikasını izləmək üçün aşağıdakı kodu HTML-inizin '<head>...</head>' bölməsinə yerləşdirin.",
"transfer-team-website-to-user": "Bu veb sayt hesabınıza köçürülsün?",
"transfer-user-website-to-team": "Bu veb saytın köçürüləcəyi komandanı seçin.",
"transfer-website": "Veb saytın mülkiyyətini hesabınıza və ya başqa komandaya köçürün.",
"triggered-event": "Baş vermiş hadisə",
"unauthorized": "İcazəsiz",
"user-deleted": "İstifadəçi silindi.",
"replay-min-duration-description": "Yalnız bu müddətə bərabər və ya ondan uzun təkrar baxışları göstər.",
"viewed-page": "Baxılan səhifə",
"upgrade-required": "Bu funksiya {plan} plan abunəliyi tələb edir.",
"visitor-log": "<b>{country}</b>-dan <b>{os}</b> <b>{device}</b> üzərində <b>{browser}</b> istifadə edən ziyarətçi",
"two-factor-setup-description": "Hər dəfə daxil olanda, parolunuza əlavə olaraq, bir dəfəlik kod yaratmaq və istifadə etmək üçün doğrulayıcı tətbiqdən istifadə edəcəksiniz.",
"two-factor-setup-required-description": "Umami-yə daxil olmaq üçün ikifaktorlu doğrulama (2FA) tələb olunur.",
"two-factor-step1-description": "Aşağıdakı QR kodu skan edin və ya məxfi açarı əl ilə doğrulayıcı tətbiqinizə daxil edin.",
"two-factor-step2-description": "Doğrulayıcı tətbiqinizdə gördüyünüz 6 rəqəmli kodu daxil edin.",
"two-factor-enabled-description": "Hesabınız artıq ikifaktorlu doğrulama ilə qorunur. Bundan sonra hər daxil olanda doğrulayıcı tətbiqinizdən bir kod daxil etməli olacaqsınız.",
"two-factor-backup-description": "Doğrulayıcı tətbiqinizə girişi itirsəniz, daxil olmaq üçün bu bir dəfəlik ehtiyat kodlarından birini istifadə edə bilərsiniz. Onları təhlükəsiz bir yerdə saxlayın, bir daha göstərilməyəcək.",
"two-factor-backup-codes-once": "Bu kodları təhlükəsiz saxlayın. Hər kod yalnız bir dəfə istifadə edilə bilər.",
"two-factor-login-description": "Doğrulayıcı tətbiqinizdən 6 rəqəmli kodu daxil edin.",
"two-factor-disable-description": "Təsdiqləmək üçün parolunuzu və doğrulayıcı tətbiqinizdən doğrulama kodunu daxil edin.",
"two-factor-required-message": "Hesabınız üçün ikifaktorlu doğrulama məcburidir və söndürülə bilməz. Kömək üçün administratorunuzla əlaqə saxlayın.",
"two-factor-override-note": "Qlobal 2FA aktiv olduqda istifadəçi və komanda üzrə 2FA parametrləri əhəmiyyətini itirir.",
"two-factor-admin-global-description": "Bütün istifadəçilərdən növbəti daxilolmada ikifaktorlu doğrulamanı qurmalarını tələb edin. 2FA-nı hələ konfiqurasiya etməmiş istifadəçilərdən Umami-yə giriş əldə etməzdən əvvəl bunu etmək istəniləcək.",
"two-factor-require-user-description": "Aktivləşdirildikdə, bu istifadəçi hələ etməyibsə növbəti daxilolmada 2FA qurmağa dəvət ediləcək.",
"two-factor-require-team-description": "Aktivləşdirildikdə, bu komandanın bütün üzvləri hələ etməyiblərsə növbəti daxilolmada 2FA qurmağa dəvət ediləcək.",
"two-factor-user-description": "Hesabınıza əlavə təhlükəsizlik qatı əlavə edin. Hər daxil olanda parolunuz və doğrulayıcı tətbiqinizdən bir kod lazım olacaq.",
"two-factor-reset-description": "Bu istifadəçinin cari ikifaktorlu konfiqurasiyasını silin. Siyasətə görə 2FA hələ tələb olunursa, növbəti daxilolmada yeni QR kodu skan etmələri istəniləcək.",
"two-factor-error-invalid-code": "Yanlış doğrulama kodu. Zəhmət olmasa yenidən cəhd edin.",
"two-factor-error-missing-token": "Doğrulama tokeni yoxdur.",
"two-factor-error-invalid-partial-token": "Yanlış və ya vaxtı bitmiş doğrulama tokeni.",
"two-factor-error-invalid-backup-code": "Yanlış ehtiyat kodu.",
"two-factor-error-code-used": "Bu kod artıq istifadə olunub. Növbəti kodu gözləyin.",
"two-factor-error-too-many-attempts": "Çox sayda uğursuz cəhd. Hesabınız müvəqqəti bloklanıb.",
"two-factor-error-disable-not-allowed": "İkifaktorlu doğrulama məcburidir və söndürülə bilməz.",
"two-factor-error-incorrect-password": "Yanlış parol.",
"two-factor-error-already-enabled": "İkifaktorlu doğrulama artıq aktivdir.",
"two-factor-error-not-enabled": "İkifaktorlu doğrulama aktiv deyil.",
"two-factor-error-no-pending-setup": "Gözləyən ikifaktorlu doğrulama qurulumu tapılmadı.",
"two-factor-error-not-configured": "Serverdə etibarlı TWO_FACTOR_ENCRYPTION_KEY olmadığı üçün ikifaktorlu doğrulama əlçatan deyil. Kömək üçün administratorunuzla əlaqə saxlayın.",
"two-factor-locked-message": "Çox sayda uğursuz cəhd. {time} vaxtında yenidən cəhd edin.",
"two-factor-active-description": "Hesabınız ikifaktorlu doğrulama ilə qorunur. Hər daxilolma doğrulayıcı tətbiqinizdən bir kod tələb edir.",
"api-key-created": "API açarınızı indi kopyalayın. Onu bir daha görə bilməyəcəksiniz.",
"no-api-keys": "Siz heç bir API açarı yaratmamısınız.",
"oauth-login-prompt": "Davam etmək üçün daxil olun.",
"oauth-consent": "{name} Umami hesabınıza giriş əldə etmək istəyir.",
"oauth-permissions": "Bu, tətbiqə aşağıdakılara icazə verəcək:",
"oauth-scope-websites-read": "Girişiniz olan veb saytları görmək",
"oauth-scope-analytics-read": "Bu veb saytların analitika məlumatlarını oxumaq",
"oauth-redirect-notice": "Təsdiqlədikdən sonra {url} ünvanına yönləndiriləcəksiniz.",
"oauth-signed-in-as": "{username} kimi daxil olunub",
"oauth-invalid-request": "Bu icazə sorğusu etibarsızdır.",
"oauth-redirecting": "Yönləndirilir…"
}
}
@@ -0,0 +1,63 @@
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { expect, test, vi } from 'vitest';
import { FilterRecord } from './FilterRecord';
vi.mock('@umami/react-zen', () => ({
Button: ({ children }: { children: ReactNode }) => <button>{children}</button>,
Column: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Grid: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Icon: ({ children }: { children: ReactNode }) => <span>{children}</span>,
Label: ({ children }: { children: ReactNode }) => <label>{children}</label>,
ListItem: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Loading: () => <div>Loading</div>,
Select: ({ children }: { children: ReactNode }) => <div>{children}</div>,
TextField: ({ value }: { value: string }) => (
<input aria-label="Filter value" value={value} readOnly />
),
}));
vi.mock('@/components/common/Empty', () => ({ Empty: () => <div>Empty</div> }));
vi.mock('@/components/common/MultiSelect', () => ({
MultiSelect: ({ children }: { children: ReactNode }) => <div>{children}</div>,
MultiSelectItem: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}));
vi.mock('@/components/hooks', () => ({
useFilters: () => ({ fields: [], operators: [] }),
useFormat: () => ({ formatValue: (value: string) => value }),
useWebsiteValuesQuery: () => ({ data: [], isLoading: false }),
}));
vi.mock('@/components/icons', () => ({ X: () => null }));
test('keeps commas in regex filter values', () => {
const value = '^[a-zA-Z0-9]{21,22}$';
render(
<FilterRecord
type="path"
startDate={new Date('2026-08-01')}
endDate={new Date('2026-08-02')}
name="path"
operator="re"
value={value}
/>,
);
expect(screen.getByLabelText('Filter value')).toHaveValue(value);
});
test('keeps commas when changing to a search operator', () => {
const value = '^[a-zA-Z0-9]{21,22}$';
const props = {
type: 'path',
startDate: new Date('2026-08-01'),
endDate: new Date('2026-08-02'),
name: 'path',
value,
};
const { rerender } = render(<FilterRecord {...props} operator="eq" />);
rerender(<FilterRecord {...props} operator="re" />);
expect(screen.getByLabelText('Filter value')).toHaveValue(value);
});
+13 -4
View File
@@ -9,7 +9,7 @@ import {
Select,
TextField,
} from '@umami/react-zen';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Empty } from '@/components/common/Empty';
import { MultiSelect, MultiSelectItem } from '@/components/common/MultiSelect';
import { useFilters, useFormat, useWebsiteValuesQuery } from '@/components/hooks';
@@ -29,6 +29,12 @@ export interface FilterRecordProps {
onChange?: (name: string, value: string) => void;
}
function getSelectedValues(value: string | string[], operator: string) {
if (Array.isArray(value)) return value;
if (!value) return [];
return isSearchOperator(operator) ? [value] : value.split(',');
}
export function FilterRecord({
websiteId,
type,
@@ -42,8 +48,8 @@ export function FilterRecord({
onChange,
}: FilterRecordProps) {
const { fields, operators } = useFilters();
const initValues = Array.isArray(value) ? value : value ? value.split(',') : [];
const [selected, setSelected] = useState<string[]>(initValues);
const isSearch = isSearchOperator(operator);
const [selected, setSelected] = useState<string[]>(() => getSelectedValues(value, operator));
const [search, setSearch] = useState('');
const { formatValue } = useFormat();
const { data, isLoading } = useWebsiteValuesQuery({
@@ -53,9 +59,12 @@ export function FilterRecord({
startDate,
endDate,
});
const isSearch = isSearchOperator(operator);
const items = data?.filter(({ value }) => value) || [];
useEffect(() => {
setSelected(getSelectedValues(value, operator));
}, [operator, value]);
const handleSearch = (value: string) => {
setSearch(value);
};
+2
View File
@@ -697,6 +697,7 @@ export const CURRENCIES = [
{ id: 'PLN', name: 'Polish Złoty' },
{ id: 'NOK', name: 'Norwegian Krone' },
{ id: 'DKK', name: 'Danish Krone' },
{ id: 'ISK', name: 'Icelandic Króna' },
{ id: 'NZD', name: 'New Zealand Dollar' },
{ id: 'ZAR', name: 'South African Rand' },
{ id: 'MXN', name: 'Mexican Peso' },
@@ -705,6 +706,7 @@ export const CURRENCIES = [
{ id: 'MYR', name: 'Malaysian Ringgit' },
{ id: 'INR', name: 'Indian Rupee' },
{ id: 'KRW', name: 'South Korean Won' },
{ id: 'TWD', name: 'New Taiwan Dollar' },
{ id: 'BRL', name: 'Brazilian Real' },
{ id: 'TRY', name: 'Turkish Lira' },
{ id: 'CZK', name: 'Czech Koruna' },
+2
View File
@@ -1,5 +1,6 @@
import {
arSA,
az,
be,
bg,
bn,
@@ -49,6 +50,7 @@ import {
export const languages = {
'ar-SA': { label: 'العربية', dateLocale: arSA, dir: 'rtl' },
'az-AZ': { label: 'Azərbaycan', dateLocale: az },
'be-BY': { label: 'Беларуская', dateLocale: be },
'bg-BG': { label: 'български език', dateLocale: bg },
'bn-BD': { label: 'বাংলা', dateLocale: bn },
+57 -1
View File
@@ -24,9 +24,10 @@ vi.mock('@/generated/prisma/client', () => ({
}));
let getRawQueryClient!: typeof import('./prisma').getRawQueryClient;
let prisma!: typeof import('./prisma').default;
beforeAll(async () => {
({ getRawQueryClient } = await import('./prisma'));
({ getRawQueryClient, default: prisma } = await import('./prisma'));
});
interface RawQueryClient {
@@ -80,3 +81,58 @@ describe('getRawQueryClient', () => {
expect(getRawQueryClient(client, { write: true })).toBe(client);
});
});
describe('pagedRawQuery default ordering', () => {
function mockQueries(count = '4') {
const queryRaw = vi.mocked((prisma.client as any).$queryRawUnsafe);
queryRaw.mockClear();
queryRaw.mockImplementation(async (sql: string) =>
sql.includes('count(*) as num') ? [{ num: count }] : [{ id: 1 }],
);
return queryRaw;
}
test.each([undefined, 5])(
'applies trusted default order only to the page query with cap %s',
async maxResults => {
const queryRaw = mockQueries('5');
const result = await prisma.pagedRawQuery(
'select * from thing',
{},
{ page: 2, pageSize: 2, maxResults },
'test',
'max(created_at) desc, session_id',
);
expect(queryRaw).toHaveBeenCalledTimes(2);
expect(queryRaw.mock.calls[0][0]).not.toContain('order by max(created_at) desc');
expect(queryRaw.mock.calls[1][0]).toContain('order by max(created_at) desc, session_id');
expect(queryRaw.mock.calls[1][0]).toContain('limit 2 offset 2');
expect(result).toEqual({
data: [{ id: 1 }],
count: 5,
page: 2,
pageSize: 2,
isCapped: !!maxResults,
orderBy: undefined,
});
},
);
test('keeps explicit ordering and legacy no-order behavior', async () => {
const queryRaw = mockQueries();
const result = await prisma.pagedRawQuery(
'select * from thing',
{},
{ page: 1, pageSize: 2, orderBy: 'name', sortDescending: true },
undefined,
'max(created_at) desc, session_id',
);
expect(queryRaw.mock.calls[1][0]).toContain('order by name desc');
expect(queryRaw.mock.calls[1][0]).not.toContain('order by max(created_at) desc, session_id');
expect(result.orderBy).toBe('name');
queryRaw.mockClear();
await prisma.pagedRawQuery('select * from thing', {}, { page: 1, pageSize: 2 });
expect(queryRaw.mock.calls[1][0]).not.toMatch(/order by/);
});
});
+2 -1
View File
@@ -786,6 +786,7 @@ async function pagedRawQuery(
queryParams: Record<string, any>,
filters: QueryFilters,
name?: string,
defaultOrderBy?: string,
) {
const { page = 1, pageSize, orderBy, sortDescending = false } = filters;
const size = +pageSize || DEFAULT_PAGE_SIZE;
@@ -793,7 +794,7 @@ async function pagedRawQuery(
const direction = sortDescending ? 'desc' : 'asc';
const statements = [
orderBy && `order by ${orderBy} ${direction}`,
orderBy ? `order by ${orderBy} ${direction}` : defaultOrderBy && `order by ${defaultOrderBy}`,
+size > 0 && `limit ${+size} offset ${offset}`,
]
.filter(n => n)
@@ -75,11 +75,11 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
session.country,
session.region,
session.city
order by max(website_event.created_at) desc, session.session_id
`,
queryParams,
filters,
FUNCTION_NAME,
'max(website_event.created_at) desc, session.session_id',
);
}
+38
View File
@@ -0,0 +1,38 @@
import { afterEach, expect, test, vi } from 'vitest';
afterEach(() => {
delete (window as Window & { umami?: unknown }).umami;
delete (document as Document & { currentScript?: HTMLScriptElement }).currentScript;
delete (document as Document & { readyState?: DocumentReadyState }).readyState;
vi.unstubAllGlobals();
vi.resetModules();
});
test('identifies data-distinct-id before the initial page view', async () => {
const script = document.createElement('script');
script.src = 'https://analytics.example.com/script.js';
script.dataset.websiteId = 'website-id';
script.dataset.distinctId = 'visitor-id';
Object.defineProperties(document, {
currentScript: { configurable: true, value: script },
readyState: { configurable: true, value: 'complete' },
});
const fetchMock = vi.fn().mockResolvedValue({ json: vi.fn().mockResolvedValue({}) });
vi.stubGlobal('fetch', fetchMock);
await import('./index');
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2));
const requests = fetchMock.mock.calls.map(([, init]) => JSON.parse(init.body));
expect(requests[0]).toMatchObject({
type: 'identify',
payload: { id: 'visitor-id', website: 'website-id' },
});
expect(requests[1]).toMatchObject({
type: 'event',
payload: { id: 'visitor-id', website: 'website-id' },
});
});
+6 -1
View File
@@ -241,6 +241,7 @@ type MetricEntry = PerformanceEntry & {
const website = config('website-id');
const hostUrl = config('host-url');
const beforeSend = config('before-send');
const distinctId = config('distinct-id') || undefined;
const tag = config('tag') || undefined;
const autoTrack = config('auto-track') !== _false;
const dnt = config('do-not-track') === _true;
@@ -641,9 +642,13 @@ type MetricEntry = PerformanceEntry & {
let initialized = false;
let disabled = false;
let cache: string | undefined;
let identity: string | undefined;
let identity = distinctId;
let flushPerformance: (() => void) | undefined;
if (distinctId) {
void identify(distinctId);
}
if (autoTrack && !trackingDisabled()) {
if (document.readyState === 'complete') {
init();