mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 14:47:14 +01:00
Merge branch 'master' into hosts-support
This commit is contained in:
commit
e11c2e452c
69 changed files with 3783 additions and 2197 deletions
|
|
@ -11,7 +11,7 @@ A detailed getting started guide can be found at [https://umami.is/docs/](https:
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- A server with Node.js version 16.13 or newer
|
- A server with Node.js version 16.13 or newer
|
||||||
- A database. Umami supports [MySQL](https://www.mysql.com/) and [Postgresql](https://www.postgresql.org/) databases.
|
- A database. Umami supports [MySQL](https://www.mysql.com/) (minimum v8.0) and [Postgresql](https://www.postgresql.org/) (minimum v12.14) databases.
|
||||||
|
|
||||||
### Install Yarn
|
### Install Yarn
|
||||||
|
|
||||||
|
|
@ -72,13 +72,13 @@ docker compose up -d
|
||||||
Alternatively, to pull just the Umami Docker image with PostgreSQL support:
|
Alternatively, to pull just the Umami Docker image with PostgreSQL support:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull ghcr.io/umami-software/umami:postgresql-latest
|
docker pull docker.umami.is/umami-software/umami:postgresql-latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Or with MySQL support:
|
Or with MySQL support:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull ghcr.io/umami-software/umami:mysql-latest
|
docker pull docker.umami.is/umami-software/umami:mysql-latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## Getting updates
|
## Getting updates
|
||||||
|
|
@ -101,3 +101,4 @@ docker compose up --force-recreate
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "umami",
|
"name": "umami",
|
||||||
"version": "2.11.2",
|
"version": "2.11.3",
|
||||||
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
|
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
|
||||||
"author": "Umami Software, Inc. <hello@umami.is>",
|
"author": "Umami Software, Inc. <hello@umami.is>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
"@react-spring/web": "^9.7.3",
|
"@react-spring/web": "^9.7.3",
|
||||||
"@tanstack/react-query": "^5.28.6",
|
"@tanstack/react-query": "^5.28.6",
|
||||||
"@umami/prisma-client": "^0.14.0",
|
"@umami/prisma-client": "^0.14.0",
|
||||||
"@umami/redis-client": "^0.18.0",
|
"@umami/redis-client": "^0.20.0",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"chart.js": "^4.4.2",
|
"chart.js": "^4.4.2",
|
||||||
"chartjs-adapter-date-fns": "^3.0.0",
|
"chartjs-adapter-date-fns": "^3.0.0",
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@formatjs/cli": "^4.2.29",
|
"@formatjs/cli": "^4.2.29",
|
||||||
"@netlify/plugin-nextjs": "^4.41.3",
|
"@netlify/plugin-nextjs": "^5.1.0",
|
||||||
"@rollup/plugin-alias": "^5.0.0",
|
"@rollup/plugin-alias": "^5.0.0",
|
||||||
"@rollup/plugin-commonjs": "^25.0.4",
|
"@rollup/plugin-commonjs": "^25.0.4",
|
||||||
"@rollup/plugin-json": "^6.0.0",
|
"@rollup/plugin-json": "^6.0.0",
|
||||||
|
|
|
||||||
251
public/intl/country/bs-BA.json
Normal file
251
public/intl/country/bs-BA.json
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
{
|
||||||
|
"AF": "Afganistan",
|
||||||
|
"AL": "Albanija",
|
||||||
|
"DZ": "Al\u017eir",
|
||||||
|
"VI": "Ameri\u010dka Djevi\u010danska ostrva",
|
||||||
|
"AS": "Ameri\u010dka Samoa",
|
||||||
|
"UM": "Ameri\u010dka Vanjska Ostrva",
|
||||||
|
"AD": "Andora",
|
||||||
|
"AO": "Angola",
|
||||||
|
"AI": "Angvila",
|
||||||
|
"AQ": "Antarktika",
|
||||||
|
"AG": "Antigva i Barbuda",
|
||||||
|
"AR": "Argentina",
|
||||||
|
"AM": "Armenija",
|
||||||
|
"AW": "Aruba",
|
||||||
|
"AU": "Australija",
|
||||||
|
"AT": "Austrija",
|
||||||
|
"AZ": "Azerbejd\u017ean",
|
||||||
|
"BS": "Bahami",
|
||||||
|
"BH": "Bahrein",
|
||||||
|
"BD": "Banglade\u0161",
|
||||||
|
"BB": "Barbados",
|
||||||
|
"BE": "Belgija",
|
||||||
|
"BZ": "Belize",
|
||||||
|
"BJ": "Benin",
|
||||||
|
"BM": "Bermuda",
|
||||||
|
"BY": "Bjelorusija",
|
||||||
|
"BW": "Bocvana",
|
||||||
|
"BO": "Bolivija",
|
||||||
|
"BA": "Bosna i Hercegovina",
|
||||||
|
"CX": "Bo\u017ei\u0107no ostrvo",
|
||||||
|
"BR": "Brazil",
|
||||||
|
"VG": "Britanska Djevi\u010danska ostrva",
|
||||||
|
"IO": "Britanska Teritorija u Indijskom Okeanu",
|
||||||
|
"BN": "Brunej",
|
||||||
|
"BG": "Bugarska",
|
||||||
|
"BF": "Burkina Faso",
|
||||||
|
"BI": "Burundi",
|
||||||
|
"BT": "Butan",
|
||||||
|
"CF": "Centralnoafri\u010dka Republika",
|
||||||
|
"ME": "Crna Gora",
|
||||||
|
"TD": "\u010cad",
|
||||||
|
"CZ": "\u010ce\u0161ka",
|
||||||
|
"CL": "\u010cile",
|
||||||
|
"DK": "Danska",
|
||||||
|
"CD": "Demokratska Republika Kongo",
|
||||||
|
"DM": "Dominika",
|
||||||
|
"DO": "Dominikanska Republika",
|
||||||
|
"DJ": "D\u017eibuti",
|
||||||
|
"EG": "Egipat",
|
||||||
|
"EC": "Ekvador",
|
||||||
|
"GQ": "Ekvatorijalna Gvineja",
|
||||||
|
"ER": "Eritreja",
|
||||||
|
"EE": "Estonija",
|
||||||
|
"SZ": "Esvatini",
|
||||||
|
"ET": "Etiopija",
|
||||||
|
"FO": "Farska ostrva",
|
||||||
|
"FJ": "Fid\u017ei",
|
||||||
|
"PH": "Filipini",
|
||||||
|
"FI": "Finska",
|
||||||
|
"FK": "Folklandska ostrva",
|
||||||
|
"FR": "Francuska",
|
||||||
|
"GF": "Francuska Gvajana",
|
||||||
|
"PF": "Francuska Polinezija",
|
||||||
|
"TF": "Francuske Ju\u017ene Teritorije",
|
||||||
|
"GA": "Gabon",
|
||||||
|
"GM": "Gambija",
|
||||||
|
"GH": "Gana",
|
||||||
|
"GG": "Gernzi",
|
||||||
|
"GI": "Gibraltar",
|
||||||
|
"GR": "Gr\u010dka",
|
||||||
|
"GD": "Grenada",
|
||||||
|
"GL": "Grenland",
|
||||||
|
"GE": "Gruzija",
|
||||||
|
"GU": "Guam",
|
||||||
|
"GP": "Gvadalupe",
|
||||||
|
"GY": "Gvajana",
|
||||||
|
"GT": "Gvatemala",
|
||||||
|
"GN": "Gvineja",
|
||||||
|
"GW": "Gvineja-Bisao",
|
||||||
|
"HT": "Haiti",
|
||||||
|
"HM": "Herd i arhipelag MekDonald",
|
||||||
|
"NL": "Holandija",
|
||||||
|
"HN": "Honduras",
|
||||||
|
"HK": "Hong Kong (SAR Kina)",
|
||||||
|
"HR": "Hrvatska",
|
||||||
|
"IN": "Indija",
|
||||||
|
"ID": "Indonezija",
|
||||||
|
"IQ": "Irak",
|
||||||
|
"IR": "Iran",
|
||||||
|
"IE": "Irska",
|
||||||
|
"IS": "Island",
|
||||||
|
"TL": "Isto\u010dni Timor",
|
||||||
|
"IT": "Italija",
|
||||||
|
"IL": "Izrael",
|
||||||
|
"JM": "Jamajka",
|
||||||
|
"JP": "Japan",
|
||||||
|
"YE": "Jemen",
|
||||||
|
"JE": "Jersey",
|
||||||
|
"JO": "Jordan",
|
||||||
|
"GS": "Ju\u017ena D\u017eord\u017eija i Ju\u017ena Sendvi\u010d ostrva",
|
||||||
|
"KR": "Ju\u017ena Koreja",
|
||||||
|
"SS": "Ju\u017eni Sudan",
|
||||||
|
"ZA": "Ju\u017enoafri\u010dka Republika",
|
||||||
|
"KY": "Kajmanska ostrva",
|
||||||
|
"KH": "Kambod\u017ea",
|
||||||
|
"CM": "Kamerun",
|
||||||
|
"CA": "Kanada",
|
||||||
|
"CV": "Kape Verde",
|
||||||
|
"BQ": "Karipska Holandija",
|
||||||
|
"QA": "Katar",
|
||||||
|
"KZ": "Kazahstan",
|
||||||
|
"KE": "Kenija",
|
||||||
|
"CN": "Kina",
|
||||||
|
"CY": "Kipar",
|
||||||
|
"KG": "Kirgistan",
|
||||||
|
"KI": "Kiribati",
|
||||||
|
"CC": "Kokosova (Keelingova) ostrva",
|
||||||
|
"CO": "Kolumbija",
|
||||||
|
"KM": "Komori",
|
||||||
|
"CG": "Kongo",
|
||||||
|
"CR": "Kostarika",
|
||||||
|
"CU": "Kuba",
|
||||||
|
"CK": "Kukova ostrva",
|
||||||
|
"CW": "Kurasao",
|
||||||
|
"KW": "Kuvajt",
|
||||||
|
"LA": "Laos",
|
||||||
|
"LV": "Latvija",
|
||||||
|
"LS": "Lesoto",
|
||||||
|
"LB": "Liban",
|
||||||
|
"LR": "Liberija",
|
||||||
|
"LY": "Libija",
|
||||||
|
"LI": "Lihten\u0161tajn",
|
||||||
|
"LT": "Litvanija",
|
||||||
|
"LU": "Luksemburg",
|
||||||
|
"MG": "Madagaskar",
|
||||||
|
"HU": "Ma\u0111arska",
|
||||||
|
"YT": "Majote",
|
||||||
|
"MO": "Makao (SAR Kina)",
|
||||||
|
"MW": "Malavi",
|
||||||
|
"MV": "Maldivi",
|
||||||
|
"MY": "Malezija",
|
||||||
|
"ML": "Mali",
|
||||||
|
"MT": "Malta",
|
||||||
|
"MA": "Maroko",
|
||||||
|
"MH": "Mar\u0161alova ostrva",
|
||||||
|
"MQ": "Martinik",
|
||||||
|
"MU": "Mauricijus",
|
||||||
|
"MR": "Mauritanija",
|
||||||
|
"MX": "Meksiko",
|
||||||
|
"FM": "Mikronezija",
|
||||||
|
"MM": "Mjanmar",
|
||||||
|
"MD": "Moldavija",
|
||||||
|
"MC": "Monako",
|
||||||
|
"MN": "Mongolija",
|
||||||
|
"MS": "Monserat",
|
||||||
|
"MZ": "Mozambik",
|
||||||
|
"NA": "Namibija",
|
||||||
|
"NR": "Nauru",
|
||||||
|
"NP": "Nepal",
|
||||||
|
"NE": "Niger",
|
||||||
|
"NG": "Nigerija",
|
||||||
|
"NI": "Nikaragva",
|
||||||
|
"NU": "Niue",
|
||||||
|
"NO": "Norve\u0161ka",
|
||||||
|
"NC": "Nova Kaledonija",
|
||||||
|
"NZ": "Novi Zeland",
|
||||||
|
"DE": "Njema\u010dka",
|
||||||
|
"CI": "Obala Slonova\u010de",
|
||||||
|
"AX": "Olandska ostrva",
|
||||||
|
"OM": "Oman",
|
||||||
|
"TC": "Ostrva Turks i Kaikos",
|
||||||
|
"WF": "Ostrva Valis i Futuna",
|
||||||
|
"BV": "Ostrvo Buve",
|
||||||
|
"IM": "Ostrvo Man",
|
||||||
|
"NF": "Ostrvo Norfolk",
|
||||||
|
"PK": "Pakistan",
|
||||||
|
"PW": "Palau",
|
||||||
|
"PS": "Palestinska Teritorija",
|
||||||
|
"PA": "Panama",
|
||||||
|
"PG": "Papua Nova Gvineja",
|
||||||
|
"PY": "Paragvaj",
|
||||||
|
"PE": "Peru",
|
||||||
|
"PN": "Pitkernska Ostrva",
|
||||||
|
"PL": "Poljska",
|
||||||
|
"PR": "Porto Riko",
|
||||||
|
"PT": "Portugal",
|
||||||
|
"RE": "Reunion",
|
||||||
|
"RW": "Ruanda",
|
||||||
|
"RO": "Rumunija",
|
||||||
|
"RU": "Rusija",
|
||||||
|
"SV": "Salvador",
|
||||||
|
"WS": "Samoa",
|
||||||
|
"SM": "San Marino",
|
||||||
|
"ST": "Sao Tome i Principe",
|
||||||
|
"SA": "Saudijska Arabija",
|
||||||
|
"SC": "Sej\u0161eli",
|
||||||
|
"SN": "Senegal",
|
||||||
|
"SL": "Sijera Leone",
|
||||||
|
"SG": "Singapur",
|
||||||
|
"SX": "Sint Marten",
|
||||||
|
"SY": "Sirija",
|
||||||
|
"US": "Sjedinjene Dr\u017eave",
|
||||||
|
"KP": "Sjeverna Koreja",
|
||||||
|
"MK": "Sjeverna Makedonija",
|
||||||
|
"MP": "Sjeverna Marijanska ostrva",
|
||||||
|
"SK": "Slova\u010dka",
|
||||||
|
"SI": "Slovenija",
|
||||||
|
"SB": "Solomonska Ostrva",
|
||||||
|
"SO": "Somalija",
|
||||||
|
"RS": "Srbija",
|
||||||
|
"SD": "Sudan",
|
||||||
|
"SR": "Surinam",
|
||||||
|
"SJ": "Svalbard i Jan Majen",
|
||||||
|
"SH": "Sveta Helena",
|
||||||
|
"LC": "Sveta Lucija",
|
||||||
|
"BL": "Sveti Bartolomej",
|
||||||
|
"KN": "Sveti Kits i Nevis",
|
||||||
|
"MF": "Sveti Martin",
|
||||||
|
"PM": "Sveti Petar i Mikelon",
|
||||||
|
"VC": "Sveti Vinsent i Grenadin",
|
||||||
|
"ES": "\u0160panija",
|
||||||
|
"LK": "\u0160ri Lanka",
|
||||||
|
"SE": "\u0160vedska",
|
||||||
|
"CH": "\u0160vicarska",
|
||||||
|
"TJ": "Tad\u017eikistan",
|
||||||
|
"TH": "Tajland",
|
||||||
|
"TW": "Tajvan",
|
||||||
|
"TZ": "Tanzanija",
|
||||||
|
"TG": "Togo",
|
||||||
|
"TK": "Tokelau",
|
||||||
|
"TO": "Tonga",
|
||||||
|
"TT": "Trinidad i Tobago",
|
||||||
|
"TN": "Tunis",
|
||||||
|
"TM": "Turkmenistan",
|
||||||
|
"TR": "Turska",
|
||||||
|
"TV": "Tuvalu",
|
||||||
|
"UG": "Uganda",
|
||||||
|
"AE": "Ujedinjeni Arapski Emirati",
|
||||||
|
"GB": "Ujedinjeno Kraljevstvo",
|
||||||
|
"UA": "Ukrajina",
|
||||||
|
"UY": "Urugvaj",
|
||||||
|
"UZ": "Uzbekistan",
|
||||||
|
"VU": "Vanuatu",
|
||||||
|
"VA": "Vatikan",
|
||||||
|
"VE": "Venecuela",
|
||||||
|
"VN": "Vijetnam",
|
||||||
|
"ZM": "Zambija",
|
||||||
|
"EH": "Zapadna Sahara",
|
||||||
|
"ZW": "Zimbabve"
|
||||||
|
}
|
||||||
611
public/intl/language/bs-BA.json
Normal file
611
public/intl/language/bs-BA.json
Normal file
|
|
@ -0,0 +1,611 @@
|
||||||
|
{
|
||||||
|
"ab": "abhazijski",
|
||||||
|
"ace": "a\u010dineski",
|
||||||
|
"ada": "adangmejski",
|
||||||
|
"ady": "adigejski",
|
||||||
|
"aa": "afarski",
|
||||||
|
"afh": "afrihili",
|
||||||
|
"af": "afrikanerski",
|
||||||
|
"agq": "aghem",
|
||||||
|
"ain": "ainu",
|
||||||
|
"ay": "ajmara",
|
||||||
|
"akk": "akadijski",
|
||||||
|
"ak": "akan",
|
||||||
|
"ach": "akoli",
|
||||||
|
"bss": "Akoose",
|
||||||
|
"akz": "Alabama",
|
||||||
|
"sq": "albanski",
|
||||||
|
"arq": "Algerian Arabic",
|
||||||
|
"ale": "aljut",
|
||||||
|
"ase": "American Sign Language",
|
||||||
|
"en_US": "ameri\u010dki engleski",
|
||||||
|
"am": "amharski",
|
||||||
|
"anp": "angika",
|
||||||
|
"njo": "Ao Naga",
|
||||||
|
"an": "aragone\u017eanski",
|
||||||
|
"aro": "Araona",
|
||||||
|
"arp": "arapaho",
|
||||||
|
"ar": "arapski",
|
||||||
|
"arn": "araukanski",
|
||||||
|
"arw": "aravak",
|
||||||
|
"arc": "armajski",
|
||||||
|
"rup": "aromanijski",
|
||||||
|
"frp": "Arpitan",
|
||||||
|
"as": "asemijski",
|
||||||
|
"ast": "asturijski",
|
||||||
|
"asa": "asu",
|
||||||
|
"cch": "atsam",
|
||||||
|
"en_AU": "australski engleski",
|
||||||
|
"de_AT": "austrijski njema\u010dki",
|
||||||
|
"awa": "avadhi",
|
||||||
|
"av": "avarski",
|
||||||
|
"ae": "avestanski",
|
||||||
|
"az": "azerbejd\u017eanski",
|
||||||
|
"bfq": "Badaga",
|
||||||
|
"ksf": "bafia",
|
||||||
|
"bfd": "Bafut",
|
||||||
|
"bqi": "Bakhtiari",
|
||||||
|
"ban": "balinezijski",
|
||||||
|
"bal": "balu\u010di",
|
||||||
|
"bm": "bambara",
|
||||||
|
"bax": "Bamun",
|
||||||
|
"bjn": "Banjar",
|
||||||
|
"bas": "basa",
|
||||||
|
"eu": "baskijski",
|
||||||
|
"ba": "ba\u0161kirski",
|
||||||
|
"bbc": "Batak Toba",
|
||||||
|
"bar": "Bavarian",
|
||||||
|
"bej": "beja",
|
||||||
|
"bem": "bemba",
|
||||||
|
"bez": "bena",
|
||||||
|
"bn": "bengalski",
|
||||||
|
"bew": "Betawi",
|
||||||
|
"zxx": "bez lingvisti\u010dkog sadr\u017eaja",
|
||||||
|
"bik": "bikol",
|
||||||
|
"bin": "bini",
|
||||||
|
"bpy": "Bishnupriya",
|
||||||
|
"bi": "bislama",
|
||||||
|
"be": "bjeloruski",
|
||||||
|
"byn": "blin",
|
||||||
|
"zbl": "blisimboli",
|
||||||
|
"brx": "bodo",
|
||||||
|
"bho": "bojpuri",
|
||||||
|
"bs": "bosanski",
|
||||||
|
"brh": "Brahui",
|
||||||
|
"bra": "braj",
|
||||||
|
"pt_BR": "Brazilian Portuguese",
|
||||||
|
"br": "bretonski",
|
||||||
|
"en_GB": "britanski engleski",
|
||||||
|
"bg": "bugarski",
|
||||||
|
"bug": "bugine\u017eanskii",
|
||||||
|
"bum": "Bulu",
|
||||||
|
"bua": "buriat",
|
||||||
|
"my": "burmanski",
|
||||||
|
"frc": "Cajun French",
|
||||||
|
"yue": "Cantonese",
|
||||||
|
"cps": "Capiznon",
|
||||||
|
"cay": "Cayuga",
|
||||||
|
"ceb": "cebuano",
|
||||||
|
"dtp": "Central Dusun",
|
||||||
|
"esu": "Central Yupik",
|
||||||
|
"shu": "Chadian Arabic",
|
||||||
|
"qug": "Chimborazo Highland Quichua",
|
||||||
|
"ksh": "Colognian",
|
||||||
|
"swb": "Comorian",
|
||||||
|
"cy": "cy",
|
||||||
|
"chg": "\u010dagatai",
|
||||||
|
"ch": "\u010damoro",
|
||||||
|
"ce": "\u010de\u010denski",
|
||||||
|
"chy": "\u010dejenski",
|
||||||
|
"cs": "\u010de\u0161ki",
|
||||||
|
"chb": "\u010dib\u010da",
|
||||||
|
"cgg": "\u010diga",
|
||||||
|
"chn": "\u010dinukski",
|
||||||
|
"chp": "\u010dipvijanski",
|
||||||
|
"chr": "\u010diroki",
|
||||||
|
"cho": "\u010doktavski",
|
||||||
|
"chk": "\u010dukeski",
|
||||||
|
"cv": "\u010duva\u0161ki",
|
||||||
|
"dak": "dakota",
|
||||||
|
"da": "danski",
|
||||||
|
"dar": "dargva",
|
||||||
|
"dzg": "Dazaga",
|
||||||
|
"del": "delaver",
|
||||||
|
"din": "dinka",
|
||||||
|
"dv": "divehijski",
|
||||||
|
"doi": "dogri",
|
||||||
|
"dgr": "dogrib",
|
||||||
|
"dsb": "donjolu\u017ei\u010dkosrpski",
|
||||||
|
"dua": "duala",
|
||||||
|
"gez": "d\u017eiz",
|
||||||
|
"dz": "d\u017eonga",
|
||||||
|
"dyu": "\u0111ula",
|
||||||
|
"efi": "efikski",
|
||||||
|
"arz": "Egyptian Arabic",
|
||||||
|
"eka": "ekajuk",
|
||||||
|
"elx": "elamitski",
|
||||||
|
"ebu": "embu",
|
||||||
|
"egl": "Emilian",
|
||||||
|
"en": "engleski",
|
||||||
|
"myv": "erzija",
|
||||||
|
"eo": "esperanto",
|
||||||
|
"et": "estonski",
|
||||||
|
"pt_PT": "European Portuguese",
|
||||||
|
"ee": "eve",
|
||||||
|
"ewo": "evondo",
|
||||||
|
"es_ES": "evropski \u0161panski",
|
||||||
|
"ext": "Extremaduran",
|
||||||
|
"fan": "fang",
|
||||||
|
"fat": "fanti",
|
||||||
|
"fo": "farski",
|
||||||
|
"phn": "feni\u010danski",
|
||||||
|
"fj": "fid\u017eijski",
|
||||||
|
"hif": "Fiji Hindi",
|
||||||
|
"fil": "filipinski",
|
||||||
|
"fi": "finski",
|
||||||
|
"nl_BE": "flamanski",
|
||||||
|
"fon": "fon",
|
||||||
|
"gur": "Frafra",
|
||||||
|
"fr": "francuski",
|
||||||
|
"fur": "friulijski",
|
||||||
|
"fy": "frizijski",
|
||||||
|
"ff": "fulah",
|
||||||
|
"gaa": "ga",
|
||||||
|
"gag": "gagau\u0161ki",
|
||||||
|
"gay": "gajo",
|
||||||
|
"gl": "galski",
|
||||||
|
"gan": "Gan Chinese",
|
||||||
|
"lg": "ganda",
|
||||||
|
"gba": "gbaja",
|
||||||
|
"aln": "Gheg Albanian",
|
||||||
|
"bbj": "Ghomala",
|
||||||
|
"glk": "Gilaki",
|
||||||
|
"gil": "gilbert\u0161ki",
|
||||||
|
"gom": "Goan Konkani",
|
||||||
|
"gon": "gondi",
|
||||||
|
"hsb": "gornjolu\u017ei\u010dkosrpski",
|
||||||
|
"de_CH": "gornjonjema\u010dki (\u0161vicarski)",
|
||||||
|
"gor": "gorontalo",
|
||||||
|
"got": "gotski",
|
||||||
|
"el": "gr\u010dki",
|
||||||
|
"grb": "grebo",
|
||||||
|
"ka": "gruzijski",
|
||||||
|
"gu": "gud\u017earati",
|
||||||
|
"guz": "gusii",
|
||||||
|
"gn": "gvarani",
|
||||||
|
"gwi": "Gwich\u02bcin",
|
||||||
|
"ht": "hai\u0107anski",
|
||||||
|
"hai": "haida",
|
||||||
|
"hak": "Hakka Chinese",
|
||||||
|
"ha": "hausa",
|
||||||
|
"haw": "havajski",
|
||||||
|
"he": "hebrejski",
|
||||||
|
"hz": "herero",
|
||||||
|
"hil": "hiligajnon",
|
||||||
|
"hi": "hindi",
|
||||||
|
"ho": "hiri motu",
|
||||||
|
"hit": "hitite",
|
||||||
|
"hmn": "hmong",
|
||||||
|
"nl": "holandski",
|
||||||
|
"hr": "hrvatski",
|
||||||
|
"hup": "hupa",
|
||||||
|
"iba": "iban",
|
||||||
|
"ibb": "Ibibio",
|
||||||
|
"io": "ido",
|
||||||
|
"ig": "igbo",
|
||||||
|
"ilo": "iloko",
|
||||||
|
"smn": "inari sami",
|
||||||
|
"id": "indonezijski",
|
||||||
|
"izh": "Ingrian",
|
||||||
|
"inh": "ingu\u0161etski",
|
||||||
|
"ia": "interlingva",
|
||||||
|
"ie": "interlingve",
|
||||||
|
"iu": "inuktitut",
|
||||||
|
"ik": "inupiak",
|
||||||
|
"ga": "irski",
|
||||||
|
"is": "islandski",
|
||||||
|
"frs": "isto\u010dni frizijski",
|
||||||
|
"it": "italijanski",
|
||||||
|
"sah": "jakut",
|
||||||
|
"jam": "Jamaican Creole English",
|
||||||
|
"yao": "jao",
|
||||||
|
"ja": "japanski",
|
||||||
|
"yap": "jape\u0161ki",
|
||||||
|
"jv": "javanski",
|
||||||
|
"hy": "jermenski",
|
||||||
|
"yi": "jidi\u0161",
|
||||||
|
"dyo": "jola-fonyi",
|
||||||
|
"yo": "jorubanski",
|
||||||
|
"jrb": "judeo-arapski",
|
||||||
|
"jpr": "judeo-persijski",
|
||||||
|
"jut": "Jutish",
|
||||||
|
"alt": "ju\u017eni altai",
|
||||||
|
"nr": "ju\u017eni ndebele",
|
||||||
|
"sma": "ju\u017eni sami",
|
||||||
|
"kbd": "kabardijski",
|
||||||
|
"kab": "kabile",
|
||||||
|
"kac": "ka\u010din",
|
||||||
|
"cad": "kado",
|
||||||
|
"kgp": "Kaingang",
|
||||||
|
"kkj": "Kako",
|
||||||
|
"kl": "kalalisutski",
|
||||||
|
"kln": "kalenjin",
|
||||||
|
"xal": "kalmik",
|
||||||
|
"kam": "kamba",
|
||||||
|
"kn": "kanada",
|
||||||
|
"en_CA": "kanadski engleski",
|
||||||
|
"fr_CA": "kanadski francuski",
|
||||||
|
"kbl": "Kanembu",
|
||||||
|
"kr": "kanuri",
|
||||||
|
"kaa": "kara-kalpa\u0161ki",
|
||||||
|
"krc": "kara\u010daj-balkar",
|
||||||
|
"krl": "karelijski",
|
||||||
|
"car": "karipski",
|
||||||
|
"kha": "kasi",
|
||||||
|
"ks": "ka\u0161miri",
|
||||||
|
"csb": "ka\u0161ubijanski",
|
||||||
|
"ca": "katalonski",
|
||||||
|
"kaw": "kavi",
|
||||||
|
"kk": "kaza\u010dki",
|
||||||
|
"ken": "Kenyang",
|
||||||
|
"khw": "Khowar",
|
||||||
|
"quc": "ki\u010de",
|
||||||
|
"ki": "kikuju",
|
||||||
|
"kmb": "kimbundu",
|
||||||
|
"krj": "Kinaray-a",
|
||||||
|
"zh": "kineski",
|
||||||
|
"zh_Hans": "kineski (pojednostavljeni)",
|
||||||
|
"zh_Hant": "kineski (tradicionalni)",
|
||||||
|
"rw": "kinjarvanda",
|
||||||
|
"ky": "kirgiski",
|
||||||
|
"kiu": "Kirmanjki",
|
||||||
|
"nwc": "klasi\u010dni nevari",
|
||||||
|
"syc": "klasi\u010dni sirijski",
|
||||||
|
"tlh": "klingonski",
|
||||||
|
"km": "kmerski",
|
||||||
|
"ses": "kojraboro seni",
|
||||||
|
"bkm": "Kom",
|
||||||
|
"kv": "komi",
|
||||||
|
"koi": "komi-permja\u010dki",
|
||||||
|
"kg": "kongo",
|
||||||
|
"swc": "kongoanski swahili",
|
||||||
|
"kok": "konkani",
|
||||||
|
"cop": "koptski",
|
||||||
|
"ko": "korejski",
|
||||||
|
"kw": "korni\u0161ki",
|
||||||
|
"kfo": "koro",
|
||||||
|
"co": "korzikanski",
|
||||||
|
"xh": "kosa",
|
||||||
|
"kos": "kosreanski",
|
||||||
|
"kho": "kotanizijski",
|
||||||
|
"avk": "Kotava",
|
||||||
|
"khq": "koyra chiini",
|
||||||
|
"kpe": "kpele",
|
||||||
|
"cr": "kri",
|
||||||
|
"crh": "krimeanski turski",
|
||||||
|
"kri": "Krio",
|
||||||
|
"mus": "kri\u0161ki",
|
||||||
|
"kj": "kuanjama",
|
||||||
|
"kum": "kumik",
|
||||||
|
"ku": "kurdski",
|
||||||
|
"kru": "kurukh",
|
||||||
|
"kut": "kutenai",
|
||||||
|
"qu": "kven\u010da",
|
||||||
|
"nmg": "kwasio",
|
||||||
|
"lad": "ladino",
|
||||||
|
"lkt": "lakota",
|
||||||
|
"lam": "lamba",
|
||||||
|
"lah": "landa",
|
||||||
|
"lag": "langi",
|
||||||
|
"lo": "lao\u0161ki",
|
||||||
|
"ltg": "Latgalian",
|
||||||
|
"es_419": "latinoameri\u010dki \u0161panski",
|
||||||
|
"la": "latinski",
|
||||||
|
"lzz": "Laz",
|
||||||
|
"lv": "letonski",
|
||||||
|
"lez": "lezgian",
|
||||||
|
"lij": "Ligurian",
|
||||||
|
"li": "limburgi\u0161",
|
||||||
|
"ln": "lingala",
|
||||||
|
"lfn": "Lingua Franca Nova",
|
||||||
|
"lzh": "Literary Chinese",
|
||||||
|
"lt": "litvanski",
|
||||||
|
"liv": "Livonian",
|
||||||
|
"jbo": "lojban",
|
||||||
|
"lmo": "Lombard",
|
||||||
|
"sli": "Lower Silesian",
|
||||||
|
"loz": "lozi",
|
||||||
|
"lu": "luba-katanga",
|
||||||
|
"lua": "luba-lulua",
|
||||||
|
"lui": "luiseno",
|
||||||
|
"lb": "luksembur\u0161ki",
|
||||||
|
"smj": "lule sami",
|
||||||
|
"lun": "lunda",
|
||||||
|
"luo": "luo",
|
||||||
|
"lus": "lu\u0161ai",
|
||||||
|
"luy": "luyia",
|
||||||
|
"mde": "Maba",
|
||||||
|
"jmc": "machame",
|
||||||
|
"mad": "madure\u0161ki",
|
||||||
|
"hu": "ma\u0111arski",
|
||||||
|
"maf": "Mafa",
|
||||||
|
"mag": "magahi",
|
||||||
|
"moh": "mahavski",
|
||||||
|
"vmf": "Main-Franconian",
|
||||||
|
"mai": "maitili",
|
||||||
|
"mak": "makasar",
|
||||||
|
"mk": "makedonski",
|
||||||
|
"mgh": "makhuwa-meetto",
|
||||||
|
"kde": "makonde",
|
||||||
|
"mg": "malagazijski",
|
||||||
|
"ml": "malajalam",
|
||||||
|
"ms": "malajski",
|
||||||
|
"mt": "malte\u0161ki",
|
||||||
|
"mnc": "man\u010du",
|
||||||
|
"mdr": "mandar",
|
||||||
|
"man": "mandingo",
|
||||||
|
"mni": "manipuri",
|
||||||
|
"gv": "manks",
|
||||||
|
"mi": "maorski",
|
||||||
|
"mr": "marati",
|
||||||
|
"chm": "mari",
|
||||||
|
"tzm": "marokanski tamazigt",
|
||||||
|
"mh": "mar\u0161alski",
|
||||||
|
"mwr": "marvari",
|
||||||
|
"mas": "masai",
|
||||||
|
"mfe": "mauricijski kreolski",
|
||||||
|
"mzn": "Mazanderani",
|
||||||
|
"byv": "Medumba",
|
||||||
|
"es_MX": "meksi\u010dki \u0161panski",
|
||||||
|
"men": "mende",
|
||||||
|
"mwv": "Mentawai",
|
||||||
|
"mer": "meru",
|
||||||
|
"mgo": "meta\u2019",
|
||||||
|
"mic": "mikmak",
|
||||||
|
"nan": "Min Nan Chinese",
|
||||||
|
"min": "minangkabau",
|
||||||
|
"xmf": "Mingrelian",
|
||||||
|
"mwl": "mirande\u0161ki",
|
||||||
|
"ar_001": "moderni standardni arapski",
|
||||||
|
"mdf": "mok\u0161a",
|
||||||
|
"ro_MD": "moldavski",
|
||||||
|
"lol": "mongo",
|
||||||
|
"mn": "mongolski",
|
||||||
|
"ary": "Moroccan Arabic",
|
||||||
|
"mos": "mosi",
|
||||||
|
"mua": "mundang",
|
||||||
|
"ttt": "Muslim Tat",
|
||||||
|
"mye": "Myene",
|
||||||
|
"naq": "nama",
|
||||||
|
"na": "nauru",
|
||||||
|
"nv": "navaho",
|
||||||
|
"ng": "ndonga",
|
||||||
|
"nap": "neapolitanski",
|
||||||
|
"ne": "nepalski",
|
||||||
|
"und": "nepoznati ili neva\u017ee\u0107i jezik",
|
||||||
|
"new": "nevari",
|
||||||
|
"sba": "Ngambay",
|
||||||
|
"nnh": "Ngiemboon",
|
||||||
|
"jgo": "ngomba",
|
||||||
|
"yrl": "Nheengatu",
|
||||||
|
"nia": "nias",
|
||||||
|
"nds": "niski nema\u010dki",
|
||||||
|
"niu": "niuean",
|
||||||
|
"nqo": "nko",
|
||||||
|
"nog": "nogai",
|
||||||
|
"no": "norve\u0161ki",
|
||||||
|
"nb": "norve\u0161ki bokmal",
|
||||||
|
"nn": "norve\u0161ki njorsk",
|
||||||
|
"nov": "Novial",
|
||||||
|
"nus": "nuer",
|
||||||
|
"nzi": "nzima",
|
||||||
|
"nym": "njamvezi",
|
||||||
|
"nyn": "njankole",
|
||||||
|
"ny": "njanja",
|
||||||
|
"tog": "njasa tonga",
|
||||||
|
"de": "njema\u010dki",
|
||||||
|
"nyo": "njoro",
|
||||||
|
"oj": "ojibva",
|
||||||
|
"or": "orijski",
|
||||||
|
"om": "oromo",
|
||||||
|
"osa": "osage",
|
||||||
|
"os": "osetski",
|
||||||
|
"ota": "otomanski turski",
|
||||||
|
"pal": "pahlavi",
|
||||||
|
"pfl": "Palatine German",
|
||||||
|
"pau": "palauanski",
|
||||||
|
"pi": "pali",
|
||||||
|
"pam": "pampanga",
|
||||||
|
"pa": "pand\u017eabski",
|
||||||
|
"pag": "pangasinski",
|
||||||
|
"pap": "papiamento",
|
||||||
|
"ps": "pa\u0161tunski",
|
||||||
|
"pdc": "Pennsylvania German",
|
||||||
|
"fa": "perzijski",
|
||||||
|
"pcd": "Picard",
|
||||||
|
"pms": "Piedmontese",
|
||||||
|
"pdt": "Plautdietsch",
|
||||||
|
"pl": "poljski",
|
||||||
|
"pon": "ponpejski",
|
||||||
|
"pnt": "Pontic",
|
||||||
|
"pt": "portugalski",
|
||||||
|
"oc": "provansalski",
|
||||||
|
"prg": "Prussian",
|
||||||
|
"raj": "ra\u0111astani",
|
||||||
|
"rap": "rapanui",
|
||||||
|
"rar": "rarotongan",
|
||||||
|
"rm": "reto-romanski",
|
||||||
|
"rif": "Riffian",
|
||||||
|
"rgn": "Romagnol",
|
||||||
|
"rom": "romani",
|
||||||
|
"rof": "rombo",
|
||||||
|
"rtm": "Rotuman",
|
||||||
|
"rug": "Roviana",
|
||||||
|
"rwk": "rua",
|
||||||
|
"ro": "rumunski",
|
||||||
|
"root": "run",
|
||||||
|
"rn": "rundi",
|
||||||
|
"ru": "ruski",
|
||||||
|
"rue": "Rusyn",
|
||||||
|
"ssy": "Saho",
|
||||||
|
"sam": "samaritanski aramejski",
|
||||||
|
"saq": "samburu",
|
||||||
|
"sm": "samoanski",
|
||||||
|
"sgs": "Samogitian",
|
||||||
|
"sad": "sandave",
|
||||||
|
"sg": "sango",
|
||||||
|
"sbp": "sangu",
|
||||||
|
"sa": "sanskrit",
|
||||||
|
"sat": "santali",
|
||||||
|
"sc": "sardinijski",
|
||||||
|
"sas": "sasak",
|
||||||
|
"sdc": "Sassarese Sardinian",
|
||||||
|
"stq": "Saterland Frisian",
|
||||||
|
"saz": "Saurashtra",
|
||||||
|
"sly": "Selayar",
|
||||||
|
"sel": "selkap",
|
||||||
|
"seh": "sena",
|
||||||
|
"see": "Seneca",
|
||||||
|
"srr": "serer",
|
||||||
|
"sei": "Seri",
|
||||||
|
"st": "sesoto",
|
||||||
|
"nso": "severni soto",
|
||||||
|
"frr": "severno-frizijski",
|
||||||
|
"ksb": "shambala",
|
||||||
|
"scn": "sicilijanski",
|
||||||
|
"ii": "si\u010duan ji",
|
||||||
|
"sid": "sidamo",
|
||||||
|
"bla": "siksika",
|
||||||
|
"szl": "Silesian",
|
||||||
|
"sd": "sindi",
|
||||||
|
"si": "singaleski",
|
||||||
|
"syr": "sirijski",
|
||||||
|
"nd": "sjeverni ndebele",
|
||||||
|
"se": "sjeverni sami",
|
||||||
|
"sms": "skoltski jezik",
|
||||||
|
"den": "slavski",
|
||||||
|
"sk": "slova\u010dki",
|
||||||
|
"sl": "slovena\u010dki",
|
||||||
|
"sog": "sod\u017eijenski",
|
||||||
|
"xog": "soga",
|
||||||
|
"so": "somalski",
|
||||||
|
"snk": "soninke",
|
||||||
|
"ckb": "soranski kurdski",
|
||||||
|
"azb": "South Azerbaijani",
|
||||||
|
"srn": "srananski tongo",
|
||||||
|
"enm": "srednji engleski",
|
||||||
|
"frm": "srednji francuski",
|
||||||
|
"dum": "srednji holandski",
|
||||||
|
"mga": "srednji irski",
|
||||||
|
"gmh": "srednji visoki nema\u010dki",
|
||||||
|
"sr": "srpski",
|
||||||
|
"sh": "srpskohrvatski",
|
||||||
|
"zgh": "standardni marokanski tamazigt",
|
||||||
|
"non": "stari norski",
|
||||||
|
"egy": "staroegipatski",
|
||||||
|
"ang": "staroengleski",
|
||||||
|
"fro": "starofrancuski",
|
||||||
|
"grc": "starogr\u010dki",
|
||||||
|
"sga": "staroirski",
|
||||||
|
"goh": "staronema\u010dki",
|
||||||
|
"peo": "staropersijski",
|
||||||
|
"pro": "staroprovansalski",
|
||||||
|
"cu": "staroslovenski",
|
||||||
|
"su": "sudanski",
|
||||||
|
"suk": "sukuma",
|
||||||
|
"sux": "sumerski",
|
||||||
|
"sus": "susu",
|
||||||
|
"sw": "svahili",
|
||||||
|
"ss": "svati",
|
||||||
|
"shn": "\u0161an",
|
||||||
|
"sco": "\u0161kotski",
|
||||||
|
"gd": "\u0161kotski galski",
|
||||||
|
"sn": "\u0161ona",
|
||||||
|
"es": "\u0161panski",
|
||||||
|
"fr_CH": "\u0161vajcarski francuski",
|
||||||
|
"gsw": "\u0161vajcarski njema\u010dki",
|
||||||
|
"sv": "\u0161vedski",
|
||||||
|
"tg": "tad\u017ei\u010dki",
|
||||||
|
"tl": "tagalski",
|
||||||
|
"shi": "tahelhit",
|
||||||
|
"ty": "tahi\u0107anski",
|
||||||
|
"dav": "taita",
|
||||||
|
"th": "tajlandski",
|
||||||
|
"tly": "Talysh",
|
||||||
|
"tmh": "tama\u0161ek",
|
||||||
|
"ta": "tamilski",
|
||||||
|
"trv": "Taroko",
|
||||||
|
"twq": "tasavak",
|
||||||
|
"tt": "tatarski",
|
||||||
|
"te": "telugu",
|
||||||
|
"ter": "tereno",
|
||||||
|
"teo": "teso",
|
||||||
|
"tet": "tetum",
|
||||||
|
"bo": "tibetanski",
|
||||||
|
"tig": "tigre",
|
||||||
|
"ti": "tigrinja",
|
||||||
|
"tem": "timne",
|
||||||
|
"tiv": "tiv",
|
||||||
|
"kcg": "tjap",
|
||||||
|
"tli": "tlingit",
|
||||||
|
"tpi": "tok pisin",
|
||||||
|
"tkl": "tokelau",
|
||||||
|
"to": "tonga",
|
||||||
|
"fit": "Tornedalen Finnish",
|
||||||
|
"tkr": "Tsakhur",
|
||||||
|
"tsd": "Tsakonian",
|
||||||
|
"tsi": "tsim\u0161ian",
|
||||||
|
"ts": "tsonga",
|
||||||
|
"tn": "tsvana",
|
||||||
|
"tcy": "Tulu",
|
||||||
|
"tum": "tumbuka",
|
||||||
|
"aeb": "Tunisian Arabic",
|
||||||
|
"tk": "turkmenski",
|
||||||
|
"tru": "Turoyo",
|
||||||
|
"tr": "turski",
|
||||||
|
"tvl": "tuvalu",
|
||||||
|
"tyv": "tuvinijski",
|
||||||
|
"tw": "tvi",
|
||||||
|
"udm": "udmurt",
|
||||||
|
"uga": "ugaritski",
|
||||||
|
"ug": "ujgurski",
|
||||||
|
"uk": "ukrajinski",
|
||||||
|
"umb": "umbundu",
|
||||||
|
"ur": "urdu",
|
||||||
|
"uz": "uzbe\u010dki",
|
||||||
|
"vai": "vai",
|
||||||
|
"wal": "valamo",
|
||||||
|
"wa": "valun",
|
||||||
|
"war": "varej",
|
||||||
|
"was": "va\u0161o",
|
||||||
|
"ve": "venda",
|
||||||
|
"vec": "Venetian",
|
||||||
|
"vep": "Veps",
|
||||||
|
"vi": "vijetnamski",
|
||||||
|
"mul": "vi\u0161e jezika",
|
||||||
|
"vo": "volap\u00fck",
|
||||||
|
"wo": "volof",
|
||||||
|
"vro": "V\u00f5ro",
|
||||||
|
"vot": "votski",
|
||||||
|
"vun": "vunjo",
|
||||||
|
"wae": "Walser",
|
||||||
|
"wbp": "Warlpiri",
|
||||||
|
"guc": "Wayuu",
|
||||||
|
"vls": "West Flemish",
|
||||||
|
"mrj": "Western Mari",
|
||||||
|
"wuu": "Wu Chinese",
|
||||||
|
"hsn": "Xiang Chinese",
|
||||||
|
"yav": "Yangben",
|
||||||
|
"ybb": "Yemba",
|
||||||
|
"zap": "zapote\u010dki",
|
||||||
|
"dje": "zarma",
|
||||||
|
"zza": "zaza",
|
||||||
|
"zea": "Zeelandic",
|
||||||
|
"kea": "zelenortski",
|
||||||
|
"zen": "zenaga",
|
||||||
|
"gbz": "Zoroastrian Dari",
|
||||||
|
"za": "zuang",
|
||||||
|
"zu": "zulu",
|
||||||
|
"zun": "zuni",
|
||||||
|
"kaj": "\u017eju"
|
||||||
|
}
|
||||||
1676
public/intl/messages/bs-BA.json
Normal file
1676
public/intl/messages/bs-BA.json
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -38,7 +38,7 @@
|
||||||
"label.add-step": [
|
"label.add-step": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Add step"
|
"value": "添加步骤"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.add-website": [
|
"label.add-website": [
|
||||||
|
|
@ -564,7 +564,7 @@
|
||||||
"label.last-months": [
|
"label.last-months": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Last "
|
"value": "最近 "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": 1,
|
"type": 1,
|
||||||
|
|
@ -572,7 +572,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": " months"
|
"value": " 个月"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.leave": [
|
"label.leave": [
|
||||||
|
|
@ -996,7 +996,7 @@
|
||||||
"label.steps": [
|
"label.steps": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Steps"
|
"value": "步骤"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.sum": [
|
"label.sum": [
|
||||||
|
|
@ -1176,7 +1176,7 @@
|
||||||
"label.update": [
|
"label.update": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Update"
|
"value": "更新"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.url": [
|
"label.url": [
|
||||||
|
|
@ -1218,7 +1218,7 @@
|
||||||
"label.utm-description": [
|
"label.utm-description": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Track your campaigns through UTM parameters."
|
"value": "通过UTM参数追踪您的广告活动。"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.value": [
|
"label.value": [
|
||||||
|
|
@ -1254,7 +1254,7 @@
|
||||||
"label.views-per-visit": [
|
"label.views-per-visit": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Views per visit"
|
"value": "每次访问的浏览量"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.visitors": [
|
"label.visitors": [
|
||||||
|
|
@ -1266,7 +1266,7 @@
|
||||||
"label.visits": [
|
"label.visits": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Visits"
|
"value": "访问次数"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.website": [
|
"label.website": [
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,9 @@ if (!process.env.SKIP_DB_CHECK && !process.env.DATABASE_TYPE) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.CLICKHOUSE_URL) {
|
if (process.env.CLICKHOUSE_URL) {
|
||||||
checkMissing(['CA_CERT', 'CLIENT_CERT', 'CLIENT_KEY', 'KAFKA_BROKER', 'KAFKA_URL', 'REDIS_URL']);
|
checkMissing(['KAFKA_BROKER', 'KAFKA_URL', 'REDIS_URL']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.CLOUD_MODE) {
|
if (process.env.CLOUD_MODE) {
|
||||||
checkMissing(['CLOUD_URL']);
|
checkMissing(['CLOUD_URL']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.ENABLE_BLOCKER) {
|
|
||||||
checkMissing(['REDIS_URL']);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,9 @@ export function TeamMemberEditForm({
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderValue = (value: string) => {
|
const renderValue = (value: string) => {
|
||||||
|
if (value === ROLES.teamManager) {
|
||||||
|
return formatMessage(labels.manager);
|
||||||
|
}
|
||||||
if (value === ROLES.teamMember) {
|
if (value === ROLES.teamMember) {
|
||||||
return formatMessage(labels.member);
|
return formatMessage(labels.member);
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +61,7 @@ export function TeamMemberEditForm({
|
||||||
minWidth: '250px',
|
minWidth: '250px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Item key={ROLES.teamManager}>{formatMessage(labels.manager)}</Item>
|
||||||
<Item key={ROLES.teamMember}>{formatMessage(labels.member)}</Item>
|
<Item key={ROLES.teamMember}>{formatMessage(labels.member)}</Item>
|
||||||
<Item key={ROLES.teamViewOnly}>{formatMessage(labels.viewOnly)}</Item>
|
<Item key={ROLES.teamViewOnly}>{formatMessage(labels.viewOnly)}</Item>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,10 @@ export function TeamMembersPage({ teamId }: { teamId: string }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const canEdit =
|
const canEdit =
|
||||||
team?.teamUser?.find(({ userId, role }) => role === ROLES.teamOwner && userId === user.id) &&
|
team?.teamUser?.find(
|
||||||
user.role !== ROLES.viewOnly;
|
({ userId, role }) =>
|
||||||
|
(role === ROLES.teamOwner || role === ROLES.teamManager) && userId === user.id,
|
||||||
|
) && user.role !== ROLES.viewOnly;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export function TeamMembersTable({
|
||||||
|
|
||||||
const roles = {
|
const roles = {
|
||||||
[ROLES.teamOwner]: formatMessage(labels.teamOwner),
|
[ROLES.teamOwner]: formatMessage(labels.teamOwner),
|
||||||
|
[ROLES.teamManager]: formatMessage(labels.teamManager),
|
||||||
[ROLES.teamMember]: formatMessage(labels.teamMember),
|
[ROLES.teamMember]: formatMessage(labels.teamMember),
|
||||||
[ROLES.teamViewOnly]: formatMessage(labels.viewOnly),
|
[ROLES.teamViewOnly]: formatMessage(labels.viewOnly),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,24 @@ export function TeamDetails({ teamId }: { teamId: string }) {
|
||||||
const { user } = useLogin();
|
const { user } = useLogin();
|
||||||
const [tab, setTab] = useState('details');
|
const [tab, setTab] = useState('details');
|
||||||
|
|
||||||
const canEdit =
|
const isTeamOwner =
|
||||||
!!team?.teamUser?.find(({ userId, role }) => role === ROLES.teamOwner && userId === user.id) &&
|
!!team?.teamUser?.find(({ userId, role }) => role === ROLES.teamOwner && userId === user.id) &&
|
||||||
user.role !== ROLES.viewOnly;
|
user.role !== ROLES.viewOnly;
|
||||||
|
|
||||||
|
const canEdit =
|
||||||
|
!!team?.teamUser?.find(
|
||||||
|
({ userId, role }) =>
|
||||||
|
(role === ROLES.teamOwner || role === ROLES.teamManager) && userId === user.id,
|
||||||
|
) && user.role !== ROLES.viewOnly;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flexbox direction="column">
|
<Flexbox direction="column">
|
||||||
<PageHeader title={team?.name} icon={<Icons.Users />}>
|
<PageHeader title={team?.name} icon={<Icons.Users />}>
|
||||||
{!canEdit && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
{!isTeamOwner && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Tabs selectedKey={tab} onSelect={(value: any) => setTab(value)} style={{ marginBottom: 30 }}>
|
<Tabs selectedKey={tab} onSelect={(value: any) => setTab(value)} style={{ marginBottom: 30 }}>
|
||||||
<Item key="details">{formatMessage(labels.details)}</Item>
|
<Item key="details">{formatMessage(labels.details)}</Item>
|
||||||
{canEdit && <Item key="manage">{formatMessage(labels.manage)}</Item>}
|
{isTeamOwner && <Item key="manage">{formatMessage(labels.manage)}</Item>}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{tab === 'details' && <TeamEditForm teamId={teamId} allowEdit={canEdit} />}
|
{tab === 'details' && <TeamEditForm teamId={teamId} allowEdit={canEdit} />}
|
||||||
{tab === 'manage' && <TeamManage teamId={teamId} />}
|
{tab === 'manage' && <TeamManage teamId={teamId} />}
|
||||||
|
|
|
||||||
3
src/app/(main)/teams/[teamId]/reports/utm/page.tsx
Normal file
3
src/app/(main)/teams/[teamId]/reports/utm/page.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Page from 'app/(main)/reports/utm/page';
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Page from 'app/(main)/websites/[websiteId]/event-data/page';
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Page from 'app/(main)/websites/[websiteId]/realtime/page';
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Page from 'app/(main)/websites/[websiteId]/reports/page';
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Favicon from 'components/common/Favicon';
|
import Favicon from 'components/common/Favicon';
|
||||||
import { useMessages, useWebsite } from 'components/hooks';
|
import { useMessages, useTeamUrl, useWebsite } from 'components/hooks';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import ActiveUsers from 'components/metrics/ActiveUsers';
|
import ActiveUsers from 'components/metrics/ActiveUsers';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
@ -19,6 +19,7 @@ export function WebsiteHeader({
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { renderTeamUrl } = useTeamUrl();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { data: website } = useWebsite(websiteId);
|
const { data: website } = useWebsite(websiteId);
|
||||||
const { name, domain } = website || {};
|
const { name, domain } = website || {};
|
||||||
|
|
@ -62,7 +63,11 @@ export function WebsiteHeader({
|
||||||
: pathname.match(/^\/websites\/[\w-]+$/);
|
: pathname.match(/^\/websites\/[\w-]+$/);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link key={label} href={`/websites/${websiteId}${path}`} shallow={true}>
|
<Link
|
||||||
|
key={label}
|
||||||
|
href={renderTeamUrl(`/websites/${websiteId}${path}`)}
|
||||||
|
shallow={true}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="quiet"
|
variant="quiet"
|
||||||
className={classNames({
|
className={classNames({
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
'use client';
|
'use client';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Button, Flexbox, Icon, Icons, Text } from 'react-basics';
|
import { Button, Flexbox, Icon, Icons, Text } from 'react-basics';
|
||||||
import { useMessages } from 'components/hooks';
|
import { useMessages, useTeamUrl } from 'components/hooks';
|
||||||
import WebsiteHeader from '../WebsiteHeader';
|
import WebsiteHeader from '../WebsiteHeader';
|
||||||
import ReportsDataTable from 'app/(main)/reports/ReportsDataTable';
|
import ReportsDataTable from 'app/(main)/reports/ReportsDataTable';
|
||||||
|
|
||||||
export function WebsiteReportsPage({ websiteId }) {
|
export function WebsiteReportsPage({ websiteId }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { renderTeamUrl } = useTeamUrl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WebsiteHeader websiteId={websiteId} />
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
<Flexbox alignItems="center" justifyContent="end">
|
<Flexbox alignItems="center" justifyContent="end">
|
||||||
<Link href={`/reports/create`}>
|
<Link href={renderTeamUrl('/reports/create')}>
|
||||||
<Button variant="primary">
|
<Button variant="primary">
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Plus />
|
<Icons.Plus />
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { UseQueryOptions } from '@tanstack/react-query';
|
import { UseQueryOptions } from '@tanstack/react-query';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useApi } from './useApi';
|
import { useApi } from './useApi';
|
||||||
import { FilterResult, SearchFilter, FilterQueryResult } from 'lib/types';
|
import { PageResult, PageParams, FilterQueryResult } from 'lib/types';
|
||||||
|
|
||||||
export function useFilterQuery<T = any>({
|
export function useFilterQuery<T = any>({
|
||||||
queryKey,
|
queryKey,
|
||||||
queryFn,
|
queryFn,
|
||||||
...options
|
...options
|
||||||
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): FilterQueryResult<T> {
|
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): FilterQueryResult<T> {
|
||||||
const [params, setParams] = useState<T | SearchFilter>({
|
const [params, setParams] = useState<T | PageParams>({
|
||||||
query: '',
|
query: '',
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
|
|
@ -21,7 +21,7 @@ export function useFilterQuery<T = any>({
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: data as FilterResult<any>,
|
result: data as PageResult<any>,
|
||||||
query,
|
query,
|
||||||
params,
|
params,
|
||||||
setParams,
|
setParams,
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,17 @@
|
||||||
import useApi from './useApi';
|
import useApi from './useApi';
|
||||||
|
import { useFilterParams } from '../useFilterParams';
|
||||||
import { UseQueryOptions } from '@tanstack/react-query';
|
import { UseQueryOptions } from '@tanstack/react-query';
|
||||||
import { useDateRange, useNavigation, useTimezone } from 'components/hooks';
|
|
||||||
import { zonedTimeToUtc } from 'date-fns-tz';
|
|
||||||
|
|
||||||
export function useWebsiteEvents(
|
export function useWebsiteEvents(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const [dateRange] = useDateRange(websiteId);
|
const params = useFilterParams(websiteId);
|
||||||
const { startDate, endDate, unit, offset } = dateRange;
|
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const {
|
|
||||||
query: { url, event },
|
|
||||||
} = useNavigation();
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
startAt: +zonedTimeToUtc(startDate, timezone),
|
|
||||||
endAt: +zonedTimeToUtc(endDate, timezone),
|
|
||||||
unit,
|
|
||||||
offset,
|
|
||||||
timezone,
|
|
||||||
url,
|
|
||||||
event,
|
|
||||||
};
|
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['events', { ...params }],
|
queryKey: ['websites:events', { websiteId, ...params }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/events`, { ...params }),
|
queryFn: () => get(`/websites/${websiteId}/events`, params),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import useApi from './useApi';
|
|
||||||
import { UseQueryOptions } from '@tanstack/react-query';
|
import { UseQueryOptions } from '@tanstack/react-query';
|
||||||
|
import useApi from './useApi';
|
||||||
|
import { useFilterParams } from '../useFilterParams';
|
||||||
|
|
||||||
export function useWebsiteMetrics(
|
export function useWebsiteMetrics(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
params?: { [key: string]: any },
|
type: string,
|
||||||
|
limit: number,
|
||||||
options?: Omit<UseQueryOptions & { onDataLoad?: (data: any) => void }, 'queryKey' | 'queryFn'>,
|
options?: Omit<UseQueryOptions & { onDataLoad?: (data: any) => void }, 'queryKey' | 'queryFn'>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
|
const params = useFilterParams(websiteId);
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
|
|
@ -14,21 +17,26 @@ export function useWebsiteMetrics(
|
||||||
{
|
{
|
||||||
websiteId,
|
websiteId,
|
||||||
...params,
|
...params,
|
||||||
|
type,
|
||||||
|
limit,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const filters = { ...params };
|
const filters = { ...params };
|
||||||
|
|
||||||
filters[params.type] = undefined;
|
filters[type] = undefined;
|
||||||
|
|
||||||
const data = await get(`/websites/${websiteId}/metrics`, {
|
const data = await get(`/websites/${websiteId}/metrics`, {
|
||||||
...filters,
|
...filters,
|
||||||
|
type,
|
||||||
|
limit,
|
||||||
});
|
});
|
||||||
|
|
||||||
options?.onDataLoad?.(data);
|
options?.onDataLoad?.(data);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,18 @@
|
||||||
import { zonedTimeToUtc } from 'date-fns-tz';
|
import { UseQueryOptions } from '@tanstack/react-query';
|
||||||
import { useApi, useDateRange, useNavigation, useTimezone } from 'components/hooks';
|
import { useApi } from './useApi';
|
||||||
|
import { useFilterParams } from '..//useFilterParams';
|
||||||
|
|
||||||
export function useWebsitePageviews(websiteId: string, options?: { [key: string]: string }) {
|
export function useWebsitePageviews(
|
||||||
|
websiteId: string,
|
||||||
|
options?: Omit<UseQueryOptions, 'queryKey' | 'queryFn'>,
|
||||||
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const [dateRange] = useDateRange(websiteId);
|
const params = useFilterParams(websiteId);
|
||||||
const { startDate, endDate, unit } = dateRange;
|
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const {
|
|
||||||
query: { url, referrer, host, os, browser, device, country, region, city, title },
|
|
||||||
} = useNavigation();
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
startAt: +zonedTimeToUtc(startDate, timezone),
|
|
||||||
endAt: +zonedTimeToUtc(endDate, timezone),
|
|
||||||
unit,
|
|
||||||
timezone,
|
|
||||||
url,
|
|
||||||
referrer,
|
|
||||||
host,
|
|
||||||
os,
|
|
||||||
browser,
|
|
||||||
device,
|
|
||||||
country,
|
|
||||||
region,
|
|
||||||
city,
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['websites:pageviews', { websiteId, ...params }],
|
queryKey: ['websites:pageviews', { websiteId, ...params }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/pageviews`, params),
|
queryFn: () => get(`/websites/${websiteId}/pageviews`, params),
|
||||||
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,14 @@
|
||||||
import { useApi, useDateRange, useNavigation } from 'components/hooks';
|
import { useApi } from './useApi';
|
||||||
|
import { useFilterParams } from '../useFilterParams';
|
||||||
|
|
||||||
export function useWebsiteStats(websiteId: string, options?: { [key: string]: string }) {
|
export function useWebsiteStats(websiteId: string, options?: { [key: string]: string }) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const [dateRange] = useDateRange(websiteId);
|
const params = useFilterParams(websiteId);
|
||||||
const { startDate, endDate } = dateRange;
|
|
||||||
const {
|
|
||||||
query: { url, referrer, host, title, os, browser, device, country, region, city },
|
|
||||||
} = useNavigation();
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
startAt: +startDate,
|
|
||||||
endAt: +endDate,
|
|
||||||
url,
|
|
||||||
referrer,
|
|
||||||
host,
|
|
||||||
title,
|
|
||||||
os,
|
|
||||||
browser,
|
|
||||||
device,
|
|
||||||
country,
|
|
||||||
region,
|
|
||||||
city,
|
|
||||||
};
|
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['websites:stats', { websiteId, ...params }],
|
queryKey: ['websites:stats', { websiteId, ...params }],
|
||||||
queryFn: () => get(`/websites/${websiteId}/stats`, params),
|
queryFn: () => get(`/websites/${websiteId}/stats`, params),
|
||||||
|
enabled: !!websiteId,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
src/components/hooks/useFilterParams.ts
Normal file
32
src/components/hooks/useFilterParams.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { useNavigation } from './useNavigation';
|
||||||
|
import { useDateRange } from './useDateRange';
|
||||||
|
import { useTimezone } from './useTimezone';
|
||||||
|
import { zonedTimeToUtc } from 'date-fns-tz';
|
||||||
|
|
||||||
|
export function useFilterParams(websiteId: string) {
|
||||||
|
const [dateRange] = useDateRange(websiteId);
|
||||||
|
const { startDate, endDate, unit, offset } = dateRange;
|
||||||
|
const { timezone } = useTimezone();
|
||||||
|
const {
|
||||||
|
query: { url, referrer, title, query, os, browser, device, country, region, city, event },
|
||||||
|
} = useNavigation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
startAt: +zonedTimeToUtc(startDate, timezone),
|
||||||
|
endAt: +zonedTimeToUtc(endDate, timezone),
|
||||||
|
unit,
|
||||||
|
offset,
|
||||||
|
timezone,
|
||||||
|
url,
|
||||||
|
referrer,
|
||||||
|
title,
|
||||||
|
query,
|
||||||
|
os,
|
||||||
|
browser,
|
||||||
|
device,
|
||||||
|
country,
|
||||||
|
region,
|
||||||
|
city,
|
||||||
|
event,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ export const labels = defineMessages({
|
||||||
createdBy: { id: 'label.created-by', defaultMessage: 'Created By' },
|
createdBy: { id: 'label.created-by', defaultMessage: 'Created By' },
|
||||||
edit: { id: 'label.edit', defaultMessage: 'Edit' },
|
edit: { id: 'label.edit', defaultMessage: 'Edit' },
|
||||||
name: { id: 'label.name', defaultMessage: 'Name' },
|
name: { id: 'label.name', defaultMessage: 'Name' },
|
||||||
|
manager: { id: 'label.manager', defaultMessage: 'Manager' },
|
||||||
member: { id: 'label.member', defaultMessage: 'Member' },
|
member: { id: 'label.member', defaultMessage: 'Member' },
|
||||||
members: { id: 'label.members', defaultMessage: 'Members' },
|
members: { id: 'label.members', defaultMessage: 'Members' },
|
||||||
accessCode: { id: 'label.access-code', defaultMessage: 'Access code' },
|
accessCode: { id: 'label.access-code', defaultMessage: 'Access code' },
|
||||||
|
|
@ -43,6 +44,7 @@ export const labels = defineMessages({
|
||||||
settings: { id: 'label.settings', defaultMessage: 'Settings' },
|
settings: { id: 'label.settings', defaultMessage: 'Settings' },
|
||||||
owner: { id: 'label.owner', defaultMessage: 'Owner' },
|
owner: { id: 'label.owner', defaultMessage: 'Owner' },
|
||||||
teamOwner: { id: 'label.team-owner', defaultMessage: 'Team owner' },
|
teamOwner: { id: 'label.team-owner', defaultMessage: 'Team owner' },
|
||||||
|
teamManager: { id: 'label.team-manager', defaultMessage: 'Team manager' },
|
||||||
teamMember: { id: 'label.team-member', defaultMessage: 'Team member' },
|
teamMember: { id: 'label.team-member', defaultMessage: 'Team member' },
|
||||||
teamViewOnly: { id: 'label.team-view-only', defaultMessage: 'Team view only' },
|
teamViewOnly: { id: 'label.team-view-only', defaultMessage: 'Team view only' },
|
||||||
enableShareUrl: { id: 'label.enable-share-url', defaultMessage: 'Enable share URL' },
|
enableShareUrl: { id: 'label.enable-share-url', defaultMessage: 'Enable share URL' },
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import LinkButton from 'components/common/LinkButton';
|
||||||
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||||
import { percentFilter } from 'lib/filters';
|
import { percentFilter } from 'lib/filters';
|
||||||
import {
|
import {
|
||||||
useDateRange,
|
|
||||||
useNavigation,
|
useNavigation,
|
||||||
useWebsiteMetrics,
|
useWebsiteMetrics,
|
||||||
useMessages,
|
useMessages,
|
||||||
|
|
@ -45,35 +44,14 @@ export function MetricsTable({
|
||||||
}: MetricsTableProps) {
|
}: MetricsTableProps) {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
const [{ startDate, endDate }] = useDateRange(websiteId);
|
const { renderUrl } = useNavigation();
|
||||||
const {
|
|
||||||
renderUrl,
|
|
||||||
query: { url, referrer, host, title, os, browser, device, country, region, city },
|
|
||||||
} = useNavigation();
|
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { dir } = useLocale();
|
const { dir } = useLocale();
|
||||||
|
|
||||||
const { data, isLoading, isFetched, error } = useWebsiteMetrics(
|
const { data, isLoading, isFetched, error } = useWebsiteMetrics(websiteId, type, limit, {
|
||||||
websiteId,
|
retryDelay: delay || DEFAULT_ANIMATION_DURATION,
|
||||||
{
|
onDataLoad,
|
||||||
type,
|
});
|
||||||
startAt: +startDate,
|
|
||||||
endAt: +endDate,
|
|
||||||
url,
|
|
||||||
referrer,
|
|
||||||
host,
|
|
||||||
os,
|
|
||||||
title,
|
|
||||||
browser,
|
|
||||||
device,
|
|
||||||
country,
|
|
||||||
region,
|
|
||||||
city,
|
|
||||||
limit,
|
|
||||||
search,
|
|
||||||
},
|
|
||||||
{ retryDelay: delay || DEFAULT_ANIMATION_DURATION, onDataLoad },
|
|
||||||
);
|
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,11 @@ export function PagesTable({ allowFilter, domainName, ...props }: PagesTableProp
|
||||||
id={view}
|
id={view}
|
||||||
value={x}
|
value={x}
|
||||||
label={!x && formatMessage(labels.none)}
|
label={!x && formatMessage(labels.none)}
|
||||||
externalUrl={`${domainName.startsWith('http') ? domainName : `https://${domainName}`}${x}`}
|
externalUrl={
|
||||||
|
view === 'url'
|
||||||
|
? `${domainName.startsWith('http') ? domainName : `https://${domainName}`}${x}`
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
246
src/lang/bs-BA.json
Normal file
246
src/lang/bs-BA.json
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
{
|
||||||
|
"label.access-code": "Pristupni kod",
|
||||||
|
"label.actions": "Akcije",
|
||||||
|
"label.activity-log": "Log aktivnosti",
|
||||||
|
"label.add": "Dodaj",
|
||||||
|
"label.add-description": "Dodaj opis",
|
||||||
|
"label.add-member": "Dodaj člana",
|
||||||
|
"label.add-step": "Dodaj korak",
|
||||||
|
"label.add-website": "Dodaj web stranicu",
|
||||||
|
"label.admin": "Administrator",
|
||||||
|
"label.after": "Nakon",
|
||||||
|
"label.all": "Sve",
|
||||||
|
"label.all-time": "Cijelo vrijeme",
|
||||||
|
"label.analytics": "Analitike",
|
||||||
|
"label.average": "Prosjek",
|
||||||
|
"label.average-visit-time": "Prosječno vrijeme posjete",
|
||||||
|
"label.back": "Nazad",
|
||||||
|
"label.before": "Prije",
|
||||||
|
"label.bounce-rate": "Bounce rate",
|
||||||
|
"label.breakdown": "Pregled po kategorijama",
|
||||||
|
"label.browser": "Browser",
|
||||||
|
"label.browsers": "Browseri",
|
||||||
|
"label.cancel": "Otkaži",
|
||||||
|
"label.change-password": "Promijeni šifru",
|
||||||
|
"label.cities": "Gradovi",
|
||||||
|
"label.city": "Grad",
|
||||||
|
"label.clear-all": "Očisti sve",
|
||||||
|
"label.confirm": "Potvrdi",
|
||||||
|
"label.confirm-password": "Potvrdi šifru",
|
||||||
|
"label.contains": "Sadrži",
|
||||||
|
"label.continue": "Nastavi",
|
||||||
|
"label.countries": "Zemlje",
|
||||||
|
"label.country": "Zemlja",
|
||||||
|
"label.create": "Kreiraj",
|
||||||
|
"label.create-report": "Kreiraj izvještaj",
|
||||||
|
"label.create-team": "Kreiraj tim",
|
||||||
|
"label.create-user": "Kreiraj korisnika",
|
||||||
|
"label.created": "Kreiraj",
|
||||||
|
"label.created-by": "Kreirao",
|
||||||
|
"label.current-password": "Trenutna šifra",
|
||||||
|
"label.custom-range": "Proizvoljni raspon",
|
||||||
|
"label.dashboard": "Dashboard",
|
||||||
|
"label.data": "Podaci",
|
||||||
|
"label.date": "Datum",
|
||||||
|
"label.date-range": "Datumski raspon",
|
||||||
|
"label.day": "Dan",
|
||||||
|
"label.default-date-range": "Defaultni datumski raspon",
|
||||||
|
"label.delete": "Izbriši",
|
||||||
|
"label.delete-report": "Izbriši report",
|
||||||
|
"label.delete-team": "Izbriši tim",
|
||||||
|
"label.delete-user": "Izbriši korisnika",
|
||||||
|
"label.delete-website": "Izbriši web stranicu",
|
||||||
|
"label.description": "Opis",
|
||||||
|
"label.desktop": "Desktop",
|
||||||
|
"label.details": "Detalji",
|
||||||
|
"label.device": "Uređaj",
|
||||||
|
"label.devices": "Uređaji",
|
||||||
|
"label.dismiss": "Odbaci",
|
||||||
|
"label.does-not-contain": "Ne sadrži",
|
||||||
|
"label.domain": "Domena",
|
||||||
|
"label.dropoff": "Dropoff",
|
||||||
|
"label.edit": "Uredi",
|
||||||
|
"label.edit-dashboard": "Uredi dashboard",
|
||||||
|
"label.edit-member": "Uredi člana",
|
||||||
|
"label.enable-share-url": "Omogući URL za dijeljenje",
|
||||||
|
"label.event": "Događaj",
|
||||||
|
"label.event-data": "Podaci o događaju",
|
||||||
|
"label.events": "Događaji",
|
||||||
|
"label.false": "Ne",
|
||||||
|
"label.field": "Polje",
|
||||||
|
"label.fields": "Polja",
|
||||||
|
"label.filter": "Filter",
|
||||||
|
"label.filter-combined": "Kombinovano",
|
||||||
|
"label.filter-raw": "Sirovo",
|
||||||
|
"label.filters": "Filtri",
|
||||||
|
"label.funnel": "Lijevak",
|
||||||
|
"label.funnel-description": "Razumite koverziju i drop-off učestalost korisnika.",
|
||||||
|
"label.greater-than": "Veće od",
|
||||||
|
"label.greater-than-equals": "Veće od ili jednako",
|
||||||
|
"label.insights": "Uvidi",
|
||||||
|
"label.insights-description": "Zaronite dublje u vaše podatke korištenjem segmenata i filtera",
|
||||||
|
"label.is": "Jeste",
|
||||||
|
"label.is-not": "Nije",
|
||||||
|
"label.is-not-set": "Nije setano",
|
||||||
|
"label.is-set": "Jeste setano",
|
||||||
|
"label.join": "Učlani se",
|
||||||
|
"label.join-team": "Učlani se u tim",
|
||||||
|
"label.language": "Jezik",
|
||||||
|
"label.languages": "Jezici",
|
||||||
|
"label.laptop": "Laptop",
|
||||||
|
"label.last-days": "Zadnjih {x} dana",
|
||||||
|
"label.last-hours": "Zadnjih {x} sati",
|
||||||
|
"label.last-months": "Zadnjih {x} mjeseci",
|
||||||
|
"label.leave": "Napusti",
|
||||||
|
"label.leave-team": "Napusti tim",
|
||||||
|
"label.less-than": "Manje od",
|
||||||
|
"label.less-than-equals": "Manje od ili jednako",
|
||||||
|
"label.login": "Login",
|
||||||
|
"label.logout": "Logout",
|
||||||
|
"label.manage": "Manage",
|
||||||
|
"label.max": "Max",
|
||||||
|
"label.member": "Član",
|
||||||
|
"label.members": "Članovi",
|
||||||
|
"label.min": "Min",
|
||||||
|
"label.mobile": "Mobile",
|
||||||
|
"label.more": "Više",
|
||||||
|
"label.my-account": "Moj račun",
|
||||||
|
"label.my-websites": "Moje web stranice",
|
||||||
|
"label.name": "Ime",
|
||||||
|
"label.new-password": "Nova šifra",
|
||||||
|
"label.none": "None",
|
||||||
|
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
||||||
|
"label.ok": "OK",
|
||||||
|
"label.os": "OS",
|
||||||
|
"label.overview": "Pregled",
|
||||||
|
"label.owner": "Vlasnik",
|
||||||
|
"label.page-of": "Strana {current} od {total}",
|
||||||
|
"label.page-views": "Pregleda stranica",
|
||||||
|
"label.pageTitle": "Naslov stranice",
|
||||||
|
"label.pages": "Stranice",
|
||||||
|
"label.password": "Šifra",
|
||||||
|
"label.powered-by": "Omogućeno s {name}",
|
||||||
|
"label.profile": "Profil",
|
||||||
|
"label.queries": "Queryji",
|
||||||
|
"label.query": "Query",
|
||||||
|
"label.query-parameters": "Query parametri",
|
||||||
|
"label.realtime": "Realno vrijeme",
|
||||||
|
"label.referrer": "Referrer",
|
||||||
|
"label.referrers": "Referrers",
|
||||||
|
"label.refresh": "Refresh",
|
||||||
|
"label.regenerate": "Regeneriši",
|
||||||
|
"label.region": "Region",
|
||||||
|
"label.regions": "Regioni",
|
||||||
|
"label.remove": "Ukloni",
|
||||||
|
"label.remove-member": "Ukloni člana",
|
||||||
|
"label.reports": "Izvještaji",
|
||||||
|
"label.required": "Required",
|
||||||
|
"label.reset": "Resetuj",
|
||||||
|
"label.reset-website": "Resetuj web stranicu",
|
||||||
|
"label.retention": "Retention",
|
||||||
|
"label.retention-description": "Izmjeri 'ljepljivost' svoje web stranice praćenjem koliko često set korisnici vraćaju.",
|
||||||
|
"label.role": "Rola",
|
||||||
|
"label.run-query": "Pokreni query",
|
||||||
|
"label.save": "Sačuvaj",
|
||||||
|
"label.screens": "Ekrani",
|
||||||
|
"label.search": "Traži",
|
||||||
|
"label.select": "Odaberi",
|
||||||
|
"label.select-date": "Odaberi datum",
|
||||||
|
"label.select-role": "Odaberi rolu",
|
||||||
|
"label.select-website": "Odaberi web stranicu",
|
||||||
|
"label.sessions": "Sesije",
|
||||||
|
"label.settings": "Postavke",
|
||||||
|
"label.share-url": "Share URL",
|
||||||
|
"label.single-day": "Jedan dan",
|
||||||
|
"label.steps": "Koraci",
|
||||||
|
"label.sum": "Suma",
|
||||||
|
"label.tablet": "Tablet",
|
||||||
|
"label.team": "Tim",
|
||||||
|
"label.team-id": "Tim ID",
|
||||||
|
"label.team-member": "Član tima",
|
||||||
|
"label.team-name": "Naziv tima",
|
||||||
|
"label.team-owner": "Vlasnik tima",
|
||||||
|
"label.team-view-only": "Samo tim može vidjeti",
|
||||||
|
"label.team-websites": "Timske web stranice",
|
||||||
|
"label.teams": "Timovi",
|
||||||
|
"label.theme": "Teme",
|
||||||
|
"label.this-month": "Ovaj mjesec",
|
||||||
|
"label.this-week": "Ova sedmica",
|
||||||
|
"label.this-year": "Ova godina",
|
||||||
|
"label.timezone": "Vremenska zona",
|
||||||
|
"label.title": "Naslov",
|
||||||
|
"label.today": "Danas",
|
||||||
|
"label.toggle-charts": "Uklj/isklj grafikone",
|
||||||
|
"label.total": "Ukupno",
|
||||||
|
"label.total-records": "Ukupno redova",
|
||||||
|
"label.tracking-code": "Kod za praćenje",
|
||||||
|
"label.transfer": "Transfer",
|
||||||
|
"label.transfer-website": "Transfer web stranice",
|
||||||
|
"label.true": "Da",
|
||||||
|
"label.type": "Tip",
|
||||||
|
"label.unique": "Jedinstveno",
|
||||||
|
"label.unique-visitors": "Jedinstvenih posjetitelja",
|
||||||
|
"label.unknown": "Nepoznato",
|
||||||
|
"label.untitled": "Bezimeno",
|
||||||
|
"label.update": "Update",
|
||||||
|
"label.url": "URL",
|
||||||
|
"label.urls": "URLs",
|
||||||
|
"label.user": "Korisnik",
|
||||||
|
"label.username": "Korisničko ime",
|
||||||
|
"label.users": "Korisnici",
|
||||||
|
"label.utm": "UTM",
|
||||||
|
"label.utm-description": "Pratite vaše kampanje kroz UTM parametre.",
|
||||||
|
"label.value": "Vrijednost",
|
||||||
|
"label.view": "Pregled",
|
||||||
|
"label.view-details": "Pogledaj detalje",
|
||||||
|
"label.view-only": "Samo gledanje",
|
||||||
|
"label.views": "Pregledi",
|
||||||
|
"label.views-per-visit": "Pregledi po posjeti",
|
||||||
|
"label.visitors": "Posjetitelji",
|
||||||
|
"label.visits": "Posjete",
|
||||||
|
"label.website": "Web stranica",
|
||||||
|
"label.website-id": "ID web stranice",
|
||||||
|
"label.websites": "Web stranice",
|
||||||
|
"label.window": "Prozor",
|
||||||
|
"label.yesterday": "Jučer",
|
||||||
|
"message.action-confirmation": "Unesite {confirmation} ispod da potvrdite.",
|
||||||
|
"message.active-users": "{x} trenutno {x, plural, one {posjetitelj} other {posjetitelja}}",
|
||||||
|
"message.confirm-delete": "Jeste li sigurni da želite obrisati {target}?",
|
||||||
|
"message.confirm-leave": "Jeste li sigurni da želite napustiti {target}?",
|
||||||
|
"message.confirm-remove": "Jeste li sigurni da želite ukloniti {target}?",
|
||||||
|
"message.confirm-reset": "Jeste li sigurni da želite resetovati {target}?",
|
||||||
|
"message.delete-team-warning": "Brisanje tima će također obrisati sve web stranice tima.",
|
||||||
|
"message.delete-website-warning": "Svi podaci web stranice biće obrisani.",
|
||||||
|
"message.error": "Nešto je pošlo po zlu.",
|
||||||
|
"message.event-log": "{event} na {url}",
|
||||||
|
"message.go-to-settings": "Idi na postavke",
|
||||||
|
"message.incorrect-username-password": "Pogrešno korisničko ime i/ili šifra.",
|
||||||
|
"message.invalid-domain": "Nevalidna domena. Ne uključujte http/https.",
|
||||||
|
"message.min-password-length": "Minimalna dužina od {n} karaktera",
|
||||||
|
"message.new-version-available": "Nova verzija Umami {version} je dostupna!",
|
||||||
|
"message.no-data-available": "Nema dostupnih podataka.",
|
||||||
|
"message.no-event-data": "Nema dostupnih podataka o događajima.",
|
||||||
|
"message.no-match-password": "Šifre se ne poklapaju.",
|
||||||
|
"message.no-results-found": "Nema rezultata.",
|
||||||
|
"message.no-team-websites": "Ovaj tim nema nikakvih web stranica.",
|
||||||
|
"message.no-teams": "Niste kreirali nijedan tim.",
|
||||||
|
"message.no-users": "Nema nikakvih korisnika.",
|
||||||
|
"message.no-websites-configured": "Nemate iskonfigurisanu nijednu web stranicu.",
|
||||||
|
"message.page-not-found": "Stranica nije pronađena",
|
||||||
|
"message.reset-website": "Da resetujete ovu web stranicu, upišite {confirmation} dole da potvrdite.",
|
||||||
|
"message.reset-website-warning": "Sve statistike o ovoj web stranici će biti obrisane, ali vaše postavke neće biti dirane.",
|
||||||
|
"message.saved": "Sačuvano.",
|
||||||
|
"message.share-url": "Statistike vaše web stranice su javno dostupne na sljedećem URLu:",
|
||||||
|
"message.team-already-member": "Već ste član tima.",
|
||||||
|
"message.team-not-found": "Tim nije pronađen.",
|
||||||
|
"message.team-websites-info": "Web stranice može vidjeti bilo ko iz tima.",
|
||||||
|
"message.tracking-code": "Da pratite statistike ove web stranice, stavite sljedeći kod u <head>...</head> sekciju vašeg HTMLa.",
|
||||||
|
"message.transfer-team-website-to-user": "Prebacite ovu web stranicu na vaš račun?",
|
||||||
|
"message.transfer-user-website-to-team": "Odaberite tim u koji želite prebaciti ovu web stranicu.",
|
||||||
|
"message.transfer-website": "Prebacite vlasništvo web stranice na vaš račun ili drugi tim.",
|
||||||
|
"message.triggered-event": "Trigerovani događaj",
|
||||||
|
"message.user-deleted": "Korisnik obrisan.",
|
||||||
|
"message.viewed-page": "Pogledana stranica",
|
||||||
|
"message.visitor-log": "Posjetitelj iz {country} koristi {browser} na {os} {device}",
|
||||||
|
"message.visitors-dropped-off": "Posjetitelji koji su napustili stranicu"
|
||||||
|
}
|
||||||
|
|
@ -1,246 +1,246 @@
|
||||||
{
|
{
|
||||||
"label.access-code": "Access code",
|
"label.access-code": "Codi d'accés",
|
||||||
"label.actions": "Accions",
|
"label.actions": "Accions",
|
||||||
"label.activity-log": "Activity log",
|
"label.activity-log": "Registre d'activitat",
|
||||||
"label.add": "Add",
|
"label.add": "Afegir",
|
||||||
"label.add-description": "Add description",
|
"label.add-description": "Afegir descripció",
|
||||||
"label.add-member": "Add member",
|
"label.add-member": "Afegir membre",
|
||||||
"label.add-step": "Add step",
|
"label.add-step": "Afegir pas",
|
||||||
"label.add-website": "Afegeix lloc web",
|
"label.add-website": "Afegir lloc web",
|
||||||
"label.admin": "Administrador",
|
"label.admin": "Administrador",
|
||||||
"label.after": "After",
|
"label.after": "Després",
|
||||||
"label.all": "Tots",
|
"label.all": "Tots",
|
||||||
"label.all-time": "Sempre",
|
"label.all-time": "Sempre",
|
||||||
"label.analytics": "Analytics",
|
"label.analytics": "Analítiques",
|
||||||
"label.average": "Average",
|
"label.average": "Mitjana",
|
||||||
"label.average-visit-time": "Temps mitjà de visita",
|
"label.average-visit-time": "Temps mitjà de visita",
|
||||||
"label.back": "Enrere",
|
"label.back": "Enrere",
|
||||||
"label.before": "Before",
|
"label.before": "Abans",
|
||||||
"label.bounce-rate": "Percentatge de rebot",
|
"label.bounce-rate": "Percentatge de rebot",
|
||||||
"label.breakdown": "Breakdown",
|
"label.breakdown": "Desglossament",
|
||||||
"label.browser": "Browser",
|
"label.browser": "Navegador",
|
||||||
"label.browsers": "Navegadors",
|
"label.browsers": "Navegadors",
|
||||||
"label.cancel": "Cancel·la",
|
"label.cancel": "Cancel·la",
|
||||||
"label.change-password": "Canvia la contrasenya",
|
"label.change-password": "Canvia la contrasenya",
|
||||||
"label.cities": "Cities",
|
"label.cities": "Ciutats",
|
||||||
"label.city": "City",
|
"label.city": "Ciutat",
|
||||||
"label.clear-all": "Clear all",
|
"label.clear-all": "Netejar tot",
|
||||||
"label.confirm": "Confirm",
|
"label.confirm": "Confirmar",
|
||||||
"label.confirm-password": "Confirma la contrasenya",
|
"label.confirm-password": "Confirma la contrasenya",
|
||||||
"label.contains": "Contains",
|
"label.contains": "Conté",
|
||||||
"label.continue": "Continue",
|
"label.continue": "Continuar",
|
||||||
"label.countries": "Països",
|
"label.countries": "Països",
|
||||||
"label.country": "Country",
|
"label.country": "País",
|
||||||
"label.create": "Create",
|
"label.create": "Crear",
|
||||||
"label.create-report": "Create report",
|
"label.create-report": "Crear informe",
|
||||||
"label.create-team": "Create team",
|
"label.create-team": "Crear equip",
|
||||||
"label.create-user": "Create user",
|
"label.create-user": "Crear usuari",
|
||||||
"label.created": "Created",
|
"label.created": "Creat",
|
||||||
"label.created-by": "Created By",
|
"label.created-by": "Creat Per",
|
||||||
"label.current-password": "Contrasenya actual",
|
"label.current-password": "Contrasenya actual",
|
||||||
"label.custom-range": "Rang personalitzat",
|
"label.custom-range": "Rang personalitzat",
|
||||||
"label.dashboard": "Panell",
|
"label.dashboard": "Panell",
|
||||||
"label.data": "Data",
|
"label.data": "Dades",
|
||||||
"label.date": "Date",
|
"label.date": "Data",
|
||||||
"label.date-range": "Interval de dates",
|
"label.date-range": "Interval de dates",
|
||||||
"label.day": "Day",
|
"label.day": "Dia",
|
||||||
"label.default-date-range": "Interval de dates per defecte",
|
"label.default-date-range": "Interval de dates per defecte",
|
||||||
"label.delete": "Esborra",
|
"label.delete": "Esborra",
|
||||||
"label.delete-report": "Delete report",
|
"label.delete-report": "Eliminar informe",
|
||||||
"label.delete-team": "Delete team",
|
"label.delete-team": "Eliminar equip",
|
||||||
"label.delete-user": "Delete user",
|
"label.delete-user": "Eliminar usuari",
|
||||||
"label.delete-website": "Esborra el lloc web",
|
"label.delete-website": "Esborra el lloc web",
|
||||||
"label.description": "Description",
|
"label.description": "Descripció",
|
||||||
"label.desktop": "Escriptori",
|
"label.desktop": "Escriptori",
|
||||||
"label.details": "Details",
|
"label.details": "Detalls",
|
||||||
"label.device": "Device",
|
"label.device": "Dispositiu",
|
||||||
"label.devices": "Dispositius",
|
"label.devices": "Dispositius",
|
||||||
"label.dismiss": "Descarta",
|
"label.dismiss": "Descarta",
|
||||||
"label.does-not-contain": "Does not contain",
|
"label.does-not-contain": "No conté",
|
||||||
"label.domain": "Domini",
|
"label.domain": "Domini",
|
||||||
"label.dropoff": "Dropoff",
|
"label.dropoff": "Abandonament",
|
||||||
"label.edit": "Edita",
|
"label.edit": "Edita",
|
||||||
"label.edit-dashboard": "Edit dashboard",
|
"label.edit-dashboard": "Edita panell",
|
||||||
"label.edit-member": "Edit member",
|
"label.edit-member": "Edita membre",
|
||||||
"label.enable-share-url": "Activa l'enllaç per compartir",
|
"label.enable-share-url": "Activa l'enllaç per compartir",
|
||||||
"label.event": "Event",
|
"label.event": "Esdeveniment",
|
||||||
"label.event-data": "Event data",
|
"label.event-data": "Dades de l'esdeveniment",
|
||||||
"label.events": "Esdeveniments",
|
"label.events": "Esdeveniments",
|
||||||
"label.false": "False",
|
"label.false": "Fals",
|
||||||
"label.field": "Field",
|
"label.field": "Camp",
|
||||||
"label.fields": "Fields",
|
"label.fields": "Camps",
|
||||||
"label.filter": "Filter",
|
"label.filter": "Filtre",
|
||||||
"label.filter-combined": "Combinat",
|
"label.filter-combined": "Combinat",
|
||||||
"label.filter-raw": "En cru",
|
"label.filter-raw": "En cru",
|
||||||
"label.filters": "Filters",
|
"label.filters": "Filtres",
|
||||||
"label.funnel": "Funnel",
|
"label.funnel": "Embut",
|
||||||
"label.funnel-description": "Understand the conversion and drop-off rate of users.",
|
"label.funnel-description": "Entengui la taxa de conversió i abandonament dels usuaris.",
|
||||||
"label.greater-than": "Greater than",
|
"label.greater-than": "Més gran que",
|
||||||
"label.greater-than-equals": "Greater than or equals",
|
"label.greater-than-equals": "Més gran que o igual a",
|
||||||
"label.insights": "Insights",
|
"label.insights": "Insights",
|
||||||
"label.insights-description": "Dive deeper into your data by using segments and filters.",
|
"label.insights-description": "Aprofundeixi en les seves dades mitjançant l'ús de segments i filtres.",
|
||||||
"label.is": "Is",
|
"label.is": "És igual a",
|
||||||
"label.is-not": "Is not",
|
"label.is-not": "No és igual a",
|
||||||
"label.is-not-set": "Is not set",
|
"label.is-not-set": "No està establert",
|
||||||
"label.is-set": "Is set",
|
"label.is-set": "Està establert",
|
||||||
"label.join": "Join",
|
"label.join": "Unir",
|
||||||
"label.join-team": "Join team",
|
"label.join-team": "Unir-se al equip",
|
||||||
"label.language": "Language",
|
"label.language": "Idioma",
|
||||||
"label.languages": "Llengües",
|
"label.languages": "Idiomes",
|
||||||
"label.laptop": "Portàtil",
|
"label.laptop": "Portàtil",
|
||||||
"label.last-days": "Últims {x} dies",
|
"label.last-days": "Últims {x} dies",
|
||||||
"label.last-hours": "Últimes {x} hores",
|
"label.last-hours": "Últimes {x} hores",
|
||||||
"label.last-months": "Last {x} months",
|
"label.last-months": "Últims {x} mesos",
|
||||||
"label.leave": "Leave",
|
"label.leave": "Abandonar",
|
||||||
"label.leave-team": "Leave team",
|
"label.leave-team": "Abandonar equip",
|
||||||
"label.less-than": "Less than",
|
"label.less-than": "Menor que",
|
||||||
"label.less-than-equals": "Less than or equals",
|
"label.less-than-equals": "Menor que o igual a",
|
||||||
"label.login": "Connecta't",
|
"label.login": "Connecta't",
|
||||||
"label.logout": "Desconnecta't",
|
"label.logout": "Desconnecta't",
|
||||||
"label.manage": "Manage",
|
"label.manage": "Administrar",
|
||||||
"label.max": "Max",
|
"label.max": "Màx",
|
||||||
"label.member": "Member",
|
"label.member": "Membre",
|
||||||
"label.members": "Members",
|
"label.members": "Membres",
|
||||||
"label.min": "Min",
|
"label.min": "Mín",
|
||||||
"label.mobile": "Mòbil",
|
"label.mobile": "Mòbil",
|
||||||
"label.more": "Més",
|
"label.more": "Més",
|
||||||
"label.my-account": "My account",
|
"label.my-account": "El meu compte",
|
||||||
"label.my-websites": "My websites",
|
"label.my-websites": "Els meus llocs web",
|
||||||
"label.name": "Nom",
|
"label.name": "Nom",
|
||||||
"label.new-password": "Contrasenya nova",
|
"label.new-password": "Contrasenya nova",
|
||||||
"label.none": "None",
|
"label.none": "Cap",
|
||||||
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
||||||
"label.ok": "OK",
|
"label.ok": "OK",
|
||||||
"label.os": "OS",
|
"label.os": "SO",
|
||||||
"label.overview": "Overview",
|
"label.overview": "Resum",
|
||||||
"label.owner": "Propietari",
|
"label.owner": "Propietari",
|
||||||
"label.page-of": "Page {current} of {total}",
|
"label.page-of": "Pàgina {current} de {total}",
|
||||||
"label.page-views": "Pàgines vistes",
|
"label.page-views": "Pàgines vistes",
|
||||||
"label.pageTitle": "Page title",
|
"label.pageTitle": "Títol de la pàgina",
|
||||||
"label.pages": "Pàgines",
|
"label.pages": "Pàgines",
|
||||||
"label.password": "Contrasenya",
|
"label.password": "Contrasenya",
|
||||||
"label.powered-by": "Funciona amb {name}",
|
"label.powered-by": "Funciona amb {name}",
|
||||||
"label.profile": "Perfil",
|
"label.profile": "Perfil",
|
||||||
"label.queries": "Queries",
|
"label.queries": "Consultes",
|
||||||
"label.query": "Query",
|
"label.query": "Consulta",
|
||||||
"label.query-parameters": "Query parameters",
|
"label.query-parameters": "Paràmetres de consulta",
|
||||||
"label.realtime": "Temps real",
|
"label.realtime": "Temps real",
|
||||||
"label.referrer": "Referrer",
|
"label.referrer": "Referent",
|
||||||
"label.referrers": "Referents",
|
"label.referrers": "Referents",
|
||||||
"label.refresh": "Refresca",
|
"label.refresh": "Refresca",
|
||||||
"label.regenerate": "Regenerate",
|
"label.regenerate": "Regenerar",
|
||||||
"label.region": "Region",
|
"label.region": "Regió",
|
||||||
"label.regions": "Regions",
|
"label.regions": "Regions",
|
||||||
"label.remove": "Remove",
|
"label.remove": "Treure",
|
||||||
"label.remove-member": "Remove member",
|
"label.remove-member": "Eliminar membre",
|
||||||
"label.reports": "Reports",
|
"label.reports": "Informes",
|
||||||
"label.required": "Obligatori",
|
"label.required": "Obligatori",
|
||||||
"label.reset": "Restableix",
|
"label.reset": "Restableix",
|
||||||
"label.reset-website": "Restableix estadístiques",
|
"label.reset-website": "Restableix estadístiques",
|
||||||
"label.retention": "Retention",
|
"label.retention": "Retenció",
|
||||||
"label.retention-description": "Measure your website stickiness by tracking how often users return.",
|
"label.retention-description": "Mesuri la retenció del seu lloc web fent un seguiment de la freqüència amb què tornen els usuaris.",
|
||||||
"label.role": "Role",
|
"label.role": "Rol",
|
||||||
"label.run-query": "Run query",
|
"label.run-query": "Executar consulta",
|
||||||
"label.save": "Desa",
|
"label.save": "Desa",
|
||||||
"label.screens": "Screens",
|
"label.screens": "Pantalles",
|
||||||
"label.search": "Search",
|
"label.search": "Buscar",
|
||||||
"label.select": "Select",
|
"label.select": "Seleccionar",
|
||||||
"label.select-date": "Select date",
|
"label.select-date": "Seleccionar data",
|
||||||
"label.select-role": "Select role",
|
"label.select-role": "Seleccionar rol",
|
||||||
"label.select-website": "Select website",
|
"label.select-website": "Seleccionar lloc web",
|
||||||
"label.sessions": "Sessions",
|
"label.sessions": "Sessions",
|
||||||
"label.settings": "Configuració",
|
"label.settings": "Configuració",
|
||||||
"label.share-url": "Enllaç per compartir",
|
"label.share-url": "Enllaç per compartir",
|
||||||
"label.single-day": "Un sol dia",
|
"label.single-day": "Un sol dia",
|
||||||
"label.steps": "Steps",
|
"label.steps": "Pasos",
|
||||||
"label.sum": "Sum",
|
"label.sum": "Suma",
|
||||||
"label.tablet": "Tauleta",
|
"label.tablet": "Tauleta",
|
||||||
"label.team": "Team",
|
"label.team": "Equip",
|
||||||
"label.team-id": "Team ID",
|
"label.team-id": "ID del equip",
|
||||||
"label.team-member": "Team member",
|
"label.team-member": "Membre de l'equip",
|
||||||
"label.team-name": "Team name",
|
"label.team-name": "Nom de l'equip",
|
||||||
"label.team-owner": "Team owner",
|
"label.team-owner": "Propietari de l'equip",
|
||||||
"label.team-view-only": "Team view only",
|
"label.team-view-only": "Vista només de l'equip",
|
||||||
"label.team-websites": "Team websites",
|
"label.team-websites": "Llocs web de l'equip",
|
||||||
"label.teams": "Teams",
|
"label.teams": "Equips",
|
||||||
"label.theme": "Theme",
|
"label.theme": "Tema",
|
||||||
"label.this-month": "Aquest mes",
|
"label.this-month": "Aquest mes",
|
||||||
"label.this-week": "Aquesta setmana",
|
"label.this-week": "Aquesta setmana",
|
||||||
"label.this-year": "Aquest any",
|
"label.this-year": "Aquest any",
|
||||||
"label.timezone": "Zona horària",
|
"label.timezone": "Zona horària",
|
||||||
"label.title": "Title",
|
"label.title": "Títol",
|
||||||
"label.today": "Avui",
|
"label.today": "Avui",
|
||||||
"label.toggle-charts": "Mostra/amaga gràfics",
|
"label.toggle-charts": "Mostra/amaga gràfics",
|
||||||
"label.total": "Total",
|
"label.total": "Total",
|
||||||
"label.total-records": "Total records",
|
"label.total-records": "Total de registres",
|
||||||
"label.tracking-code": "Codi de seguiment",
|
"label.tracking-code": "Codi de seguiment",
|
||||||
"label.transfer": "Transfer",
|
"label.transfer": "Transferir",
|
||||||
"label.transfer-website": "Transfer website",
|
"label.transfer-website": "Transferir lloc web",
|
||||||
"label.true": "True",
|
"label.true": "Cert",
|
||||||
"label.type": "Type",
|
"label.type": "Tipus",
|
||||||
"label.unique": "Unique",
|
"label.unique": "Únic",
|
||||||
"label.unique-visitors": "Visitants únics",
|
"label.unique-visitors": "Visitants únics",
|
||||||
"label.unknown": "Desconegut",
|
"label.unknown": "Desconegut",
|
||||||
"label.untitled": "Untitled",
|
"label.untitled": "Sense títol",
|
||||||
"label.update": "Update",
|
"label.update": "Actualitzar",
|
||||||
"label.url": "URL",
|
"label.url": "URL",
|
||||||
"label.urls": "URLs",
|
"label.urls": "URLs",
|
||||||
"label.user": "User",
|
"label.user": "Usuari",
|
||||||
"label.username": "Nom d'usuari",
|
"label.username": "Nom d'usuari",
|
||||||
"label.users": "Users",
|
"label.users": "Usuaris",
|
||||||
"label.utm": "UTM",
|
"label.utm": "UTM",
|
||||||
"label.utm-description": "Track your campaigns through UTM parameters.",
|
"label.utm-description": "Rastreji les seves campanyes a través de paràmetres UTM.",
|
||||||
"label.value": "Value",
|
"label.value": "Valor",
|
||||||
"label.view": "View",
|
"label.view": "Visualitzar",
|
||||||
"label.view-details": "Veure els detalls",
|
"label.view-details": "Veure els detalls",
|
||||||
"label.view-only": "View only",
|
"label.view-only": "Només veure",
|
||||||
"label.views": "Vistes",
|
"label.views": "Vistes",
|
||||||
"label.views-per-visit": "Views per visit",
|
"label.views-per-visit": "Views per visit",
|
||||||
"label.visitors": "Visitants",
|
"label.visitors": "Visitants",
|
||||||
"label.visits": "Visits",
|
"label.visits": "Visites",
|
||||||
"label.website": "Website",
|
"label.website": "Lloc web",
|
||||||
"label.website-id": "Website ID",
|
"label.website-id": "ID del lloc web",
|
||||||
"label.websites": "Llocs web",
|
"label.websites": "Llocs web",
|
||||||
"label.window": "Window",
|
"label.window": "Finestra",
|
||||||
"label.yesterday": "Ahir",
|
"label.yesterday": "Ahir",
|
||||||
"message.action-confirmation": "Type {confirmation} in the box below to confirm.",
|
"message.action-confirmation": "Escrigui {confirmation} al cuadre inferior per confirmar.",
|
||||||
"message.active-users": "{x} {x, plural, one {visitant actual} other {visitants actuals}}",
|
"message.active-users": "{x} {x, plural, one {visitant actual} other {visitants actuals}}",
|
||||||
"message.confirm-delete": "Segur que vols esborrar {target}?",
|
"message.confirm-delete": "Segur que vol esborrar {target}?",
|
||||||
"message.confirm-leave": "Are you sure you want to leave {target}?",
|
"message.confirm-leave": "Segur que vol abandonar {target}?",
|
||||||
"message.confirm-remove": "Are you sure you want to remove {target}?",
|
"message.confirm-remove": "Segur que vol eliminar {target}?",
|
||||||
"message.confirm-reset": "Segur que vols restablir les estadístiques de {target}?",
|
"message.confirm-reset": "Segur que vol restablir les estadístiques de {target}?",
|
||||||
"message.delete-team-warning": "Deleting a team will also delete all team websites.",
|
"message.delete-team-warning": "Al eliminar un equip també s'eliminaran tots els llocs web de l'equip.",
|
||||||
"message.delete-website-warning": "També s'esborraran totes les dades relacionades.",
|
"message.delete-website-warning": "També s'esborraran totes les dades relacionades.",
|
||||||
"message.error": "S'ha produït un error.",
|
"message.error": "S'ha produït un error.",
|
||||||
"message.event-log": "{event} on {url}",
|
"message.event-log": "{event} a {url}",
|
||||||
"message.go-to-settings": "Vés a la configuració",
|
"message.go-to-settings": "Vés a la configuració",
|
||||||
"message.incorrect-username-password": "Nom d'usuari o contrasenya incorrectes.",
|
"message.incorrect-username-password": "Nom d'usuari o contrasenya incorrectes.",
|
||||||
"message.invalid-domain": "Domini invàlid",
|
"message.invalid-domain": "Domini invàlid",
|
||||||
"message.min-password-length": "Minimum length of {n} characters",
|
"message.min-password-length": "Longitud mínima de {n} caràcters",
|
||||||
"message.new-version-available": "A new version of Umami {version} is available!",
|
"message.new-version-available": "Una nova versió d'Umami {version} està disponible!",
|
||||||
"message.no-data-available": "No hi ha dades disponibles.",
|
"message.no-data-available": "No hi ha dades disponibles.",
|
||||||
"message.no-event-data": "No event data is available.",
|
"message.no-event-data": "No hi ha dades d'esdeveniments disponibles.",
|
||||||
"message.no-match-password": "Les contrasenyes no coincideixen",
|
"message.no-match-password": "Les contrasenyes no coincideixen",
|
||||||
"message.no-results-found": "No results were found.",
|
"message.no-results-found": "No s'han trobat resultats.",
|
||||||
"message.no-team-websites": "This team does not have any websites.",
|
"message.no-team-websites": "Aquest equip no té cap lloc web.",
|
||||||
"message.no-teams": "You have not created any teams.",
|
"message.no-teams": "No ha creat cap equip.",
|
||||||
"message.no-users": "There are no users.",
|
"message.no-users": "No hi ha cap usuari.",
|
||||||
"message.no-websites-configured": "No hi ha cap lloc web configurat.",
|
"message.no-websites-configured": "No hi ha cap lloc web configurat.",
|
||||||
"message.page-not-found": "No s'ha trobat la pàgina.",
|
"message.page-not-found": "No s'ha trobat la pàgina.",
|
||||||
"message.reset-website": "To reset this website, type {confirmation} in the box below to confirm.",
|
"message.reset-website": "Per restablir aquest lloc web, escrigui {confirmation} al cuadre inferior per confirmar.",
|
||||||
"message.reset-website-warning": "S'esborraran totes les estadístiques per aquest lloc web, però el codi de seguiment es mantindrà.",
|
"message.reset-website-warning": "S'esborraran totes les estadístiques per aquest lloc web, però el codi de seguiment es mantindrà.",
|
||||||
"message.saved": "S'ha desat amb èxit.",
|
"message.saved": "S'ha desat amb èxit.",
|
||||||
"message.share-url": "Aquest és l'enllaç públic per compartir de {target}.",
|
"message.share-url": "Aquest és l'enllaç públic per compartir de {target}.",
|
||||||
"message.team-already-member": "You are already a member of the team.",
|
"message.team-already-member": "Ja és membre d'aquest equip.",
|
||||||
"message.team-not-found": "Team not found.",
|
"message.team-not-found": "Equip no trobat.",
|
||||||
"message.team-websites-info": "Websites can be viewed by anyone on the team.",
|
"message.team-websites-info": "Els llocs web poden ser visualitzats per qualsevol membre de l'equip.",
|
||||||
"message.tracking-code": "Codi de seguiment",
|
"message.tracking-code": "Codi de seguiment",
|
||||||
"message.transfer-team-website-to-user": "Transfer this website to your account?",
|
"message.transfer-team-website-to-user": "Transferir aquest lloc web al seu compte?",
|
||||||
"message.transfer-user-website-to-team": "Select the team to transfer this website to.",
|
"message.transfer-user-website-to-team": "Seleccioni l'equip al qui transferir aquest lloc web.",
|
||||||
"message.transfer-website": "Transfer website ownership to your account or another team.",
|
"message.transfer-website": "Transferir la propietat del lloc web al seu compte o a un altre equip.",
|
||||||
"message.triggered-event": "Triggered event",
|
"message.triggered-event": "Esdeveniment desencadenat",
|
||||||
"message.user-deleted": "User deleted.",
|
"message.user-deleted": "Usuari eliminat.",
|
||||||
"message.viewed-page": "Viewed page",
|
"message.viewed-page": "Pàgina vista",
|
||||||
"message.visitor-log": "Visitant de {country} usant {browser} a {os} {device}",
|
"message.visitor-log": "Visitant de {country} usant {browser} a {os} {device}",
|
||||||
"message.visitors-dropped-off": "Visitors dropped off"
|
"message.visitors-dropped-off": "Els visitants han sortit"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,246 +1,246 @@
|
||||||
{
|
{
|
||||||
"label.access-code": "Access code",
|
"label.access-code": "Erişim Kodu",
|
||||||
"label.actions": "Hareketler",
|
"label.actions": "Hareketler",
|
||||||
"label.activity-log": "Activity log",
|
"label.activity-log": "Aktivite Kaydı",
|
||||||
"label.add": "Add",
|
"label.add": "Ekle",
|
||||||
"label.add-description": "Add description",
|
"label.add-description": "Açıklama ekle",
|
||||||
"label.add-member": "Add member",
|
"label.add-member": "Üye ekle",
|
||||||
"label.add-step": "Add step",
|
"label.add-step": "Adım ekle",
|
||||||
"label.add-website": "Web sitesi ekle",
|
"label.add-website": "Web sitesi ekle",
|
||||||
"label.admin": "Yönetici",
|
"label.administrator": "Yönetici",
|
||||||
"label.after": "After",
|
"label.after": "Sonra",
|
||||||
"label.all": "Tümü",
|
"label.all": "Tümü",
|
||||||
"label.all-time": "All time",
|
"label.all-time": "Tüm zamanlar",
|
||||||
"label.analytics": "Analytics",
|
"label.analytics": "Analitik",
|
||||||
"label.average": "Average",
|
"label.average": "Ortalama",
|
||||||
"label.average-visit-time": "Ortalama ziyaret süresi",
|
"label.average-visit-time": "Ortalama ziyaret süresi",
|
||||||
"label.back": "Geri",
|
"label.back": "Geri",
|
||||||
"label.before": "Before",
|
"label.before": "Önce",
|
||||||
"label.bounce-rate": "Çıkma oranı",
|
"label.bounce-rate": "Tek sayfa ziyaret oranı",
|
||||||
"label.breakdown": "Breakdown",
|
"label.breakdown": "Dağılım",
|
||||||
"label.browser": "Browser",
|
"label.browser": "Tarayıcı",
|
||||||
"label.browsers": "Tarayıcılar",
|
"label.browsers": "Tarayıcılar",
|
||||||
"label.cancel": "İptal",
|
"label.cancel": "İptal",
|
||||||
"label.change-password": "Şifre değiştir",
|
"label.change-password": "Şifre değiştir",
|
||||||
"label.cities": "Cities",
|
"label.cities": "Şehirler",
|
||||||
"label.city": "City",
|
"label.city": "Şehir",
|
||||||
"label.clear-all": "Clear all",
|
"label.clear-all": "Hepsini temizle",
|
||||||
"label.confirm": "Confirm",
|
"label.confirm": "Onayla",
|
||||||
"label.confirm-password": "Parolayı onayla",
|
"label.confirm-password": "Parolayı onayla",
|
||||||
"label.contains": "Contains",
|
"label.contains": "İçeriği",
|
||||||
"label.continue": "Continue",
|
"label.continue": "Devam et",
|
||||||
"label.countries": "Ülkeler",
|
"label.countries": "Ülkeler",
|
||||||
"label.country": "Country",
|
"label.country": "Ülke",
|
||||||
"label.create": "Create",
|
"label.create": "Oluştur",
|
||||||
"label.create-report": "Create report",
|
"label.create-report": "Rapor oluştur",
|
||||||
"label.create-team": "Create team",
|
"label.create-team": "Takım oluştur",
|
||||||
"label.create-user": "Create user",
|
"label.create-user": "Kullanıcı oluştur",
|
||||||
"label.created": "Created",
|
"label.created": "Oluşturuldu",
|
||||||
"label.created-by": "Created By",
|
"label.created-by": "Tarafından oluşturldu",
|
||||||
"label.current-password": "Mevcut parola",
|
"label.current-password": "Mevcut parola",
|
||||||
"label.custom-range": "Özelleştirilmiş aralık",
|
"label.custom-range": "Özelleştirilmiş aralık",
|
||||||
"label.dashboard": "Kontrol Paneli",
|
"label.dashboard": "Kontrol Paneli",
|
||||||
"label.data": "Data",
|
"label.data": "Veri",
|
||||||
"label.date": "Date",
|
"label.date": "Tarih",
|
||||||
"label.date-range": "Tarih aralığı",
|
"label.date-range": "Tarih aralığı",
|
||||||
"label.day": "Day",
|
"label.day": "Gün",
|
||||||
"label.default-date-range": "Varsayılan tarih aralığı",
|
"label.default-date-range": "Varsayılan tarih aralığı",
|
||||||
"label.delete": "Sil",
|
"label.delete": "Sil",
|
||||||
"label.delete-report": "Delete report",
|
"label.delete-report": "Rapor sil",
|
||||||
"label.delete-team": "Delete team",
|
"label.delete-team": "Takım sil",
|
||||||
"label.delete-user": "Delete user",
|
"label.delete-user": "Kullanıcı sil",
|
||||||
"label.delete-website": "Web sitesini sil",
|
"label.delete-website": "Web sitesini sil",
|
||||||
"label.description": "Description",
|
"label.description": "Açıklama",
|
||||||
"label.desktop": "Masaüstü",
|
"label.desktop": "Masaüstü",
|
||||||
"label.details": "Details",
|
"label.details": "Detaylar",
|
||||||
"label.device": "Device",
|
"label.device": "Cihaz",
|
||||||
"label.devices": "Cihazlar",
|
"label.devices": "Cihazlar",
|
||||||
"label.dismiss": "Reddet",
|
"label.dismiss": "Reddet",
|
||||||
"label.does-not-contain": "Does not contain",
|
"label.does-not-contain": "İçermez",
|
||||||
"label.domain": "Alan adı",
|
"label.domain": "Alan adı",
|
||||||
"label.dropoff": "Dropoff",
|
"label.dropoff": "Bırakma",
|
||||||
"label.edit": "Düzenle",
|
"label.edit": "Düzenle",
|
||||||
"label.edit-dashboard": "Edit dashboard",
|
"label.edit-dashboard": "Kontrol panelini düzenle",
|
||||||
"label.edit-member": "Edit member",
|
"label.edit-member": "Üyeyi düzenle",
|
||||||
"label.enable-share-url": "Anonim paylaşım URL'i aktif",
|
"label.enable-share-url": "Anonim paylaşım URL'i aktif",
|
||||||
"label.event": "Event",
|
"label.event": "Olay",
|
||||||
"label.event-data": "Event data",
|
"label.event-data": "Olay verisi",
|
||||||
"label.events": "Olaylar",
|
"label.events": "Olaylar",
|
||||||
"label.false": "False",
|
"label.false": "Yanlış",
|
||||||
"label.field": "Field",
|
"label.field": "Alan",
|
||||||
"label.fields": "Fields",
|
"label.fields": "Alanlar",
|
||||||
"label.filter": "Filter",
|
"label.filter": "Filtre",
|
||||||
"label.filter-combined": "Birleşik",
|
"label.filter-combined": "Birleşik filtre",
|
||||||
"label.filter-raw": "Ham",
|
"label.filter-raw": "Ham filtre",
|
||||||
"label.filters": "Filters",
|
"label.filters": "Filtreler",
|
||||||
"label.funnel": "Funnel",
|
"label.funnel": "Huni",
|
||||||
"label.funnel-description": "Understand the conversion and drop-off rate of users.",
|
"label.funnel-description": "Kullanıcıların dönüşüm ve ayrılma oranlarını anlayın.",
|
||||||
"label.greater-than": "Greater than",
|
"label.greater-than": "Büyüktür",
|
||||||
"label.greater-than-equals": "Greater than or equals",
|
"label.greater-than-equals": "Büyük veya eşittir",
|
||||||
"label.insights": "Insights",
|
"label.insights": "Insights",
|
||||||
"label.insights-description": "Dive deeper into your data by using segments and filters.",
|
"label.insights-description": "Segmentleri ve filtreleri kullanarak verilerinizi derinlemesine inceleyin.",
|
||||||
"label.is": "Is",
|
"label.is": "Is",
|
||||||
"label.is-not": "Is not",
|
"label.is-not": "Değil",
|
||||||
"label.is-not-set": "Is not set",
|
"label.is-not-set": "Ayarlanmamış",
|
||||||
"label.is-set": "Is set",
|
"label.is-set": "Ayarlandı",
|
||||||
"label.join": "Join",
|
"label.join": "Katıl",
|
||||||
"label.join-team": "Join team",
|
"label.join-team": "Takıma katıl",
|
||||||
"label.language": "Language",
|
"label.language": "Dil",
|
||||||
"label.languages": "Languages",
|
"label.languages": "Diller",
|
||||||
"label.laptop": "Dizüstü",
|
"label.laptop": "Dizüstü",
|
||||||
"label.last-days": "Son {x} gün",
|
"label.last-days": "Son {x} gün",
|
||||||
"label.last-hours": "Son {x} saat",
|
"label.last-hours": "Son {x} saat",
|
||||||
"label.last-months": "Last {x} months",
|
"label.last-months": "Son {x} ay",
|
||||||
"label.leave": "Leave",
|
"label.leave": "Ayrıl",
|
||||||
"label.leave-team": "Leave team",
|
"label.leave-team": "Takımdan Ayrıl",
|
||||||
"label.less-than": "Less than",
|
"label.less-than": "Küçüktür",
|
||||||
"label.less-than-equals": "Less than or equals",
|
"label.less-than-equals": "Küçük veya eşittir",
|
||||||
"label.login": "Giriş Yap",
|
"label.login": "Giriş Yap",
|
||||||
"label.logout": "Çıkış Yap",
|
"label.logout": "Çıkış Yap",
|
||||||
"label.manage": "Manage",
|
"label.manage": "Yönet",
|
||||||
"label.max": "Max",
|
"label.max": "Max",
|
||||||
"label.member": "Member",
|
"label.member": "Üye",
|
||||||
"label.members": "Members",
|
"label.members": "Üyeler",
|
||||||
"label.min": "Min",
|
"label.min": "Min",
|
||||||
"label.mobile": "Mobil Cihaz",
|
"label.mobile": "Mobil Cihaz",
|
||||||
"label.more": "Detaylı göster",
|
"label.more": "Detaylı göster",
|
||||||
"label.my-account": "My account",
|
"label.my-account": "Hesabım",
|
||||||
"label.my-websites": "My websites",
|
"label.my-websites": "Web sitelerim",
|
||||||
"label.name": "İsim",
|
"label.name": "İsim",
|
||||||
"label.new-password": "Yeni parola",
|
"label.new-password": "Yeni parola",
|
||||||
"label.none": "None",
|
"label.none": "Yok",
|
||||||
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
||||||
"label.ok": "OK",
|
"label.ok": "TAMAM",
|
||||||
"label.os": "OS",
|
"label.os": "OS",
|
||||||
"label.overview": "Overview",
|
"label.overview": "Genel bakış",
|
||||||
"label.owner": "Owner",
|
"label.owner": "Sahibi",
|
||||||
"label.page-of": "Page {current} of {total}",
|
"label.page-of": "{total} sayfada {current} ",
|
||||||
"label.page-views": "Sayfa görünümü",
|
"label.page-views": "Sayfa görünümü",
|
||||||
"label.pageTitle": "Page title",
|
"label.pageTitle": "Sayfa başlığı",
|
||||||
"label.pages": "Sayfalar",
|
"label.pages": "Sayfalar",
|
||||||
"label.password": "Parola",
|
"label.password": "Parola",
|
||||||
"label.powered-by": "Sağlayıcı: {name}",
|
"label.powered-by": "Sağlayıcı: {name}",
|
||||||
"label.profile": "Profil",
|
"label.profile": "Profil",
|
||||||
"label.queries": "Queries",
|
"label.queries": "Sorgular",
|
||||||
"label.query": "Query",
|
"label.query": "Sorgu",
|
||||||
"label.query-parameters": "Query parameters",
|
"label.query-parameters": "Sorgu parametreleri",
|
||||||
"label.realtime": "Gerçek Zamanlı",
|
"label.realtime": "Gerçek Zamanlı",
|
||||||
"label.referrer": "Referrer",
|
"label.referrer": "Referrer",
|
||||||
"label.referrers": "Yönlendirenler",
|
"label.referrers": "Yönlendirenler",
|
||||||
"label.refresh": "Yenile",
|
"label.refresh": "Yenile",
|
||||||
"label.regenerate": "Regenerate",
|
"label.regenerate": "Yeniden Oluştur",
|
||||||
"label.region": "Region",
|
"label.region": "Bölge",
|
||||||
"label.regions": "Regions",
|
"label.regions": "Bölgeler",
|
||||||
"label.remove": "Remove",
|
"label.remove": "Kaldır",
|
||||||
"label.remove-member": "Remove member",
|
"label.remove-member": "Üyeyi kaldır",
|
||||||
"label.reports": "Reports",
|
"label.reports": "Raporlar",
|
||||||
"label.required": "Zorunlu alan",
|
"label.required": "Zorunlu alan",
|
||||||
"label.reset": "Sıfırla",
|
"label.reset": "Sıfırla",
|
||||||
"label.reset-website": "Reset statistics",
|
"label.reset-website": "İstatistikleri sıfırla",
|
||||||
"label.retention": "Retention",
|
"label.retention": "Geri dönüş",
|
||||||
"label.retention-description": "Measure your website stickiness by tracking how often users return.",
|
"label.retention-description": "Kullanıcıların ne sıklıkla geri döndüğünü takip ederek web sitenizin kalıcılığını ölçün.",
|
||||||
"label.role": "Role",
|
"label.role": "Rol",
|
||||||
"label.run-query": "Run query",
|
"label.run-query": "Sorgu çalıştır",
|
||||||
"label.save": "Kaydet",
|
"label.save": "Kaydet",
|
||||||
"label.screens": "Ekranlar",
|
"label.screens": "Ekranlar",
|
||||||
"label.search": "Search",
|
"label.search": "Ara",
|
||||||
"label.select": "Select",
|
"label.select": "Seç",
|
||||||
"label.select-date": "Select date",
|
"label.select-date": "Tarih seç",
|
||||||
"label.select-role": "Select role",
|
"label.select-role": "Rol seç",
|
||||||
"label.select-website": "Select website",
|
"label.select-website": "Web sitesi seç",
|
||||||
"label.sessions": "Sessions",
|
"label.sessions": "Sessions",
|
||||||
"label.settings": "Ayarlar",
|
"label.settings": "Ayarlar",
|
||||||
"label.share-url": "Paylaşım adresi",
|
"label.share-url": "Paylaşım adresi",
|
||||||
"label.single-day": "Tekil gün",
|
"label.single-day": "Tekil gün",
|
||||||
"label.steps": "Steps",
|
"label.steps": "Adımlar",
|
||||||
"label.sum": "Sum",
|
"label.sum": "Toplam",
|
||||||
"label.tablet": "Tablet",
|
"label.tablet": "Tablet",
|
||||||
"label.team": "Team",
|
"label.team": "Takım",
|
||||||
"label.team-id": "Team ID",
|
"label.team-id": "Takım ID",
|
||||||
"label.team-member": "Team member",
|
"label.team-member": "Takım üyesi",
|
||||||
"label.team-name": "Team name",
|
"label.team-name": "Takım ismi",
|
||||||
"label.team-owner": "Team owner",
|
"label.team-owner": "Takım sahibi",
|
||||||
"label.team-view-only": "Team view only",
|
"label.team-view-only": "Yalnızca ekip görünümü",
|
||||||
"label.team-websites": "Team websites",
|
"label.team-websites": "Takım web siteleri",
|
||||||
"label.teams": "Teams",
|
"label.teams": "Takımlar",
|
||||||
"label.theme": "Theme",
|
"label.theme": "Tema",
|
||||||
"label.this-month": "Bu ay",
|
"label.this-month": "Bu ay",
|
||||||
"label.this-week": "Bu hafta",
|
"label.this-week": "Bu hafta",
|
||||||
"label.this-year": "Bu yıl",
|
"label.this-year": "Bu yıl",
|
||||||
"label.timezone": "Zaman dilimi",
|
"label.timezone": "Zaman dilimi",
|
||||||
"label.title": "Title",
|
"label.title": "Başlık",
|
||||||
"label.today": "Bugün",
|
"label.today": "Bugün",
|
||||||
"label.toggle-charts": "Toggle charts",
|
"label.toggle-charts": "Grafikleri değiştir",
|
||||||
"label.total": "Total",
|
"label.total": "Toplam",
|
||||||
"label.total-records": "Total records",
|
"label.total-records": "Toplam kayıt",
|
||||||
"label.tracking-code": "İzleme kodu",
|
"label.tracking-code": "İzleme kodu",
|
||||||
"label.transfer": "Transfer",
|
"label.transfer": "Transfer",
|
||||||
"label.transfer-website": "Transfer website",
|
"label.transfer-website": "Transfer web sitesi",
|
||||||
"label.true": "True",
|
"label.true": "Doğru",
|
||||||
"label.type": "Type",
|
"label.type": "Tip",
|
||||||
"label.unique": "Unique",
|
"label.unique": "Benzersiz",
|
||||||
"label.unique-visitors": "Tekil kullanıcı",
|
"label.unique-visitors": "Tekil kullanıcı",
|
||||||
"label.unknown": "Bilinmeyen",
|
"label.unknown": "Bilinmeyen",
|
||||||
"label.untitled": "Untitled",
|
"label.untitled": "İsimsiz",
|
||||||
"label.update": "Update",
|
"label.update": "Güncelle",
|
||||||
"label.url": "URL",
|
"label.url": "URL",
|
||||||
"label.urls": "URLs",
|
"label.urls": "URLs",
|
||||||
"label.user": "User",
|
"label.user": "Kullanıcı",
|
||||||
"label.username": "Kullanıcı adı",
|
"label.username": "Kullanıcı adı",
|
||||||
"label.users": "Users",
|
"label.users": "Kullanıcılar",
|
||||||
"label.utm": "UTM",
|
"label.utm": "UTM",
|
||||||
"label.utm-description": "Track your campaigns through UTM parameters.",
|
"label.utm-description": "Kampanyalarınızı UTM parametreleri aracılığıyla takip edin.",
|
||||||
"label.value": "Value",
|
"label.value": "Değer",
|
||||||
"label.view": "View",
|
"label.view": "Görünüm",
|
||||||
"label.view-details": "Detayı incele",
|
"label.view-details": "Detayı incele",
|
||||||
"label.view-only": "View only",
|
"label.view-only": "Sadece görünüm",
|
||||||
"label.views": "Görüntüleme",
|
"label.views": "Görüntüleme",
|
||||||
"label.views-per-visit": "Views per visit",
|
"label.views-per-visit": "Ziyaret başına görüntüleme",
|
||||||
"label.visitors": "Ziyaretçi",
|
"label.visitors": "Ziyaretçi",
|
||||||
"label.visits": "Visits",
|
"label.visits": "Ziyaretler",
|
||||||
"label.website": "Website",
|
"label.website": "Web sitesi",
|
||||||
"label.website-id": "Website ID",
|
"label.website-id": "Website ID",
|
||||||
"label.websites": "Web siteleri",
|
"label.websites": "Web siteleri",
|
||||||
"label.window": "Window",
|
"label.window": "Pencere",
|
||||||
"label.yesterday": "Yesterday",
|
"label.yesterday": "Dün",
|
||||||
"message.action-confirmation": "Type {confirmation} in the box below to confirm.",
|
"message.action-confirmation": "Onaylamak için aşağıdaki kutuya {confirmation} yazın.",
|
||||||
"message.active-users": "{x} aktif ziyaretçi",
|
"message.active-users": "{x} aktif ziyaretçi",
|
||||||
"message.confirm-delete": "{target} kaydını silmek istediğinizden emin misiniz?",
|
"message.confirm-delete": "{target} kaydını silmek istediğinizden emin misiniz?",
|
||||||
"message.confirm-leave": "Are you sure you want to leave {target}?",
|
"message.confirm-leave": "{target} kaydından ayrılmak istediğinizden emin misiniz?",
|
||||||
"message.confirm-remove": "Are you sure you want to remove {target}?",
|
"message.confirm-remove": "{target} kaydını kaldırmak istediğinizden emin misiniz?",
|
||||||
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
|
"message.confirm-reset": "{target} istatistiklerini sıfırlamak istediğinizden emin misiniz?",
|
||||||
"message.delete-team-warning": "Deleting a team will also delete all team websites.",
|
"message.delete-team-warning": "Bir takımı silmek tüm takım web sitelerini de silecektir.",
|
||||||
"message.delete-website-warning": "İlişkili tüm veriler de silinecektir.",
|
"message.delete-website-warning": "İlişkili tüm veriler de silinecektir.",
|
||||||
"message.error": "Bir şeyler ters gitti!",
|
"message.error": "Bir şeyler ters gitti!",
|
||||||
"message.event-log": "{event} on {url}",
|
"message.event-log": "{event} on {url}",
|
||||||
"message.go-to-settings": "Ayarlara git",
|
"message.go-to-settings": "Ayarlara git",
|
||||||
"message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.",
|
"message.incorrect-username-password": "Hatalı kullanıcı adı ya da parola.",
|
||||||
"message.invalid-domain": "Geçersiz alan adı",
|
"message.invalid-domain": "Geçersiz alan adı",
|
||||||
"message.min-password-length": "Minimum length of {n} characters",
|
"message.min-password-length": "Minimum {n} karakter uzunluğu",
|
||||||
"message.new-version-available": "A new version of Umami {version} is available!",
|
"message.new-version-available": "Yeni versiyon Umami {version} mevcut!",
|
||||||
"message.no-data-available": "Henüz hiç veri yok.",
|
"message.no-data-available": "Henüz hiç veri yok.",
|
||||||
"message.no-event-data": "No event data is available.",
|
"message.no-event-data": "Hiçbir olay verisi mevcut değil.",
|
||||||
"message.no-match-password": "Parolalar uyuşmuyor",
|
"message.no-match-password": "Parolalar uyuşmuyor",
|
||||||
"message.no-results-found": "No results were found.",
|
"message.no-results-found": "Hiçbir sonuç bulunamadı.",
|
||||||
"message.no-team-websites": "This team does not have any websites.",
|
"message.no-team-websites": "Bu takımın herhangi bir web sitesi yok.",
|
||||||
"message.no-teams": "You have not created any teams.",
|
"message.no-teams": "Herhangi bir takım oluşturmadınız.",
|
||||||
"message.no-users": "There are no users.",
|
"message.no-users": "Kullanıcı yok.",
|
||||||
"message.no-websites-configured": "Henüz hiç web sitesi tanımlamadınız",
|
"message.no-websites-configured": "Henüz hiç web sitesi tanımlamadınız",
|
||||||
"message.page-not-found": "Sayfa bulunamadı.",
|
"message.page-not-found": "Sayfa bulunamadı.",
|
||||||
"message.reset-website": "To reset this website, type {confirmation} in the box below to confirm.",
|
"message.reset-website": "Bu websitesini sıfılamak için aşağıdaki kutuya {confirmation} yazın.",
|
||||||
"message.reset-website-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
|
"message.reset-website-warning": "Bu web sitesi için tüm istatistikler silinecek, ancak izleme kodunuz bozulmadan kalacaktır.",
|
||||||
"message.saved": "Başarıyla kaydedildi.",
|
"message.saved": "Başarıyla kaydedildi.",
|
||||||
"message.share-url": "{target} için kullanılabilir anonim paylaşım adresidir.",
|
"message.share-url": "{target} için kullanılabilir anonim paylaşım adresidir.",
|
||||||
"message.team-already-member": "You are already a member of the team.",
|
"message.team-already-member": "Zaten bu takımın üyesisiniz",
|
||||||
"message.team-not-found": "Team not found.",
|
"message.team-not-found": "Takım bulunamadı",
|
||||||
"message.team-websites-info": "Websites can be viewed by anyone on the team.",
|
"message.team-websites-info": "Web siteleri takımdaki herkes tarafından görüntülenebilir.",
|
||||||
"message.tracking-code": "İzleme kodu",
|
"message.tracking-code": "İzleme kodu",
|
||||||
"message.transfer-team-website-to-user": "Transfer this website to your account?",
|
"message.transfer-team-website-to-user": "Bu web sitesi hesbınıza aktarılsın mı?",
|
||||||
"message.transfer-user-website-to-team": "Select the team to transfer this website to.",
|
"message.transfer-user-website-to-team": "Bu web sitesinin aktarılacağı takımı seçin.",
|
||||||
"message.transfer-website": "Transfer website ownership to your account or another team.",
|
"message.transfer-website": "Web sitesi sahipliğini hesabınıza veya başka bir takıma aktarın",
|
||||||
"message.triggered-event": "Triggered event",
|
"message.triggered-event": "Tetiklenen olay",
|
||||||
"message.user-deleted": "User deleted.",
|
"message.user-deleted": "Kullanıcı silindi.",
|
||||||
"message.viewed-page": "Viewed page",
|
"message.viewed-page": "Görüntülenen sayfa",
|
||||||
"message.visitor-log": "Yeni ziyaretçi: {country}, {os}, {device}, {browser}",
|
"message.visitor-log": "Yeni ziyaretçi: {country}, {os}, {device}, {browser}",
|
||||||
"message.visitors-dropped-off": "Visitors dropped off"
|
"message.visitors-dropped-off": "Bırakan ziyaretçiler"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"label.add": "添加",
|
"label.add": "添加",
|
||||||
"label.add-description": "添加描述",
|
"label.add-description": "添加描述",
|
||||||
"label.add-member": "添加成员",
|
"label.add-member": "添加成员",
|
||||||
"label.add-step": "Add step",
|
"label.add-step": "添加步骤",
|
||||||
"label.add-website": "添加网站",
|
"label.add-website": "添加网站",
|
||||||
"label.admin": "管理员",
|
"label.admin": "管理员",
|
||||||
"label.after": "之后",
|
"label.after": "之后",
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
"label.laptop": "笔记本",
|
"label.laptop": "笔记本",
|
||||||
"label.last-days": "最近 {x} 天",
|
"label.last-days": "最近 {x} 天",
|
||||||
"label.last-hours": "最近 {x} 小时",
|
"label.last-hours": "最近 {x} 小时",
|
||||||
"label.last-months": "Last {x} months",
|
"label.last-months": "最近 {x} 个月",
|
||||||
"label.leave": "离开",
|
"label.leave": "离开",
|
||||||
"label.leave-team": "离开团队",
|
"label.leave-team": "离开团队",
|
||||||
"label.less-than": "少于",
|
"label.less-than": "少于",
|
||||||
|
|
@ -152,7 +152,7 @@
|
||||||
"label.settings": "设置",
|
"label.settings": "设置",
|
||||||
"label.share-url": "共享链接",
|
"label.share-url": "共享链接",
|
||||||
"label.single-day": "单日",
|
"label.single-day": "单日",
|
||||||
"label.steps": "Steps",
|
"label.steps": "步骤",
|
||||||
"label.sum": "总和",
|
"label.sum": "总和",
|
||||||
"label.tablet": "平板",
|
"label.tablet": "平板",
|
||||||
"label.team": "团队",
|
"label.team": "团队",
|
||||||
|
|
@ -182,22 +182,22 @@
|
||||||
"label.unique-visitors": "独立访客",
|
"label.unique-visitors": "独立访客",
|
||||||
"label.unknown": "未知",
|
"label.unknown": "未知",
|
||||||
"label.untitled": "未命名",
|
"label.untitled": "未命名",
|
||||||
"label.update": "Update",
|
"label.update": "更新",
|
||||||
"label.url": "网址",
|
"label.url": "网址",
|
||||||
"label.urls": "网址",
|
"label.urls": "网址",
|
||||||
"label.user": "用户",
|
"label.user": "用户",
|
||||||
"label.username": "用户名",
|
"label.username": "用户名",
|
||||||
"label.users": "用户",
|
"label.users": "用户",
|
||||||
"label.utm": "UTM",
|
"label.utm": "UTM",
|
||||||
"label.utm-description": "Track your campaigns through UTM parameters.",
|
"label.utm-description": "通过UTM参数追踪您的广告活动。",
|
||||||
"label.value": "值",
|
"label.value": "值",
|
||||||
"label.view": "查看",
|
"label.view": "查看",
|
||||||
"label.view-details": "查看更多",
|
"label.view-details": "查看更多",
|
||||||
"label.view-only": "仅浏览量",
|
"label.view-only": "仅浏览量",
|
||||||
"label.views": "浏览量",
|
"label.views": "浏览量",
|
||||||
"label.views-per-visit": "Views per visit",
|
"label.views-per-visit": "每次访问的浏览量",
|
||||||
"label.visitors": "访客",
|
"label.visitors": "访客",
|
||||||
"label.visits": "Visits",
|
"label.visits": "访问次数",
|
||||||
"label.website": "网站",
|
"label.website": "网站",
|
||||||
"label.website-id": "网站 ID",
|
"label.website-id": "网站 ID",
|
||||||
"label.websites": "网站",
|
"label.websites": "网站",
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
import { User, Website } from '@prisma/client';
|
|
||||||
import redis from '@umami/redis-client';
|
|
||||||
import { getSession, getUser, getWebsite } from '../queries';
|
|
||||||
|
|
||||||
async function fetchWebsite(websiteId: string): Promise<Website> {
|
|
||||||
return redis.client.getCache(`website:${websiteId}`, () => getWebsite(websiteId), 86400);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function storeWebsite(data: { id: any }) {
|
|
||||||
const { id } = data;
|
|
||||||
const key = `website:${id}`;
|
|
||||||
|
|
||||||
const obj = await redis.client.setCache(key, data);
|
|
||||||
await redis.client.expire(key, 86400);
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteWebsite(id) {
|
|
||||||
return redis.client.deleteCache(`website:${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUser(id): Promise<User> {
|
|
||||||
return redis.client.getCache(`user:${id}`, () => getUser(id, { includePassword: true }), 86400);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function storeUser(data) {
|
|
||||||
const { id } = data;
|
|
||||||
const key = `user:${id}`;
|
|
||||||
|
|
||||||
const obj = await redis.client.setCache(key, data);
|
|
||||||
await redis.client.expire(key, 86400);
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteUser(id) {
|
|
||||||
return redis.client.deleteCache(`user:${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchSession(id) {
|
|
||||||
return redis.client.getCache(`session:${id}`, () => getSession(id), 86400);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function storeSession(data) {
|
|
||||||
const { id } = data;
|
|
||||||
const key = `session:${id}`;
|
|
||||||
|
|
||||||
const obj = await redis.client.setCache(key, data);
|
|
||||||
await redis.client.expire(key, 86400);
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteSession(id) {
|
|
||||||
return redis.client.deleteCache(`session:${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUserBlock(userId: string) {
|
|
||||||
const key = `user:block:${userId}`;
|
|
||||||
return redis.client.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function incrementUserBlock(userId: string) {
|
|
||||||
const key = `user:block:${userId}`;
|
|
||||||
return redis.client.incr(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fetchWebsite,
|
|
||||||
storeWebsite,
|
|
||||||
deleteWebsite,
|
|
||||||
fetchUser,
|
|
||||||
storeUser,
|
|
||||||
deleteUser,
|
|
||||||
fetchSession,
|
|
||||||
storeSession,
|
|
||||||
deleteSession,
|
|
||||||
fetchUserBlock,
|
|
||||||
incrementUserBlock,
|
|
||||||
enabled: !!redis.enabled,
|
|
||||||
};
|
|
||||||
|
|
@ -4,7 +4,7 @@ import debug from 'debug';
|
||||||
import { CLICKHOUSE } from 'lib/db';
|
import { CLICKHOUSE } from 'lib/db';
|
||||||
import { QueryFilters, QueryOptions } from './types';
|
import { QueryFilters, QueryOptions } from './types';
|
||||||
import { OPERATORS } from './constants';
|
import { OPERATORS } from './constants';
|
||||||
import { loadWebsite } from './load';
|
import { fetchWebsite } from './load';
|
||||||
import { maxDate } from './date';
|
import { maxDate } from './date';
|
||||||
import { filtersToArray } from './params';
|
import { filtersToArray } from './params';
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ function getFilterParams(filters: QueryFilters = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseFilters(websiteId: string, filters: QueryFilters = {}, options?: QueryOptions) {
|
async function parseFilters(websiteId: string, filters: QueryFilters = {}, options?: QueryOptions) {
|
||||||
const website = await loadWebsite(websiteId);
|
const website = await fetchWebsite(websiteId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filterQuery: getFilterQuery(filters, options),
|
filterQuery: getFilterQuery(filters, options),
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ export const FILTER_DAY = 'filter-day';
|
||||||
export const FILTER_RANGE = 'filter-range';
|
export const FILTER_RANGE = 'filter-range';
|
||||||
export const FILTER_REFERRERS = 'filter-referrers';
|
export const FILTER_REFERRERS = 'filter-referrers';
|
||||||
export const FILTER_PAGES = 'filter-pages';
|
export const FILTER_PAGES = 'filter-pages';
|
||||||
export const UNIT_TYPES = ['year', 'month', 'hour', 'day'];
|
|
||||||
|
export const UNIT_TYPES = ['year', 'month', 'hour', 'day', 'minute'];
|
||||||
export const EVENT_COLUMNS = ['url', 'referrer', 'title', 'query', 'event', 'host'];
|
export const EVENT_COLUMNS = ['url', 'referrer', 'title', 'query', 'event', 'host'];
|
||||||
|
|
||||||
export const SESSION_COLUMNS = [
|
export const SESSION_COLUMNS = [
|
||||||
|
|
@ -134,6 +135,7 @@ export const ROLES = {
|
||||||
user: 'user',
|
user: 'user',
|
||||||
viewOnly: 'view-only',
|
viewOnly: 'view-only',
|
||||||
teamOwner: 'team-owner',
|
teamOwner: 'team-owner',
|
||||||
|
teamManager: 'team-manager',
|
||||||
teamMember: 'team-member',
|
teamMember: 'team-member',
|
||||||
teamViewOnly: 'team-view-only',
|
teamViewOnly: 'team-view-only',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
@ -164,6 +166,12 @@ export const ROLE_PERMISSIONS = {
|
||||||
PERMISSIONS.websiteUpdate,
|
PERMISSIONS.websiteUpdate,
|
||||||
PERMISSIONS.websiteDelete,
|
PERMISSIONS.websiteDelete,
|
||||||
],
|
],
|
||||||
|
[ROLES.teamManager]: [
|
||||||
|
PERMISSIONS.teamUpdate,
|
||||||
|
PERMISSIONS.websiteCreate,
|
||||||
|
PERMISSIONS.websiteUpdate,
|
||||||
|
PERMISSIONS.websiteDelete,
|
||||||
|
],
|
||||||
[ROLES.teamMember]: [
|
[ROLES.teamMember]: [
|
||||||
PERMISSIONS.websiteCreate,
|
PERMISSIONS.websiteCreate,
|
||||||
PERMISSIONS.websiteUpdate,
|
PERMISSIONS.websiteUpdate,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { startOfHour, startOfMonth } from 'date-fns';
|
import { startOfHour, startOfMonth } from 'date-fns';
|
||||||
import { hash } from 'next-basics';
|
import { hash } from 'next-basics';
|
||||||
import { v4, v5, validate } from 'uuid';
|
import { v4, v5 } from 'uuid';
|
||||||
|
|
||||||
export function secret() {
|
export function secret() {
|
||||||
return hash(process.env.APP_SECRET || process.env.DATABASE_URL);
|
return hash(process.env.APP_SECRET || process.env.DATABASE_URL);
|
||||||
|
|
@ -23,7 +23,3 @@ export function uuid(...args: any) {
|
||||||
|
|
||||||
return v5(hash(...args, salt()), v5.DNS);
|
return v5(hash(...args, salt()), v5.DNS);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isUuid(value: string) {
|
|
||||||
return validate(value);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ export async function getClientInfo(req: NextApiRequestCollect) {
|
||||||
const subdivision2 = location?.subdivision2;
|
const subdivision2 = location?.subdivision2;
|
||||||
const city = location?.city;
|
const city = location?.city;
|
||||||
const browser = browserName(userAgent);
|
const browser = browserName(userAgent);
|
||||||
const os = detectOS(userAgent);
|
const os = detectOS(userAgent) as string;
|
||||||
const device = getDevice(req.body?.payload?.screen, os);
|
const device = getDevice(req.body?.payload?.screen, os);
|
||||||
|
|
||||||
return { userAgent, browser, os, ip, country, subdivision1, subdivision2, city, device };
|
return { userAgent, browser, os, ip, country, subdivision1, subdivision2, city, device };
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {
|
||||||
arSA,
|
arSA,
|
||||||
be,
|
be,
|
||||||
bn,
|
bn,
|
||||||
|
bs,
|
||||||
cs,
|
cs,
|
||||||
sk,
|
sk,
|
||||||
da,
|
da,
|
||||||
|
|
@ -48,6 +49,7 @@ export const languages = {
|
||||||
'ar-SA': { label: 'العربية', dateLocale: arSA, dir: 'rtl' },
|
'ar-SA': { label: 'العربية', dateLocale: arSA, dir: 'rtl' },
|
||||||
'be-BY': { label: 'Беларуская', dateLocale: be },
|
'be-BY': { label: 'Беларуская', dateLocale: be },
|
||||||
'bn-BD': { label: 'বাংলা', dateLocale: bn },
|
'bn-BD': { label: 'বাংলা', dateLocale: bn },
|
||||||
|
'bs-BA': { label: 'Bosanski', dateLocale: bs },
|
||||||
'ca-ES': { label: 'Català', dateLocale: ca },
|
'ca-ES': { label: 'Català', dateLocale: ca },
|
||||||
'cs-CZ': { label: 'Čeština', dateLocale: cs },
|
'cs-CZ': { label: 'Čeština', dateLocale: cs },
|
||||||
'da-DK': { label: 'Dansk', dateLocale: da },
|
'da-DK': { label: 'Dansk', dateLocale: da },
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import cache from 'lib/cache';
|
import { getSession, getWebsite } from 'queries';
|
||||||
import { getSession, getUser, getWebsite } from 'queries';
|
import { Website, Session } from '@prisma/client';
|
||||||
import { User, Website, Session } from '@prisma/client';
|
import redis from '@umami/redis-client';
|
||||||
|
|
||||||
export async function loadWebsite(websiteId: string): Promise<Website> {
|
export async function fetchWebsite(websiteId: string): Promise<Website> {
|
||||||
let website;
|
let website;
|
||||||
|
|
||||||
if (cache.enabled) {
|
if (redis.enabled) {
|
||||||
website = await cache.fetchWebsite(websiteId);
|
website = await redis.client.fetch(`website:${websiteId}`, () => getWebsite(websiteId), 86400);
|
||||||
} else {
|
} else {
|
||||||
website = await getWebsite(websiteId);
|
website = await getWebsite(websiteId);
|
||||||
}
|
}
|
||||||
|
|
@ -18,11 +18,11 @@ export async function loadWebsite(websiteId: string): Promise<Website> {
|
||||||
return website;
|
return website;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadSession(sessionId: string): Promise<Session> {
|
export async function fetchSession(sessionId: string): Promise<Session> {
|
||||||
let session;
|
let session;
|
||||||
|
|
||||||
if (cache.enabled) {
|
if (redis.enabled) {
|
||||||
session = await cache.fetchSession(sessionId);
|
session = await redis.client.fetch(`session:${sessionId}`, () => getSession(sessionId), 86400);
|
||||||
} else {
|
} else {
|
||||||
session = await getSession(sessionId);
|
session = await getSession(sessionId);
|
||||||
}
|
}
|
||||||
|
|
@ -33,19 +33,3 @@ export async function loadSession(sessionId: string): Promise<Session> {
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadUser(userId: string): Promise<User> {
|
|
||||||
let user;
|
|
||||||
|
|
||||||
if (cache.enabled) {
|
|
||||||
user = await cache.fetchUser(userId);
|
|
||||||
} else {
|
|
||||||
user = await getUser(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user || user.deletedAt) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,12 @@ import redis from '@umami/redis-client';
|
||||||
import { getAuthToken, parseShareToken } from 'lib/auth';
|
import { getAuthToken, parseShareToken } from 'lib/auth';
|
||||||
import { ROLES } from 'lib/constants';
|
import { ROLES } from 'lib/constants';
|
||||||
import { secret } from 'lib/crypto';
|
import { secret } from 'lib/crypto';
|
||||||
import { findSession } from 'lib/session';
|
import { getSession } from 'lib/session';
|
||||||
import {
|
import {
|
||||||
badRequest,
|
badRequest,
|
||||||
createMiddleware,
|
createMiddleware,
|
||||||
forbidden,
|
notFound,
|
||||||
parseSecureToken,
|
parseSecureToken,
|
||||||
tooManyRequest,
|
|
||||||
unauthorized,
|
unauthorized,
|
||||||
} from 'next-basics';
|
} from 'next-basics';
|
||||||
import { NextApiRequestCollect } from 'pages/api/send';
|
import { NextApiRequestCollect } from 'pages/api/send';
|
||||||
|
|
@ -27,7 +26,7 @@ export const useCors = createMiddleware(
|
||||||
|
|
||||||
export const useSession = createMiddleware(async (req, res, next) => {
|
export const useSession = createMiddleware(async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const session = await findSession(req as NextApiRequestCollect);
|
const session = await getSession(req as NextApiRequestCollect);
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
log('useSession: Session not found');
|
log('useSession: Session not found');
|
||||||
|
|
@ -36,11 +35,8 @@ export const useSession = createMiddleware(async (req, res, next) => {
|
||||||
|
|
||||||
(req as any).session = session;
|
(req as any).session = session;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.message === 'Usage Limit.') {
|
if (e.message.startsWith('Website not found')) {
|
||||||
return tooManyRequest(res, e.message);
|
return notFound(res, e.message);
|
||||||
}
|
|
||||||
if (e.message.startsWith('Website not found:')) {
|
|
||||||
return forbidden(res, e.message);
|
|
||||||
}
|
}
|
||||||
return badRequest(res, e.message);
|
return badRequest(res, e.message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import prisma from '@umami/prisma-client';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
import { MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db';
|
import { MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db';
|
||||||
import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE } from './constants';
|
import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE } from './constants';
|
||||||
import { loadWebsite } from './load';
|
import { fetchWebsite } from './load';
|
||||||
import { maxDate } from './date';
|
import { maxDate } from './date';
|
||||||
import { QueryFilters, QueryOptions, SearchFilter } from './types';
|
import { QueryFilters, QueryOptions, PageParams } from './types';
|
||||||
import { filtersToArray } from './params';
|
import { filtersToArray } from './params';
|
||||||
|
|
||||||
const MYSQL_DATE_FORMATS = {
|
const MYSQL_DATE_FORMATS = {
|
||||||
|
|
@ -152,7 +152,7 @@ async function parseFilters(
|
||||||
filters: QueryFilters = {},
|
filters: QueryFilters = {},
|
||||||
options: QueryOptions = {},
|
options: QueryOptions = {},
|
||||||
) {
|
) {
|
||||||
const website = await loadWebsite(websiteId);
|
const website = await fetchWebsite(websiteId);
|
||||||
const joinSession = Object.keys(filters).find(key => SESSION_COLUMNS.includes(key));
|
const joinSession = Object.keys(filters).find(key => SESSION_COLUMNS.includes(key));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -191,7 +191,7 @@ async function rawQuery(sql: string, data: object): Promise<any> {
|
||||||
return prisma.rawQuery(query, params);
|
return prisma.rawQuery(query, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pagedQuery<T>(model: string, criteria: T, filters: SearchFilter) {
|
async function pagedQuery<T>(model: string, criteria: T, filters: PageParams) {
|
||||||
const { page = 1, pageSize, orderBy, sortDescending = false } = filters || {};
|
const { page = 1, pageSize, orderBy, sortDescending = false } = filters || {};
|
||||||
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
const size = +pageSize || DEFAULT_PAGE_SIZE;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import * as yup from 'yup';
|
||||||
|
|
||||||
export const dateRange = {
|
export const dateRange = {
|
||||||
startAt: yup.number().integer().required(),
|
startAt: yup.number().integer().required(),
|
||||||
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
|
endAt: yup.number().integer().min(yup.ref('startAt')).required(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pageInfo = {
|
export const pageInfo = {
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,13 @@
|
||||||
import { isUuid, secret, uuid, visitSalt } from 'lib/crypto';
|
import { secret, uuid, visitSalt } from 'lib/crypto';
|
||||||
import { getClientInfo } from 'lib/detect';
|
import { getClientInfo } from 'lib/detect';
|
||||||
import { parseToken } from 'next-basics';
|
import { parseToken } from 'next-basics';
|
||||||
import { NextApiRequestCollect } from 'pages/api/send';
|
import { NextApiRequestCollect } from 'pages/api/send';
|
||||||
import { createSession } from 'queries';
|
import { createSession } from 'queries';
|
||||||
import cache from './cache';
|
|
||||||
import clickhouse from './clickhouse';
|
import clickhouse from './clickhouse';
|
||||||
import { loadSession, loadWebsite } from './load';
|
import { fetchSession, fetchWebsite } from './load';
|
||||||
|
import { SessionData } from 'lib/types';
|
||||||
|
|
||||||
export async function findSession(req: NextApiRequestCollect): Promise<{
|
export async function getSession(req: NextApiRequestCollect): Promise<SessionData> {
|
||||||
id: any;
|
|
||||||
websiteId: string;
|
|
||||||
visitId: string;
|
|
||||||
hostname: string;
|
|
||||||
browser: string;
|
|
||||||
os: any;
|
|
||||||
device: string;
|
|
||||||
screen: string;
|
|
||||||
language: string;
|
|
||||||
country: any;
|
|
||||||
subdivision1: any;
|
|
||||||
subdivision2: any;
|
|
||||||
city: any;
|
|
||||||
ownerId: string;
|
|
||||||
}> {
|
|
||||||
const { payload } = req.body;
|
const { payload } = req.body;
|
||||||
|
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
|
@ -35,9 +20,8 @@ export async function findSession(req: NextApiRequestCollect): Promise<{
|
||||||
if (cacheToken) {
|
if (cacheToken) {
|
||||||
const result = await parseToken(cacheToken, secret());
|
const result = await parseToken(cacheToken, secret());
|
||||||
|
|
||||||
|
// Token is valid
|
||||||
if (result) {
|
if (result) {
|
||||||
await checkUserBlock(result?.ownerId);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,25 +29,13 @@ export async function findSession(req: NextApiRequestCollect): Promise<{
|
||||||
// Verify payload
|
// Verify payload
|
||||||
const { website: websiteId, hostname, screen, language } = payload;
|
const { website: websiteId, hostname, screen, language } = payload;
|
||||||
|
|
||||||
// Check the hostname value for legality to eliminate dirty data
|
|
||||||
const validHostnameRegex = /^[\w-.]+$/;
|
|
||||||
if (!validHostnameRegex.test(hostname)) {
|
|
||||||
throw new Error('Invalid hostname.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isUuid(websiteId)) {
|
|
||||||
throw new Error('Invalid website ID.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find website
|
// Find website
|
||||||
const website = await loadWebsite(websiteId);
|
const website = await fetchWebsite(websiteId);
|
||||||
|
|
||||||
if (!website) {
|
if (!website) {
|
||||||
throw new Error(`Website not found: ${websiteId}.`);
|
throw new Error(`Website not found: ${websiteId}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkUserBlock(website.userId);
|
|
||||||
|
|
||||||
const { userAgent, browser, os, ip, country, subdivision1, subdivision2, city, device } =
|
const { userAgent, browser, os, ip, country, subdivision1, subdivision2, city, device } =
|
||||||
await getClientInfo(req);
|
await getClientInfo(req);
|
||||||
|
|
||||||
|
|
@ -78,7 +50,7 @@ export async function findSession(req: NextApiRequestCollect): Promise<{
|
||||||
visitId,
|
visitId,
|
||||||
hostname,
|
hostname,
|
||||||
browser,
|
browser,
|
||||||
os: os as any,
|
os,
|
||||||
device,
|
device,
|
||||||
screen,
|
screen,
|
||||||
language,
|
language,
|
||||||
|
|
@ -86,12 +58,11 @@ export async function findSession(req: NextApiRequestCollect): Promise<{
|
||||||
subdivision1,
|
subdivision1,
|
||||||
subdivision2,
|
subdivision2,
|
||||||
city,
|
city,
|
||||||
ownerId: website.userId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find session
|
// Find session
|
||||||
let session = await loadSession(sessionId);
|
let session = await fetchSession(sessionId);
|
||||||
|
|
||||||
// Create a session if not found
|
// Create a session if not found
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|
@ -117,13 +88,5 @@ export async function findSession(req: NextApiRequestCollect): Promise<{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...session, ownerId: website.userId, visitId: visitId };
|
return { ...session, visitId: visitId };
|
||||||
}
|
|
||||||
|
|
||||||
async function checkUserBlock(userId: string) {
|
|
||||||
if (process.env.ENABLE_BLOCKER && (await cache.fetchUserBlock(userId))) {
|
|
||||||
await cache.incrementUserBlock(userId);
|
|
||||||
|
|
||||||
throw new Error('Usage Limit.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,31 +24,7 @@ export type DynamicDataType = ObjectValues<typeof DATA_TYPE>;
|
||||||
export type KafkaTopic = ObjectValues<typeof KAFKA_TOPIC>;
|
export type KafkaTopic = ObjectValues<typeof KAFKA_TOPIC>;
|
||||||
export type ReportType = ObjectValues<typeof REPORT_TYPES>;
|
export type ReportType = ObjectValues<typeof REPORT_TYPES>;
|
||||||
|
|
||||||
export interface WebsiteSearchFilter extends SearchFilter {
|
export interface PageParams {
|
||||||
userId?: string;
|
|
||||||
teamId?: string;
|
|
||||||
includeTeams?: boolean;
|
|
||||||
onlyTeams?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserSearchFilter extends SearchFilter {
|
|
||||||
teamId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TeamSearchFilter extends SearchFilter {
|
|
||||||
userId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TeamUserSearchFilter extends SearchFilter {
|
|
||||||
teamId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReportSearchFilter extends SearchFilter {
|
|
||||||
userId?: string;
|
|
||||||
websiteId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchFilter {
|
|
||||||
query?: string;
|
query?: string;
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
|
@ -56,7 +32,7 @@ export interface SearchFilter {
|
||||||
sortDescending?: boolean;
|
sortDescending?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FilterResult<T> {
|
export interface PageResult<T> {
|
||||||
data: T;
|
data: T;
|
||||||
count: number;
|
count: number;
|
||||||
page: number;
|
page: number;
|
||||||
|
|
@ -66,10 +42,10 @@ export interface FilterResult<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FilterQueryResult<T> {
|
export interface FilterQueryResult<T> {
|
||||||
result: FilterResult<T>;
|
result: PageResult<T>;
|
||||||
query: any;
|
query: any;
|
||||||
params: SearchFilter;
|
params: PageParams;
|
||||||
setParams: Dispatch<SetStateAction<T | SearchFilter>>;
|
setParams: Dispatch<SetStateAction<T | PageParams>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DynamicData {
|
export interface DynamicData {
|
||||||
|
|
@ -230,3 +206,19 @@ export interface RealtimeData {
|
||||||
countries?: any[];
|
countries?: any[];
|
||||||
visitors?: any[];
|
visitors?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SessionData {
|
||||||
|
id: string;
|
||||||
|
websiteId: string;
|
||||||
|
visitId: string;
|
||||||
|
hostname: string;
|
||||||
|
browser: string;
|
||||||
|
os: string;
|
||||||
|
device: string;
|
||||||
|
screen: string;
|
||||||
|
language: string;
|
||||||
|
country: string;
|
||||||
|
subdivision1: string;
|
||||||
|
subdivision2: string;
|
||||||
|
city: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { canViewUsers } from 'lib/auth';
|
import { canViewUsers } from 'lib/auth';
|
||||||
import { useAuth, useValidate } from 'lib/middleware';
|
import { useAuth, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, Role, SearchFilter, User } from 'lib/types';
|
import { NextApiRequestQueryBody, Role, PageParams, User } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getUsers } from 'queries';
|
import { getUsers } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface UsersRequestQuery extends SearchFilter {}
|
export interface UsersRequestQuery extends PageParams {}
|
||||||
export interface UsersRequestBody {
|
export interface UsersRequestBody {
|
||||||
userId: string;
|
userId: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
import { canViewAllWebsites } from 'lib/auth';
|
import { canViewAllWebsites } from 'lib/auth';
|
||||||
|
import { ROLES } from 'lib/constants';
|
||||||
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { pageInfo } from 'lib/schema';
|
||||||
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getWebsites } from 'queries';
|
import { getWebsites } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import { pageInfo } from 'lib/schema';
|
|
||||||
|
|
||||||
export interface WebsitesRequestQuery extends SearchFilter {}
|
export interface WebsitesRequestQuery extends PageParams {
|
||||||
|
userId?: string;
|
||||||
|
includeTeams?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WebsitesRequestBody {
|
export interface WebsitesRequestBody {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -39,8 +43,29 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { userId, includeOwnedTeams } = req.query;
|
||||||
|
|
||||||
const websites = await getWebsites(
|
const websites = await getWebsites(
|
||||||
{
|
{
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
...(userId && [{ userId }]),
|
||||||
|
...(userId &&
|
||||||
|
includeOwnedTeams && [
|
||||||
|
{
|
||||||
|
team: {
|
||||||
|
deletedAt: null,
|
||||||
|
teamUser: {
|
||||||
|
some: {
|
||||||
|
role: ROLES.teamOwner,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
|
|
@ -48,6 +73,18 @@ export default async (
|
||||||
id: true,
|
id: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
team: {
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
teamUser: {
|
||||||
|
where: {
|
||||||
|
role: ROLES.teamOwner,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
req.query,
|
req.query,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const schema = {
|
||||||
GET: yup.object().shape({
|
GET: yup.object().shape({
|
||||||
websiteId: yup.string().uuid().required(),
|
websiteId: yup.string().uuid().required(),
|
||||||
startAt: yup.number().integer().required(),
|
startAt: yup.number().integer().required(),
|
||||||
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
|
endAt: yup.number().integer().min(yup.ref('startAt')).required(),
|
||||||
event: yup.string(),
|
event: yup.string(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const schema = {
|
||||||
GET: yup.object().shape({
|
GET: yup.object().shape({
|
||||||
websiteId: yup.string().uuid().required(),
|
websiteId: yup.string().uuid().required(),
|
||||||
startAt: yup.number().integer().required(),
|
startAt: yup.number().integer().required(),
|
||||||
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
|
endAt: yup.number().integer().min(yup.ref('startAt')).required(),
|
||||||
field: yup.string(),
|
field: yup.string(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const schema = {
|
||||||
GET: yup.object().shape({
|
GET: yup.object().shape({
|
||||||
websiteId: yup.string().uuid().required(),
|
websiteId: yup.string().uuid().required(),
|
||||||
startAt: yup.number().integer().required(),
|
startAt: yup.number().integer().required(),
|
||||||
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
|
endAt: yup.number().integer().min(yup.ref('startAt')).required(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
import ipaddr from 'ipaddr.js';
|
import ipaddr from 'ipaddr.js';
|
||||||
import { isbot } from 'isbot';
|
import { isbot } from 'isbot';
|
||||||
import { COLLECTION_TYPE, HOSTNAME_REGEX, IP_REGEX } from 'lib/constants';
|
|
||||||
import { secret, visitSalt, uuid } from 'lib/crypto';
|
|
||||||
import { getIpAddress } from 'lib/detect';
|
|
||||||
import { useCors, useSession, useValidate } from 'lib/middleware';
|
|
||||||
import { CollectionType, YupRequest } from 'lib/types';
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import {
|
import {
|
||||||
badRequest,
|
badRequest,
|
||||||
|
|
@ -15,6 +10,11 @@ import {
|
||||||
safeDecodeURI,
|
safeDecodeURI,
|
||||||
send,
|
send,
|
||||||
} from 'next-basics';
|
} from 'next-basics';
|
||||||
|
import { COLLECTION_TYPE, HOSTNAME_REGEX, IP_REGEX } from 'lib/constants';
|
||||||
|
import { secret, visitSalt, uuid } from 'lib/crypto';
|
||||||
|
import { getIpAddress } from 'lib/detect';
|
||||||
|
import { useCors, useSession, useValidate } from 'lib/middleware';
|
||||||
|
import { CollectionType, YupRequest } from 'lib/types';
|
||||||
import { saveEvent, saveSessionData } from 'queries';
|
import { saveEvent, saveSessionData } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
|
@ -41,7 +41,6 @@ export interface NextApiRequestCollect extends NextApiRequest {
|
||||||
id: string;
|
id: string;
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
visitId: string;
|
visitId: string;
|
||||||
ownerId: string;
|
|
||||||
hostname: string;
|
hostname: string;
|
||||||
browser: string;
|
browser: string;
|
||||||
os: string;
|
os: string;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const schema = {
|
||||||
POST: yup.object().shape({
|
POST: yup.object().shape({
|
||||||
role: yup
|
role: yup
|
||||||
.string()
|
.string()
|
||||||
.matches(/team-member|team-view-only/i)
|
.matches(/team-member|team-view-only|team-manager/i)
|
||||||
.required(),
|
.required(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { canAddUserToTeam, canViewTeam } from 'lib/auth';
|
import { canAddUserToTeam, canViewTeam } from 'lib/auth';
|
||||||
import { useAuth, useValidate } from 'lib/middleware';
|
import { useAuth, useValidate } from 'lib/middleware';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { createTeamUser, getTeamUser, getTeamUsers } from 'queries';
|
import { createTeamUser, getTeamUser, getTeamUsers } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface TeamUserRequestQuery extends SearchFilter {
|
export interface TeamUserRequestQuery extends PageParams {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ const schema = {
|
||||||
userId: yup.string().uuid().required(),
|
userId: yup.string().uuid().required(),
|
||||||
role: yup
|
role: yup
|
||||||
.string()
|
.string()
|
||||||
.matches(/team-member|team-view-only/i)
|
.matches(/team-member|team-view-only|team-manager/i)
|
||||||
.required(),
|
.required(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import { canViewTeam } from 'lib/auth';
|
import { canViewTeam } from 'lib/auth';
|
||||||
import { useAuth, useValidate } from 'lib/middleware';
|
import { useAuth, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { ok, unauthorized } from 'next-basics';
|
import { ok, unauthorized } from 'next-basics';
|
||||||
import { getTeamWebsites } from 'queries';
|
import { getTeamWebsites } from 'queries';
|
||||||
|
|
||||||
export interface TeamWebsiteRequestQuery extends SearchFilter {
|
export interface TeamWebsiteRequestQuery extends PageParams {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ import { Team } from '@prisma/client';
|
||||||
import { canCreateTeam } from 'lib/auth';
|
import { canCreateTeam } from 'lib/auth';
|
||||||
import { uuid } from 'lib/crypto';
|
import { uuid } from 'lib/crypto';
|
||||||
import { useAuth, useValidate } from 'lib/middleware';
|
import { useAuth, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { createTeam } from 'queries';
|
import { createTeam } from 'queries';
|
||||||
|
|
||||||
export interface TeamsRequestQuery extends SearchFilter {}
|
export interface TeamsRequestQuery extends PageParams {}
|
||||||
export interface TeamsRequestBody {
|
export interface TeamsRequestBody {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getUserTeams } from 'queries';
|
import { getUserTeams } from 'queries';
|
||||||
|
|
||||||
export interface UserTeamsRequestQuery extends SearchFilter {
|
export interface UserTeamsRequestQuery extends PageParams {
|
||||||
userId: string;
|
userId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ const schema = {
|
||||||
GET: yup.object().shape({
|
GET: yup.object().shape({
|
||||||
id: yup.string().uuid().required(),
|
id: yup.string().uuid().required(),
|
||||||
startAt: yup.number().integer().required(),
|
startAt: yup.number().integer().required(),
|
||||||
endAt: yup.number().integer().moreThan(yup.ref<number>('startAt')).required(),
|
endAt: yup.number().integer().min(yup.ref<number>('startAt')).required(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ import { canCreateUser } from 'lib/auth';
|
||||||
import { ROLES } from 'lib/constants';
|
import { ROLES } from 'lib/constants';
|
||||||
import { uuid } from 'lib/crypto';
|
import { uuid } from 'lib/crypto';
|
||||||
import { useAuth, useValidate } from 'lib/middleware';
|
import { useAuth, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, Role, SearchFilter, User } from 'lib/types';
|
import { NextApiRequestQueryBody, Role, PageParams, User } from 'lib/types';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { createUser, getUserByUsername } from 'queries';
|
import { createUser, getUserByUsername } from 'queries';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
export interface UsersRequestQuery extends SearchFilter {}
|
export interface UsersRequestQuery extends PageParams {}
|
||||||
export interface UsersRequestBody {
|
export interface UsersRequestBody {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { canViewWebsite } from 'lib/auth';
|
import { canViewWebsite } from 'lib/auth';
|
||||||
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { getRequestDateRange } from 'lib/request';
|
import { getRequestFilters, getRequestDateRange } from 'lib/request';
|
||||||
import { NextApiRequestQueryBody, WebsiteMetric } from 'lib/types';
|
import { NextApiRequestQueryBody, WebsiteMetric } from 'lib/types';
|
||||||
import { TimezoneTest, UnitTypeTest } from 'lib/yup';
|
import { TimezoneTest, UnitTypeTest } from 'lib/yup';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
|
|
@ -15,16 +15,32 @@ export interface WebsiteEventsRequestQuery {
|
||||||
unit?: string;
|
unit?: string;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
referrer?: string;
|
||||||
|
title?: string;
|
||||||
|
os?: string;
|
||||||
|
browser?: string;
|
||||||
|
device?: string;
|
||||||
|
country?: string;
|
||||||
|
region: string;
|
||||||
|
city?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
GET: yup.object().shape({
|
GET: yup.object().shape({
|
||||||
websiteId: yup.string().uuid().required(),
|
websiteId: yup.string().uuid().required(),
|
||||||
startAt: yup.number().integer().required(),
|
startAt: yup.number().integer().required(),
|
||||||
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
|
endAt: yup.number().integer().min(yup.ref('startAt')).required(),
|
||||||
unit: UnitTypeTest,
|
unit: UnitTypeTest,
|
||||||
timezone: TimezoneTest,
|
timezone: TimezoneTest,
|
||||||
url: yup.string(),
|
url: yup.string(),
|
||||||
|
referrer: yup.string(),
|
||||||
|
title: yup.string(),
|
||||||
|
os: yup.string(),
|
||||||
|
browser: yup.string(),
|
||||||
|
device: yup.string(),
|
||||||
|
country: yup.string(),
|
||||||
|
region: yup.string(),
|
||||||
|
city: yup.string(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -36,7 +52,7 @@ export default async (
|
||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
await useValidate(schema, req, res);
|
await useValidate(schema, req, res);
|
||||||
|
|
||||||
const { websiteId, timezone, url } = req.query;
|
const { websiteId, timezone } = req.query;
|
||||||
const { startDate, endDate, unit } = await getRequestDateRange(req);
|
const { startDate, endDate, unit } = await getRequestDateRange(req);
|
||||||
|
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
|
|
@ -44,13 +60,15 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = await getEventMetrics(websiteId, {
|
const filters = {
|
||||||
|
...getRequestFilters(req),
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
timezone,
|
timezone,
|
||||||
unit,
|
unit,
|
||||||
url,
|
};
|
||||||
});
|
|
||||||
|
const events = await getEventMetrics(websiteId, filters);
|
||||||
|
|
||||||
return ok(res, events);
|
return ok(res, events);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import { canViewWebsite } from 'lib/auth';
|
import { canViewWebsite } from 'lib/auth';
|
||||||
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { getWebsiteReports } from 'queries';
|
import { getWebsiteReports } from 'queries';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
|
|
||||||
export interface ReportsRequestQuery extends SearchFilter {
|
export interface ReportsRequestQuery extends PageParams {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { canCreateTeamWebsite, canCreateWebsite } from 'lib/auth';
|
import { canCreateTeamWebsite, canCreateWebsite } from 'lib/auth';
|
||||||
import { uuid } from 'lib/crypto';
|
import { uuid } from 'lib/crypto';
|
||||||
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
import { useAuth, useCors, useValidate } from 'lib/middleware';
|
||||||
import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
|
import { NextApiRequestQueryBody, PageParams } from 'lib/types';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||||
import { createWebsite } from 'queries';
|
import { createWebsite } from 'queries';
|
||||||
|
|
@ -9,7 +9,7 @@ import userWebsitesRoute from 'pages/api/users/[userId]/websites';
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
import { pageInfo } from 'lib/schema';
|
import { pageInfo } from 'lib/schema';
|
||||||
|
|
||||||
export interface WebsitesRequestQuery extends SearchFilter {}
|
export interface WebsitesRequestQuery extends PageParams {}
|
||||||
|
|
||||||
export interface WebsitesRequestBody {
|
export interface WebsitesRequestBody {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Prisma, Report } from '@prisma/client';
|
import { Prisma, Report } from '@prisma/client';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, ReportSearchFilter } from 'lib/types';
|
import { PageResult, PageParams } from 'lib/types';
|
||||||
import ReportFindManyArgs = Prisma.ReportFindManyArgs;
|
import ReportFindManyArgs = Prisma.ReportFindManyArgs;
|
||||||
|
|
||||||
async function findReport(criteria: Prisma.ReportFindUniqueArgs): Promise<Report> {
|
async function findReport(criteria: Prisma.ReportFindUniqueArgs): Promise<Report> {
|
||||||
|
|
@ -17,8 +17,8 @@ export async function getReport(reportId: string): Promise<Report> {
|
||||||
|
|
||||||
export async function getReports(
|
export async function getReports(
|
||||||
criteria: ReportFindManyArgs,
|
criteria: ReportFindManyArgs,
|
||||||
filters: ReportSearchFilter = {},
|
filters: PageParams = {},
|
||||||
): Promise<FilterResult<Report[]>> {
|
): Promise<PageResult<Report[]>> {
|
||||||
const { query } = filters;
|
const { query } = filters;
|
||||||
|
|
||||||
const where: Prisma.ReportWhereInput = {
|
const where: Prisma.ReportWhereInput = {
|
||||||
|
|
@ -50,8 +50,8 @@ export async function getReports(
|
||||||
|
|
||||||
export async function getUserReports(
|
export async function getUserReports(
|
||||||
userId: string,
|
userId: string,
|
||||||
filters?: ReportSearchFilter,
|
filters?: PageParams,
|
||||||
): Promise<FilterResult<Report[]>> {
|
): Promise<PageResult<Report[]>> {
|
||||||
return getReports(
|
return getReports(
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -72,8 +72,8 @@ export async function getUserReports(
|
||||||
|
|
||||||
export async function getWebsiteReports(
|
export async function getWebsiteReports(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: ReportSearchFilter = {},
|
filters: PageParams = {},
|
||||||
): Promise<FilterResult<Report[]>> {
|
): Promise<PageResult<Report[]>> {
|
||||||
return getReports(
|
return getReports(
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Prisma, Team } from '@prisma/client';
|
||||||
import { ROLES } from 'lib/constants';
|
import { ROLES } from 'lib/constants';
|
||||||
import { uuid } from 'lib/crypto';
|
import { uuid } from 'lib/crypto';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, TeamSearchFilter } from 'lib/types';
|
import { PageResult, PageParams } from 'lib/types';
|
||||||
import TeamFindManyArgs = Prisma.TeamFindManyArgs;
|
import TeamFindManyArgs = Prisma.TeamFindManyArgs;
|
||||||
|
|
||||||
export async function findTeam(criteria: Prisma.TeamFindUniqueArgs): Promise<Team> {
|
export async function findTeam(criteria: Prisma.TeamFindUniqueArgs): Promise<Team> {
|
||||||
|
|
@ -22,8 +22,8 @@ export async function getTeam(teamId: string, options: { includeMembers?: boolea
|
||||||
|
|
||||||
export async function getTeams(
|
export async function getTeams(
|
||||||
criteria: TeamFindManyArgs,
|
criteria: TeamFindManyArgs,
|
||||||
filters: TeamSearchFilter = {},
|
filters: PageParams = {},
|
||||||
): Promise<FilterResult<Team[]>> {
|
): Promise<PageResult<Team[]>> {
|
||||||
const { getSearchParameters } = prisma;
|
const { getSearchParameters } = prisma;
|
||||||
const { query } = filters;
|
const { query } = filters;
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ export async function getTeams(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserTeams(userId: string, filters: TeamSearchFilter = {}) {
|
export async function getUserTeams(userId: string, filters: PageParams = {}) {
|
||||||
return getTeams(
|
return getTeams(
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Prisma, TeamUser } from '@prisma/client';
|
import { Prisma, TeamUser } from '@prisma/client';
|
||||||
import { uuid } from 'lib/crypto';
|
import { uuid } from 'lib/crypto';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, TeamUserSearchFilter } from 'lib/types';
|
import { PageResult, PageParams } from 'lib/types';
|
||||||
import TeamUserFindManyArgs = Prisma.TeamUserFindManyArgs;
|
import TeamUserFindManyArgs = Prisma.TeamUserFindManyArgs;
|
||||||
|
|
||||||
export async function findTeamUser(criteria: Prisma.TeamUserFindUniqueArgs): Promise<TeamUser> {
|
export async function findTeamUser(criteria: Prisma.TeamUserFindUniqueArgs): Promise<TeamUser> {
|
||||||
|
|
@ -19,8 +19,8 @@ export async function getTeamUser(teamId: string, userId: string): Promise<TeamU
|
||||||
|
|
||||||
export async function getTeamUsers(
|
export async function getTeamUsers(
|
||||||
criteria: TeamUserFindManyArgs,
|
criteria: TeamUserFindManyArgs,
|
||||||
filters?: TeamUserSearchFilter,
|
filters?: PageParams,
|
||||||
): Promise<FilterResult<TeamUser[]>> {
|
): Promise<PageResult<TeamUser[]>> {
|
||||||
const { query } = filters;
|
const { query } = filters;
|
||||||
|
|
||||||
const where: Prisma.TeamUserWhereInput = {
|
const where: Prisma.TeamUserWhereInput = {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import cache from 'lib/cache';
|
|
||||||
import { ROLES } from 'lib/constants';
|
import { ROLES } from 'lib/constants';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, Role, User, UserSearchFilter } from 'lib/types';
|
import { PageResult, Role, User, PageParams } from 'lib/types';
|
||||||
import { getRandomChars } from 'next-basics';
|
import { getRandomChars } from 'next-basics';
|
||||||
import UserFindManyArgs = Prisma.UserFindManyArgs;
|
import UserFindManyArgs = Prisma.UserFindManyArgs;
|
||||||
|
|
||||||
|
|
@ -50,8 +49,8 @@ export async function getUserByUsername(username: string, options: GetUserOption
|
||||||
|
|
||||||
export async function getUsers(
|
export async function getUsers(
|
||||||
criteria: UserFindManyArgs,
|
criteria: UserFindManyArgs,
|
||||||
filters?: UserSearchFilter,
|
filters?: PageParams,
|
||||||
): Promise<FilterResult<User[]>> {
|
): Promise<PageResult<User[]>> {
|
||||||
const { query } = filters;
|
const { query } = filters;
|
||||||
|
|
||||||
const where: Prisma.UserWhereInput = {
|
const where: Prisma.UserWhereInput = {
|
||||||
|
|
@ -221,15 +220,5 @@ export async function deleteUser(
|
||||||
id: userId,
|
id: userId,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]).then(async data => {
|
]);
|
||||||
if (cache.enabled) {
|
|
||||||
const ids = websites.map(a => a.id);
|
|
||||||
|
|
||||||
for (let i = 0; i < ids.length; i++) {
|
|
||||||
await cache.deleteWebsite(`website:${ids[i]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Prisma, Website } from '@prisma/client';
|
import { Prisma, Website } from '@prisma/client';
|
||||||
import cache from 'lib/cache';
|
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import { FilterResult, WebsiteSearchFilter } from 'lib/types';
|
import { PageResult, PageParams } from 'lib/types';
|
||||||
import WebsiteFindManyArgs = Prisma.WebsiteFindManyArgs;
|
import WebsiteFindManyArgs = Prisma.WebsiteFindManyArgs;
|
||||||
|
|
||||||
async function findWebsite(criteria: Prisma.WebsiteFindUniqueArgs): Promise<Website> {
|
async function findWebsite(criteria: Prisma.WebsiteFindUniqueArgs): Promise<Website> {
|
||||||
|
|
@ -26,8 +25,8 @@ export async function getSharedWebsite(shareId: string) {
|
||||||
|
|
||||||
export async function getWebsites(
|
export async function getWebsites(
|
||||||
criteria: WebsiteFindManyArgs,
|
criteria: WebsiteFindManyArgs,
|
||||||
filters: WebsiteSearchFilter,
|
filters: PageParams,
|
||||||
): Promise<FilterResult<Website[]>> {
|
): Promise<PageResult<Website[]>> {
|
||||||
const { query } = filters;
|
const { query } = filters;
|
||||||
|
|
||||||
const where: Prisma.WebsiteWhereInput = {
|
const where: Prisma.WebsiteWhereInput = {
|
||||||
|
|
@ -54,8 +53,8 @@ export async function getAllWebsites(userId: string) {
|
||||||
|
|
||||||
export async function getUserWebsites(
|
export async function getUserWebsites(
|
||||||
userId: string,
|
userId: string,
|
||||||
filters?: WebsiteSearchFilter,
|
filters?: PageParams,
|
||||||
): Promise<FilterResult<Website[]>> {
|
): Promise<PageResult<Website[]>> {
|
||||||
return getWebsites(
|
return getWebsites(
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -79,8 +78,8 @@ export async function getUserWebsites(
|
||||||
|
|
||||||
export async function getTeamWebsites(
|
export async function getTeamWebsites(
|
||||||
teamId: string,
|
teamId: string,
|
||||||
filters?: WebsiteSearchFilter,
|
filters?: PageParams,
|
||||||
): Promise<FilterResult<Website[]>> {
|
): Promise<PageResult<Website[]>> {
|
||||||
return getWebsites(
|
return getWebsites(
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -102,17 +101,9 @@ export async function getTeamWebsites(
|
||||||
export async function createWebsite(
|
export async function createWebsite(
|
||||||
data: Prisma.WebsiteCreateInput | Prisma.WebsiteUncheckedCreateInput,
|
data: Prisma.WebsiteCreateInput | Prisma.WebsiteUncheckedCreateInput,
|
||||||
): Promise<Website> {
|
): Promise<Website> {
|
||||||
return prisma.client.website
|
return prisma.client.website.create({
|
||||||
.create({
|
data,
|
||||||
data,
|
});
|
||||||
})
|
|
||||||
.then(async data => {
|
|
||||||
if (cache.enabled) {
|
|
||||||
await cache.storeWebsite(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateWebsite(
|
export async function updateWebsite(
|
||||||
|
|
@ -148,13 +139,7 @@ export async function resetWebsite(
|
||||||
resetAt: new Date(),
|
resetAt: new Date(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]).then(async data => {
|
]);
|
||||||
if (cache.enabled) {
|
|
||||||
await cache.storeWebsite(data[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteWebsite(
|
export async function deleteWebsite(
|
||||||
|
|
@ -188,11 +173,5 @@ export async function deleteWebsite(
|
||||||
: client.website.delete({
|
: client.website.delete({
|
||||||
where: { id: websiteId },
|
where: { id: websiteId },
|
||||||
}),
|
}),
|
||||||
]).then(async data => {
|
]);
|
||||||
if (cache.enabled) {
|
|
||||||
await cache.deleteWebsite(websiteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
and event_data.created_at between {{startDate}} and {{endDate}}
|
and event_data.created_at between {{startDate}} and {{endDate}}
|
||||||
and website_event.event_name = {{event}}
|
and website_event.event_name = {{event}}
|
||||||
group by website_event.event_name, event_data.data_key, event_data.data_type, event_data.string_value
|
group by website_event.event_name, event_data.data_key, event_data.data_type, event_data.string_value
|
||||||
order by 1 asc, 2 asc, 3 asc, 4 desc
|
order by 1 asc, 2 asc, 3 asc, 5 desc
|
||||||
`,
|
`,
|
||||||
params,
|
params,
|
||||||
);
|
);
|
||||||
|
|
@ -81,7 +81,7 @@ async function clickhouseQuery(
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_name = {event:String}
|
and event_name = {event:String}
|
||||||
group by data_key, data_type, string_value, event_name
|
group by data_key, data_type, string_value, event_name
|
||||||
order by 1 asc, 2 asc, 3 asc, 4 desc
|
order by 1 asc, 2 asc, 3 asc, 5 desc
|
||||||
limit 500
|
limit 500
|
||||||
`,
|
`,
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
||||||
`
|
`
|
||||||
select
|
select
|
||||||
event_name x,
|
event_name x,
|
||||||
${getDateQuery('created_at', unit, timezone)} t,
|
${getDateQuery('website_event.created_at', unit, timezone)} t,
|
||||||
count(*) y
|
count(*) y
|
||||||
from website_event
|
from website_event
|
||||||
${joinSession}
|
${joinSession}
|
||||||
where website_id = {{websiteId::uuid}}
|
where website_event.website_id = {{websiteId::uuid}}
|
||||||
and created_at between {{startDate}} and {{endDate}}
|
and website_event.created_at between {{startDate}} and {{endDate}}
|
||||||
and event_type = {{eventType}}
|
and event_type = {{eventType}}
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
group by 1, 2
|
group by 1, 2
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import cache from 'lib/cache';
|
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
|
|
||||||
export async function createSession(data: Prisma.SessionCreateInput) {
|
export async function createSession(data: Prisma.SessionCreateInput) {
|
||||||
|
|
@ -18,28 +17,20 @@ export async function createSession(data: Prisma.SessionCreateInput) {
|
||||||
city,
|
city,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
return prisma.client.session
|
return prisma.client.session.create({
|
||||||
.create({
|
data: {
|
||||||
data: {
|
id,
|
||||||
id,
|
websiteId,
|
||||||
websiteId,
|
hostname,
|
||||||
hostname,
|
browser,
|
||||||
browser,
|
os,
|
||||||
os,
|
device,
|
||||||
device,
|
screen,
|
||||||
screen,
|
language,
|
||||||
language,
|
country,
|
||||||
country,
|
subdivision1,
|
||||||
subdivision1,
|
subdivision2,
|
||||||
subdivision2,
|
city,
|
||||||
city,
|
},
|
||||||
},
|
});
|
||||||
})
|
|
||||||
.then(async data => {
|
|
||||||
if (cache.enabled) {
|
|
||||||
await cache.storeSession(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue