tacilytics 2.0.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/i18n.js ADDED
@@ -0,0 +1,242 @@
1
+ /* Tacilytics — dashboard translations.
2
+ The UI is written in English. When German is active, every text node and every
3
+ placeholder / title / aria-label that appears in the DOM is looked up in a dictionary
4
+ (exact match, then a few patterns for strings with numbers or names in them).
5
+ A MutationObserver covers everything rendered later, so new code needs no t() calls.
6
+ Unknown strings stay English; with ?i18n=debug window.__taciMissing collects them (for translators).
7
+ Language: Settings → Security → Language (auto = browser language), per browser. */
8
+ (function () {
9
+ var DE = {
10
+ // header / toolbar
11
+ 'Settings': 'Einstellungen', 'Sign out': 'Abmelden', 'Add site': 'Seite hinzufügen', 'online now': 'gerade online', 'online': 'online',
12
+ 'View live visitors': 'Live-Besucher ansehen', 'Account, security and team': 'Konto, Sicherheit und Team',
13
+ "Cookieless analytics · we count, we don't tell": 'Analyse ohne Cookies · wir zählen, wir verraten nichts',
14
+ 'Today': 'Heute', '7 days': '7 Tage', '30 days': '30 Tage', '90 days': '90 Tage', '12 mo': '12 Mon.',
15
+ 'From': 'Von', 'To': 'Bis', 'Apply': 'Anwenden', 'CSV': 'CSV', 'Share': 'Teilen', 'Embed code': 'Einbettungscode',
16
+ 'Alerts': 'Alarme', 'Import': 'Import', 'Create a public read-only link': 'Öffentlichen Nur-Lesen-Link erstellen',
17
+ 'Get a message on traffic spikes, drops or reached goals': 'Nachricht bei Traffic-Spitzen, Einbrüchen oder erreichten Zielen',
18
+ 'Bring your history from Google Analytics or Plausible': 'Alte Daten aus Google Analytics oder Plausible übernehmen',
19
+ 'Filtered by': 'Gefiltert nach', 'Clear all': 'Alle entfernen',
20
+ // stat cards / chart
21
+ 'Unique visitors': 'Besucher', 'Visitors': 'Besucher', 'Pageviews': 'Seitenaufrufe', 'Bounce rate': 'Absprungrate',
22
+ 'Views / visitor': 'Aufrufe / Besucher', 'Avg. visit time': 'Ø Besuchsdauer', 'Conversion rate': 'Conversion-Rate',
23
+ 'Revenue': 'Umsatz', 'Orders': 'Bestellungen', 'Avg. order value': 'Ø Bestellwert', 'Notes': 'Notizen',
24
+ 'Mark a day in the chart, e.g. a newsletter or a release': 'Einen Tag im Diagramm markieren, z. B. Newsletter oder Release',
25
+ 'Includes imported history.': 'Enthält importierte Daten.', 'Imported history is hidden while a filter is active.': 'Importierte Daten sind ausgeblendet, solange ein Filter aktiv ist.',
26
+ 'vs. previous period': 'ggü. Vorzeitraum', 'vs. last year': 'ggü. Vorjahr',
27
+ // panels
28
+ 'Pages': 'Seiten', 'Top': 'Top', 'Entry': 'Einstieg', 'Exit': 'Ausstieg', 'Sources': 'Quellen', 'Source': 'Quelle', 'Medium': 'Medium', 'Campaign': 'Kampagne',
29
+ 'Locations': 'Länder', 'Browsers': 'Browser', 'OS': 'Betriebssysteme', 'Devices': 'Geräte', 'Languages': 'Sprachen',
30
+ 'Platforms': 'Plattformen', 'Platform': 'Plattform', 'App version': 'App-Version', 'web vs. apps': 'Web vs. Apps',
31
+ 'Goals & conversions': 'Ziele & Conversions', 'Goal': 'Ziel', 'Funnels': 'Funnels', '+ New funnel': '+ Neuer Funnel',
32
+ 'Visitor journeys': 'Besucherwege', 'Scroll depth': 'Scrolltiefe', 'avg · share reaching 75 %': 'Ø · Anteil, der 75 % erreicht',
33
+ 'Site search': 'Seitensuche', 'No results': 'Ohne Treffer', 'Broken pages (404)': 'Kaputte Seiten (404)',
34
+ 'Revenue by source': 'Umsatz nach Quelle', 'Revenue by platform': 'Umsatz nach Plattform', 'first source of the buying visit': 'erste Quelle des Kaufbesuchs',
35
+ 'No data yet': 'Noch keine Daten', 'Not enough data yet': 'Noch nicht genug Daten', 'Add a site to begin': 'Füge eine Seite hinzu, um zu starten',
36
+ 'No funnels yet — define one to see where visitors drop off.': 'Noch keine Funnels. Lege einen an, um zu sehen, wo Besucher abspringen.',
37
+ 'Direct': 'Direkt', 'Other': 'Andere', 'Left the site': 'Seite verlassen', 'Desktop': 'Desktop', 'Mobile': 'Mobil', 'Tablet': 'Tablet',
38
+ 'visitors': 'Besucher', 'views': 'Aufrufe', 'Page': 'Seite',
39
+ // live
40
+ 'Live': 'Live', 'Recent activity': 'Letzte Aktivität', 'Visitors over the last 30 minutes · updates every few seconds': 'Besucher der letzten 30 Minuten · aktualisiert sich alle paar Sekunden',
41
+ 'No visitors in the last 30 minutes': 'Keine Besucher in den letzten 30 Minuten',
42
+ // common buttons
43
+ 'Close': 'Schließen', 'Signing in with your password also asks for a 6-digit code from your authenticator app.': 'Bei der Anmeldung mit Passwort wird zusätzlich ein 6-stelliger Code aus deiner Authenticator-App abgefragt.', 'Cancel': 'Abbrechen', 'Create': 'Erstellen', 'Add': 'Hinzufügen', 'Save': 'Speichern', 'Remove': 'Entfernen',
44
+ 'Revoke': 'Widerrufen', 'Copy': 'Kopieren', 'Copy link': 'Link kopieren', 'Copied ✓': 'Kopiert ✓', 'Copied': 'Kopiert', 'Saved ✓': 'Gespeichert ✓',
45
+ 'Sending…': 'Wird gesendet …', 'Sent ✓': 'Gesendet ✓', 'Failed': 'Fehlgeschlagen', 'Test': 'Test', 'Loading…': 'Lädt …', 'Older': 'Ältere',
46
+ 'Uploading…': 'Wird hochgeladen …', 'Download': 'Herunterladen', 'Downloaded': 'Heruntergeladen', 'Changed ✓': 'Geändert ✓', 'on': 'an',
47
+ // add site / snippet / share / funnel
48
+ 'Add a site': 'Seite hinzufügen', 'Enter the domain you want to track.': 'Gib die Domain ein, die du messen willst.', 'example.com': 'beispiel.de',
49
+ 'Add tracking': 'Tracking einbauen', 'for': 'für', '. Same dashboard for website and apps.': '. Ein Dashboard für Website und Apps.',
50
+ 'Website': 'Website', 'Flutter app': 'Flutter-App', 'Other apps': 'Andere Apps', 'Copy step 2': 'Schritt 2 kopieren',
51
+ 'Share dashboard': 'Dashboard teilen', 'Anyone with this link can view stats for': 'Jeder mit diesem Link sieht die Zahlen von',
52
+ '— read-only, no login.': '. Nur lesen, ohne Anmeldung.', 'Revoke this link': 'Diesen Link widerrufen',
53
+ 'New funnel': 'Neuer Funnel', '2–8 ordered steps. A visitor advances only after completing the previous step.': '2–8 Schritte in fester Reihenfolge. Ein Besucher kommt erst weiter, wenn er den vorigen Schritt erledigt hat.',
54
+ 'Funnel name, e.g. Signup flow': 'Name, z. B. Registrierung', '+ Add step': '+ Schritt hinzufügen',
55
+ 'Link expired': 'Link abgelaufen', 'This share link was revoked or never existed.': 'Dieser Link wurde widerrufen oder hat nie existiert.',
56
+ // notes / alerts / import
57
+ 'Notes in the chart': 'Notizen im Diagramm', 'e.g. Newsletter sent': 'z. B. Newsletter verschickt', 'No notes yet.': 'Noch keine Notizen.',
58
+ 'Mark a day — a newsletter, a release, a sale — so a spike explains itself later. Everyone with access to this site sees them.': 'Markiere einen Tag, z. B. Newsletter, Release oder Sale, damit eine Spitze später erklärt ist. Alle mit Zugriff auf die Seite sehen die Notizen.',
59
+ 'Alerts for': 'Alarme für', 'New alert': 'Neuer Alarm', 'Add alert': 'Alarm hinzufügen', 'No alerts for this site yet.': 'Für diese Seite gibt es noch keine Alarme.',
60
+ 'Tacilytics sends a message to a webhook (Discord, Slack, or anything that takes JSON). Checked every 10 minutes.': 'Tacilytics schickt eine Nachricht an einen Webhook (Discord, Slack oder alles, was JSON annimmt). Geprüft wird alle 10 Minuten.',
61
+ 'Traffic spike — about 3× the usual for this hour': 'Traffic-Spitze: etwa 3× so viel wie sonst zu dieser Uhrzeit',
62
+ 'Traffic drop — under 20 % of usual (tracker broken?)': 'Traffic-Einbruch: unter 20 % von sonst (Tracker kaputt?)',
63
+ 'Goal reached — e.g. 10× Purchase': 'Ziel erreicht, z. B. 10× Purchase', 'Event name, e.g. Purchase': 'Event-Name, z. B. Purchase', 'How many': 'Wie oft',
64
+ 'in 1 hour': 'in 1 Stunde', 'in 24 hours': 'in 24 Stunden', 'in 7 days': 'in 7 Tagen', 'Traffic spike': 'Traffic-Spitze', 'Traffic drop': 'Traffic-Einbruch',
65
+ 'Webhook URL (https://…)': 'Webhook-URL (https://…)', 'Webhook URL (optional — server default)': 'Webhook-URL (optional, sonst Server-Standard)',
66
+ 'Leave the webhook empty to use the server default (DIGEST_WEBHOOK_URL).': 'Webhook leer lassen, um den Server-Standard zu nutzen (DIGEST_WEBHOOK_URL).',
67
+ 'Discord: channel settings → Integrations → Webhooks → Copy URL.': 'Discord: Kanaleinstellungen → Integrationen → Webhooks → URL kopieren.',
68
+ 'Import history into': 'Daten importieren in', 'Imported': 'Importiert', 'Plausible': 'Plausible', 'Google Analytics 4': 'Google Analytics 4',
69
+ 'Bring your old numbers along. They show up in the chart and in the panels for the days': 'Nimm deine alten Zahlen mit. Sie erscheinen im Diagramm und in den Listen für die Tage,',
70
+ 'before': 'bevor', 'Tacilytics started counting — never twice.': 'Tacilytics selbst gezählt hat, nie doppelt.',
71
+ 'Choose a file first': 'Bitte zuerst eine Datei wählen',
72
+ // sign in / invite / reset
73
+ 'Cookieless analytics. Sign in with your passphrase.': 'Analyse ohne Cookies. Melde dich mit deiner Passphrase an.',
74
+ 'Cookieless analytics. Sign in to your account.': 'Analyse ohne Cookies. Melde dich mit deinem Konto an.',
75
+ 'Passphrase': 'Passphrase', 'Password': 'Passwort', 'Username': 'Benutzername', 'Sign in': 'Anmelden', 'Sign in with a passkey': 'Mit Passkey anmelden',
76
+ '6-digit code': '6-stelliger Code', 'Verify': 'Bestätigen', 'Lost your phone? Enter one of your recovery codes instead.': 'Handy verloren? Gib stattdessen einen deiner Wiederherstellungscodes ein.',
77
+ 'Wrong password': 'Falsches Passwort', 'Wrong username or password': 'Falscher Benutzername oder falsches Passwort', 'Wrong code': 'Falscher Code',
78
+ 'Connection error': 'Verbindungsfehler', 'Too many attempts — wait 15 minutes': 'Zu viele Versuche, bitte 15 Minuten warten',
79
+ 'Sign-in expired — enter your password again': 'Anmeldung abgelaufen, bitte Passwort erneut eingeben', 'Passkey not accepted': 'Passkey nicht akzeptiert',
80
+ 'Join Tacilytics': 'Tacilytics beitreten', 'Choose a username and a password.': 'Wähle Benutzername und Passwort.', 'Create account': 'Konto erstellen',
81
+ 'Password (at least 10 characters)': 'Passwort (mindestens 10 Zeichen)', 'New password': 'Neues Passwort',
82
+ 'New password (at least 10 characters)': 'Neues Passwort (mindestens 10 Zeichen)', 'Choose a new password.': 'Wähle ein neues Passwort.', 'Save password': 'Passwort speichern',
83
+ 'Password saved. Sign in with your new password.': 'Passwort gespeichert. Melde dich mit dem neuen Passwort an.',
84
+ 'This invite link is invalid, used or expired': 'Dieser Einladungslink ist ungültig, schon benutzt oder abgelaufen',
85
+ 'This reset link is invalid, used or expired': 'Dieser Link ist ungültig, schon benutzt oder abgelaufen',
86
+ // settings
87
+ 'Security': 'Sicherheit', 'Team': 'Team', 'Activity': 'Aktivität',
88
+ 'Passkeys': 'Passkeys', 'optional': 'optional', 'No passkey yet.': 'Noch kein Passkey.', 'Add passkey': 'Passkey hinzufügen', 'Name, e.g. MacBook': 'Name, z. B. MacBook',
89
+ 'Sign in with Face ID, Touch ID, Windows Hello or a security key. No password, no code — and it cannot be phished.': 'Anmelden mit Face ID, Touch ID, Windows Hello oder Sicherheitsschlüssel. Kein Passwort, kein Code, und Phishing geht nicht.',
90
+ 'This browser does not support passkeys.': 'Dieser Browser unterstützt keine Passkeys.',
91
+ 'Two-factor code (OTP)': 'Zwei-Faktor-Code (OTP)', 'Set up': 'Einrichten', 'Turn on': 'Einschalten', 'Turn off': 'Ausschalten', 'New recovery codes': 'Neue Wiederherstellungscodes',
92
+ 'Use any authenticator app (1Password, Google Authenticator, Authy, Bitwarden …).': 'Geht mit jeder Authenticator-App (1Password, Google Authenticator, Authy, Bitwarden …).',
93
+ 'Scan the code, or enter this key by hand:': 'Scanne den Code oder gib den Schlüssel von Hand ein:', 'Your password': 'Dein Passwort',
94
+ 'Save your recovery codes': 'Wiederherstellungscodes sichern', 'I saved them': 'Gesichert',
95
+ 'Each code works once, instead of a 6-digit code. Keep them somewhere safe — they are shown only now.': 'Jeder Code funktioniert einmal, anstelle eines 6-stelligen Codes. Bewahre sie sicher auf, sie werden nur jetzt angezeigt.',
96
+ "Don’t count my own visits": 'Eigene Besuche nicht zählen', 'Ignore this browser': 'Diesen Browser ignorieren', 'Count again': 'Wieder zählen',
97
+ 'Open your site once with one of these links, in every browser and on every phone you use. That browser is then ignored on that site (stored in the browser, nothing on the server). The second link undoes it.': 'Öffne deine Seite einmal mit einem dieser Links, in jedem Browser und auf jedem Handy, das du nutzt. Dieser Browser wird dann auf der Seite nicht mehr gezählt (gespeichert im Browser, nichts auf dem Server). Der zweite Link macht es rückgängig.',
98
+ 'Weekly report': 'Wochenbericht', 'e-mail': 'E-Mail', 'every Monday': 'jeden Montag', 'Send one now': 'Jetzt einen senden',
99
+ 'Every Monday morning, for each of your sites: key figures, a day-by-day chart, highlights, top pages, sources, countries, devices, goals, shop numbers and your notes — compared with the week before.': 'Jeden Montagmorgen für jede deiner Seiten: Kennzahlen, Tagesdiagramm, Auffälliges, Top-Seiten, Quellen, Länder, Geräte, Ziele, Shop-Zahlen und deine Notizen, verglichen mit der Vorwoche.',
100
+ 'Language of the e-mail': 'Sprache der Mail',
101
+ 'E-mail is not set up on this server yet (SMTP_URL and REPORT_FROM in .env).': 'Auf diesem Server ist noch kein Mailversand eingerichtet (SMTP_URL und REPORT_FROM in der .env).',
102
+ 'API keys': 'API-Keys', 'No keys yet.': 'Noch keine Keys.', 'Create key': 'Key erstellen', 'read-only': 'nur lesen', 'read + write': 'lesen + schreiben', 'never used': 'nie benutzt',
103
+ 'Name, e.g. reporting script': 'Name, z. B. Reporting-Skript', 'Copy key': 'Key kopieren', 'Copy it now — it is shown only once.': 'Jetzt kopieren, er wird nur einmal angezeigt.',
104
+ 'Current password': 'Aktuelles Passwort', 'New password (10+)': 'Neues Passwort (10+)', 'Change': 'Ändern',
105
+ 'Changing it signs you out on all other devices.': 'Danach bist du auf allen anderen Geräten abgemeldet.',
106
+ 'Language': 'Sprache', 'Theme': 'Darstellung', 'System': 'System', 'Light': 'Hell', 'Dark': 'Dunkel', 'Automatic': 'Automatisch',
107
+ 'Appearance': 'Darstellung', 'Language and appearance are saved in this browser.': 'Sprache und Darstellung werden in diesem Browser gespeichert.',
108
+ 'Create your account': 'Konto erstellen', 'Create owner account': 'Owner-Konto erstellen',
109
+ 'Members': 'Mitglieder', 'Invite someone': 'Jemanden einladen', 'Create link': 'Link erstellen', 'Open invites': 'Offene Einladungen',
110
+ 'Creates a link that works once, for 7 days. Send it yourself — Tacilytics sends no e-mail.': 'Erstellt einen Link, der einmal und 7 Tage lang funktioniert. Verschick ihn selbst, Tacilytics verschickt keine Mails.',
111
+ 'Owner': 'Owner', 'Admin': 'Admin', 'Viewer': 'Viewer', 'Sites': 'Seiten', 'Password link': 'Passwort-Link', 'Reset 2FA': '2FA zurücksetzen',
112
+ 'One-time link to set a new password (24 h)': 'Einmaliger Link für ein neues Passwort (24 h)', '(you)': '(du)', 'all sites': 'alle Seiten', '2FA on': '2FA an', 'no 2FA': 'ohne 2FA',
113
+ 'Nothing yet.': 'Noch nichts.', 'someone': 'jemand',
114
+ 'Remove this passkey?': 'Diesen Passkey entfernen?', 'Remove this member? They are signed out immediately.': 'Mitglied entfernen? Die Person wird sofort abgemeldet.',
115
+ 'Turn off two-factor codes?': 'Zwei-Faktor-Codes ausschalten?', 'Turn off 2FA for this member? Use this when they lost their phone.': '2FA für dieses Mitglied ausschalten? Das ist für den Fall, dass das Handy weg ist.',
116
+ 'Revoke this key? Scripts using it stop working immediately.': 'Key widerrufen? Skripte, die ihn nutzen, funktionieren sofort nicht mehr.',
117
+ 'Remove this import? The imported numbers disappear from all charts.': 'Import entfernen? Die importierten Zahlen verschwinden aus allen Diagrammen.',
118
+ // activity labels
119
+ 'added site': 'hat Seite hinzugefügt', 'removed site': 'hat Seite entfernt', 'created share link': 'hat Share-Link erstellt', 'revoked share link': 'hat Share-Link widerrufen',
120
+ 'added note': 'hat Notiz hinzugefügt', 'removed note': 'hat Notiz entfernt', 'added funnel': 'hat Funnel angelegt', 'removed funnel': 'hat Funnel entfernt',
121
+ 'created the first account': 'hat das erste Konto erstellt', 'created invite': 'hat eingeladen', 'revoked invite': 'hat Einladung widerrufen',
122
+ 'joined via invite': 'ist über Einladung beigetreten', 'changed role': 'hat Rolle geändert', 'removed member': 'hat Mitglied entfernt',
123
+ 'reset 2FA of': 'hat 2FA zurückgesetzt von', 'created password link for': 'hat Passwort-Link erstellt für', 'changed own password': 'hat eigenes Passwort geändert',
124
+ 'set a new password via link': 'hat per Link ein neues Passwort gesetzt', 'turned 2FA on': 'hat 2FA eingeschaltet', 'turned 2FA off': 'hat 2FA ausgeschaltet',
125
+ 'made new recovery codes': 'hat neue Wiederherstellungscodes erzeugt', 'added passkey': 'hat Passkey hinzugefügt', 'removed passkey': 'hat Passkey entfernt',
126
+ 'created API key': 'hat API-Key erstellt', 'revoked API key': 'hat API-Key widerrufen', 'turned the weekly report on': 'hat den Wochenbericht eingeschaltet',
127
+ 'turned the weekly report off': 'hat den Wochenbericht ausgeschaltet', 'added alert': 'hat Alarm hinzugefügt', 'removed alert': 'hat Alarm entfernt',
128
+ 'turned alert on': 'hat Alarm eingeschaltet', 'turned alert off': 'hat Alarm ausgeschaltet', 'imported history': 'hat Daten importiert', 'removed import': 'hat Import entfernt',
129
+ 'reset the login (password, 2FA, sessions) of': 'hat die Anmeldung zurückgesetzt (Passwort, 2FA, Sitzungen) von',
130
+ 'Visitors on imported days are summed per day, not unique across days.': 'Besucher an importierten Tagen werden pro Tag summiert, nicht über Tage hinweg eindeutig gezählt.',
131
+ 'Tacilytics · open source ·': 'Tacilytics · Open Source ·',
132
+ '· open source ·': '· Open Source ·',
133
+ 'Discard your changes?': 'Änderungen verwerfen?',
134
+ 'Time zone': 'Zeitzone',
135
+ 'Time zone (days start at midnight here)': 'Zeitzone (hier beginnt der Tag um Mitternacht)',
136
+ 'Time zone of this site — days start at midnight here': 'Zeitzone dieser Website, hier beginnt der Tag um Mitternacht',
137
+ 'Days, hours, the weekly report and alerts follow this time zone for': 'Tage, Stunden, Wochenbericht und Alarme richten sich nach dieser Zeitzone für',
138
+ 'Stored data does not change — switching back and forth is safe.': 'Gespeicherte Daten ändern sich nicht, Hin- und Herwechseln ist gefahrlos.',
139
+ 'Unique visitors per day are an approximation outside UTC: the anonymous visitor ID changes at midnight UTC.': 'Eindeutige Besucher pro Tag sind außerhalb von UTC eine Näherung: Die anonyme Besucher-ID wechselt um Mitternacht UTC.',
140
+ 'Unknown time zone': 'Unbekannte Zeitzone',
141
+ 'Direct / none': 'Direkt / keine', 'Android app': 'Android-App', 'iOS app': 'iOS-App', 'Web': 'Web',
142
+ 'For scripts, agents and the CLI (': 'Für Skripte, Agents und die CLI (',
143
+ '). A key acts as you, sees only your sites, and cannot touch the team or your security settings. Read-only unless you untick it.': '). Ein Key handelt als du, sieht nur deine Seiten und kann weder das Team noch deine Sicherheitseinstellungen ändern. Nur lesen, außer du nimmst den Haken raus.',
144
+ 'Signed in as': 'Angemeldet als',
145
+ 'Viewer — read only': 'Viewer: nur lesen', 'Admin — sites, share links and funnels': 'Admin: Seiten, Share-Links und Funnels', 'Owner — everything, including the team': 'Owner: alles, auch das Team',
146
+ 'In Plausible:': 'In Plausible:', 'Site settings → Imports & Exports → Export data': 'Website-Einstellungen → Imports & Exports → Export data',
147
+ '. Upload the ZIP you get (or the CSV files in it).': '. Lade das ZIP hoch (oder die CSV-Dateien darin).',
148
+ 'In Google Analytics 4: open a report (e.g.': 'In Google Analytics 4: Öffne einen Bericht (z. B.', 'Pages and screens': 'Seiten und Bildschirme', 'Traffic acquisition': 'Akquisition von Zugriffen',
149
+ 'Demographic details': 'Demografische Details', '), add': ') und füge', 'Date': 'Datum', 'as a secondary dimension, then': 'als sekundäre Dimension hinzu, dann',
150
+ 'Share → Download file → CSV': 'Teilen → Datei herunterladen → CSV', '. One file per report; pick several at once.': '. Eine Datei pro Bericht, du kannst mehrere auf einmal wählen.',
151
+ 'Privacy flags on the tag:': 'Datenschutz-Optionen am Tag:', 'Track goals & properties from your code:': 'Ziele und Eigenschaften aus deinem Code messen:',
152
+ 'Recovery codes left:': 'Übrige Wiederherstellungscodes:', 'server default': 'Server-Standard', 'last sent': 'zuletzt gesendet',
153
+ };
154
+ // strings with numbers / names in them
155
+ var DE_RX = [
156
+ [/^Every change by anyone on the team, newest first\. Kept for a year \(AUDIT_RETENTION_DAYS\)\.$/, 'Jede Änderung im Team, neueste zuerst. Wird ein Jahr aufbewahrt (AUDIT_RETENTION_DAYS).'],
157
+ [/^Signed in as (.+) · (Owner|Admin|Viewer)$/, 'Angemeldet als $1 · $2'],
158
+ [/^You were invited as (owner|admin|viewer)\. Choose a username and a password\.$/, function (m, r) { return 'Du wurdest als ' + { owner: 'Owner', admin: 'Admin', viewer: 'Viewer' }[r] + ' eingeladen. Wähle Benutzername und Passwort.'; }],
159
+ [/^Choose a new password for (.+)\.( Your two-factor code stays on\.)?$/, function (m, u, z) { return 'Wähle ein neues Passwort für ' + u + '.' + (z ? ' Dein Zwei-Faktor-Code bleibt an.' : ''); }],
160
+ [/^(\d+) files? imported \(([\d.,]+) views\)\.\s*(.*)$/, function (m, n, v, rest) { return n + (n === '1' ? ' Datei' : ' Dateien') + ' importiert (' + v + ' Aufrufe). ' + rest; }],
161
+ [/^Includes imported history \(until (.+)\)\.$/, 'Enthält importierte Daten (bis $1).'],
162
+ [/^Own data starts (.+); imported days from then on are ignored\.$/, 'Eigene Daten ab $1. Importierte Tage ab dann werden ignoriert.'],
163
+ [/^Days from (.+) on already have own data — imported numbers count only before that day\.$/, 'Ab $1 gibt es schon eigene Daten. Importierte Zahlen zählen nur vor diesem Tag.'],
164
+ [/^Left the site \(([\d.,]+k?)\)$/, 'Seite verlassen ($1)'],
165
+ [/^(\d+)% deep$/, '$1 % tief'],
166
+ [/^(.+) — ([\d.,]+k?) measured views$/, '$1 · $2 gemessene Aufrufe'],
167
+ [/^· ([\d.,]+k?) sessions, top paths, 30-min session window$/, '· $1 Sitzungen, häufigste Wege, Sitzungsfenster 30 Min.'],
168
+ [/^([\d.,]+) searches · ([\d.,]+)% without results$/, '$1 Suchen · $2 % ohne Treffer'],
169
+ [/^(“[^”]+”)\s+· (\d+)× no results$/, '$1 · $2× ohne Treffer'],
170
+ [/^· (Owner|Admin|Viewer)$/, '· $1'],
171
+ [/^(2FA on|no 2FA)((?: · \d+ passkeys?)?) · last sign-in (.+)$/, function (m, a, pk, d) { return (a === '2FA on' ? '2FA an' : 'ohne 2FA') + pk.replace('passkeys', 'Passkeys').replace('passkey', 'Passkey') + ' · letzte Anmeldung ' + d; }],
172
+ [/^(.+) · (2FA on|no 2FA)((?: · \d+ passkeys?)?) · last sign-in (.+)$/, function (m, s, a, pk, d) { return s.replace('all sites', 'alle Seiten') + ' · ' + (a === '2FA on' ? '2FA an' : 'ohne 2FA') + pk + ' · letzte Anmeldung ' + d; }],
173
+ [/^(\d+) visitors$/, '$1 Besucher'], [/^(\d+) views$/, '$1 Aufrufe'], [/^added (.+)$/, 'hinzugefügt $1'],
174
+ [/^used (.+)$/, 'benutzt $1'], [/^last sign-in (.+)$/, 'letzte Anmeldung $1'], [/^expires (.+)$/, 'läuft ab $1'], [/^Last sent (.+)$/, 'Zuletzt gesendet $1'],
175
+ [/^Server did not answer with JSON \(HTTP (\d+)\)$/, 'Der Server hat nicht mit JSON geantwortet (HTTP $1). Bitte neu laden.'],
176
+ [/^Send this to (.+)\. It works once, for 24 hours\. Their 2FA stays on\.$/, 'Schick das an $1. Funktioniert einmal, 24 Stunden lang. Die 2FA bleibt an.'],
177
+ ];
178
+
179
+ function lang() {
180
+ var p; try { p = localStorage.getItem('tacilytics_lang') || 'auto'; } catch (e) { p = 'auto'; }
181
+ if (p === 'auto') p = /^de\b/i.test(navigator.language || '') ? 'de' : 'en';
182
+ return p === 'de' ? 'de' : 'en';
183
+ }
184
+ var L = lang();
185
+ var DE_VALUES = {}; // already-German strings: never "missing", never re-translated
186
+ document.documentElement.setAttribute('lang', L);
187
+ // collecting untranslated strings only on demand (?i18n=debug) — otherwise the set would grow with
188
+ // every page path / source shown during a long session
189
+ var DEBUG = /[?&]i18n=debug\b/.test(location.search);
190
+ if (DEBUG) window.__taciMissing = window.__taciMissing || {};
191
+ function tr(s) {
192
+ if (L !== 'de' || !s) return null;
193
+ var lead = s.match(/^\s*/)[0], tail = s.match(/\s*$/)[0], core = s.trim();
194
+ if (!core || !/[A-Za-z]/.test(core)) return null;
195
+ if (Object.prototype.hasOwnProperty.call(DE, core)) return lead + DE[core] + tail;
196
+ if (DE_VALUES[core]) return null;
197
+ for (var i = 0; i < DE_RX.length; i++) if (DE_RX[i][0].test(core)) return lead + noteResult(core.replace(DE_RX[i][0], DE_RX[i][1])) + tail;
198
+ if (DEBUG) window.__taciMissing[core] = 1;
199
+ return null;
200
+ }
201
+ function addValue(v) { if (typeof v === 'string') DE_VALUES[v.trim()] = 1; }
202
+ for (var k in DE) if (Object.prototype.hasOwnProperty.call(DE, k)) addValue(DE[k]);
203
+ function noteResult(t) { if (t) addValue(t); return t;
204
+ }
205
+ var ATTRS = ['placeholder', 'title', 'aria-label'];
206
+ var SKIP = { SCRIPT: 1, STYLE: 1, CODE: 1, PRE: 1, TEXTAREA: 1 };
207
+ function walk(node) {
208
+ if (L !== 'de') return;
209
+ if (node.nodeType === 3) {
210
+ var p = node.parentNode;
211
+ if (!p || SKIP[p.nodeName] || (p.closest && p.closest('.snippet, .bar .name, .feed, #actRows .act-target, [data-no-i18n]'))) return;
212
+ var t = tr(node.nodeValue); if (t != null && t !== node.nodeValue) node.nodeValue = t;
213
+ return;
214
+ }
215
+ if (node.nodeType !== 1 || SKIP[node.nodeName]) return;
216
+ if (node.closest && node.closest('[data-no-i18n]')) return;
217
+ for (var a = 0; a < ATTRS.length; a++) {
218
+ var v = node.getAttribute && node.getAttribute(ATTRS[a]);
219
+ if (v) { var tv = tr(v); if (tv != null && tv !== v) node.setAttribute(ATTRS[a], tv); } // equal → no write, or the observer loops
220
+ }
221
+ if (node.nodeName === 'OPTION' && node.parentNode && node.parentNode.id === 'siteSelect') return; // domains
222
+ for (var c = node.firstChild; c; c = c.nextSibling) walk(c);
223
+ }
224
+ function start() {
225
+ walk(document.body);
226
+ new MutationObserver(function (list) {
227
+ for (var i = 0; i < list.length; i++) {
228
+ var m = list[i];
229
+ if (m.type === 'characterData') walk(m.target);
230
+ else if (m.type === 'attributes') walk(m.target);
231
+ else for (var j = 0; j < m.addedNodes.length; j++) walk(m.addedNodes[j]);
232
+ }
233
+ }).observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ATTRS });
234
+ }
235
+ if (L === 'de') { if (document.body) start(); else document.addEventListener('DOMContentLoaded', start); }
236
+ window.TaciLang = {
237
+ get: function () { try { return localStorage.getItem('tacilytics_lang') || 'auto'; } catch (e) { return 'auto'; } },
238
+ set: function (v) { try { localStorage.setItem('tacilytics_lang', v); } catch (e) {} location.reload(); },
239
+ current: L,
240
+ t: function (s) { var r = tr(s); return r == null ? s : r; }, // for texts that never reach the DOM (confirm())
241
+ };
242
+ })();