feat: Rename to Toju and add translation
Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped

This commit is contained in:
2026-06-05 17:13:03 +02:00
parent 8ecfc9a1fe
commit ee293d7daf
301 changed files with 8247 additions and 2218 deletions

View File

@@ -10,7 +10,7 @@
[class.border-destructive/40]="isServerMarkedBanned(server)"
[class.bg-destructive/5]="isServerMarkedBanned(server)"
[class.hover:border-destructive/60]="isServerMarkedBanned(server)"
[title]="isJoinedServer(server) ? 'Double-click to open ' + server.name : 'Double-click to join ' + server.name"
[title]="serverCardTitle(server)"
(dblclick)="openServerCard(server)"
>
<div class="flex min-w-0 items-start gap-3">
@@ -43,7 +43,7 @@
name="lucideLock"
class="h-3 w-3"
/>
Banned
{{ 'servers.browser.card.banned' | translate }}
</span>
} @else if (server.isPrivate) {
<span class="inline-flex items-center gap-1 rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
@@ -51,7 +51,7 @@
name="lucideLock"
class="h-3 w-3"
/>
Private
{{ 'servers.browser.card.private' | translate }}
</span>
} @else if (server.hasPassword) {
<span class="inline-flex items-center gap-1 rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
@@ -59,7 +59,7 @@
name="lucideLock"
class="h-3 w-3"
/>
Password
{{ 'servers.browser.card.password' | translate }}
</span>
} @else {
<ng-icon
@@ -84,8 +84,8 @@
@if (server.topic) {
<span class="truncate">{{ server.topic }}</span>
}
<span class="truncate">Owner: {{ getServerOwnerLabel(server) }}</span>
<span class="truncate">{{ server.sourceName || server.hostName || 'Unknown' }}</span>
<span class="truncate">{{ ownerLabel(server) }}</span>
<span class="truncate">{{ server.sourceName || server.hostName || ('common.labels.unknown' | translate) }}</span>
</div>
</div>
@@ -94,11 +94,11 @@
<div
class="flex items-center overflow-hidden rounded-md border border-emerald-500/30 bg-emerald-500/10 text-xs font-semibold text-emerald-500"
>
<span class="px-2.5 py-1.5">Joined</span>
<span class="px-2.5 py-1.5">{{ 'servers.browser.card.joined' | translate }}</span>
<button
type="button"
class="grid h-8 w-8 place-items-center border-l border-emerald-500/20 transition-colors hover:bg-emerald-500/15"
[attr.aria-label]="'Server actions for ' + server.name"
[attr.aria-label]="serverActionsLabel(server)"
(click)="toggleJoinedServerMenu($event, server)"
>
<ng-icon
@@ -115,7 +115,7 @@
class="w-full px-3 py-2 text-left text-xs font-medium text-destructive transition-colors hover:bg-destructive/10"
(click)="openLeaveDialog($event, server)"
>
Leave
{{ 'servers.browser.card.leave' | translate }}
</button>
</div>
}
@@ -123,11 +123,11 @@
<button
type="button"
class="rounded-md bg-primary px-2.5 py-1.5 text-xs font-semibold text-primary-foreground transition-colors hover:bg-primary/90"
[attr.aria-label]="'Join ' + server.name"
[attr.aria-label]="joinServerLabel(server)"
(click)="joinServer(server)"
>
<span class="sr-only">{{ server.name }}</span>
Join
{{ 'servers.browser.card.join' | translate }}
</button>
}
</div>
@@ -144,9 +144,9 @@
/>
<input
type="text"
aria-label="Search servers"
[attr.aria-label]="'servers.browser.search.ariaLabel' | translate"
class="h-10 w-full rounded-lg border border-border bg-secondary py-2 pl-10 pr-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
[placeholder]="searchPlaceholder"
[placeholder]="resolvedSearchPlaceholder"
[(ngModel)]="searchQuery"
(ngModelChange)="onSearchChange($event)"
/>
@@ -154,7 +154,7 @@
@if (showMyServers && savedRooms().length > 0) {
<div class="mt-2 flex items-center gap-2 overflow-x-auto pb-1">
<span class="shrink-0 text-xs font-medium text-muted-foreground">My Servers</span>
<span class="shrink-0 text-xs font-medium text-muted-foreground">{{ 'servers.browser.search.myServers' | translate }}</span>
@for (room of savedRooms(); track room.id) {
<button
type="button"
@@ -172,8 +172,8 @@
@if (isSearchMode) {
<div class="sticky top-0 z-10 flex items-center justify-between border-b border-border bg-background/95 px-3 py-2 backdrop-blur">
<div>
<h3 class="text-sm font-semibold text-foreground">Search results</h3>
<p class="text-xs text-muted-foreground">{{ searchResults().length }} found</p>
<h3 class="text-sm font-semibold text-foreground">{{ 'servers.browser.search.resultsTitle' | translate }}</h3>
<p class="text-xs text-muted-foreground">{{ 'servers.browser.search.resultsCount' | translate: { count: searchResults().length } }}</p>
</div>
</div>
@@ -187,7 +187,7 @@
name="lucideSearch"
class="mb-3 h-10 w-10 opacity-50"
/>
<p class="text-sm font-medium">No servers found</p>
<p class="text-sm font-medium">{{ 'servers.browser.search.noResults' | translate }}</p>
</div>
} @else {
<div class="space-y-2 p-3">
@@ -202,8 +202,8 @@
name="lucideSearch"
class="mb-4 h-12 w-12 opacity-40"
/>
<p class="text-base font-semibold text-foreground">{{ emptyStateTitle }}</p>
<p class="mt-1 max-w-sm text-sm">{{ emptyStateMessage }}</p>
<p class="text-base font-semibold text-foreground">{{ resolvedEmptyStateTitle }}</p>
<p class="mt-1 max-w-sm text-sm">{{ resolvedEmptyStateMessage }}</p>
</div>
} @else {
<div class="space-y-6 p-3">
@@ -244,42 +244,42 @@
@if (showBannedDialog()) {
<app-confirm-dialog
title="Banned"
confirmLabel="OK"
cancelLabel="Close"
[title]="'servers.browser.bannedDialog.title' | translate"
[confirmLabel]="'common.actions.ok' | translate"
[cancelLabel]="'common.actions.close' | translate"
variant="danger"
[widthClass]="'w-96 max-w-[90vw]'"
(confirmed)="closeBannedDialog()"
(cancelled)="closeBannedDialog()"
>
<p>You are banned from {{ bannedServerName() || 'this server' }}.</p>
<p>{{ bannedDialogMessage() }}</p>
</app-confirm-dialog>
}
@if (showPasswordDialog() && passwordPromptServer()) {
<app-confirm-dialog
title="Password required"
confirmLabel="Join server"
cancelLabel="Cancel"
[title]="'servers.browser.passwordDialog.title' | translate"
[confirmLabel]="'servers.browser.passwordDialog.confirm' | translate"
[cancelLabel]="'common.actions.cancel' | translate"
[widthClass]="'w-[420px] max-w-[92vw]'"
(confirmed)="confirmPasswordJoin()"
(cancelled)="closePasswordDialog()"
>
<div class="space-y-3">
<p>Enter the password to join {{ passwordPromptServer()!.name }}.</p>
<p>{{ passwordDialogMessage(passwordPromptServer()!) }}</p>
<div>
<label
for="join-server-password"
class="mb-1 block text-xs font-medium uppercase tracking-wide text-muted-foreground"
>
Server password
{{ 'servers.browser.passwordDialog.label' | translate }}
</label>
<input
id="join-server-password"
type="password"
[(ngModel)]="joinPassword"
placeholder="Enter password"
[placeholder]="'servers.browser.passwordDialog.placeholder' | translate"
class="w-full rounded-lg border border-border bg-secondary px-3 py-2 text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
@@ -303,19 +303,19 @@
aria-labelledby="join-plugin-consent-title"
>
<header class="border-b border-border p-4">
<p class="text-sm text-muted-foreground">Plugin downloads</p>
<p class="text-sm text-muted-foreground">{{ 'servers.plugins.eyebrow' | translate }}</p>
<h2
id="join-plugin-consent-title"
class="mt-1 text-lg font-semibold"
>
{{ dialog.server.name }} uses plugins
{{ pluginUsesPluginsLabel(dialog.server.name) }}
</h2>
</header>
<div class="grid min-h-0 gap-4 overflow-auto p-4">
@if (dialog.required.length > 0) {
<section class="grid gap-2">
<h3 class="text-sm font-semibold">Required before joining</h3>
<h3 class="text-sm font-semibold">{{ 'servers.plugins.requiredTitle' | translate }}</h3>
@for (requirement of dialog.required; track requirement.pluginId) {
<div class="grid gap-3 rounded-lg border border-border bg-background/50 px-3 py-2">
<div class="flex items-start justify-between gap-3">
@@ -325,12 +325,14 @@
<p class="mt-1 text-xs text-muted-foreground">{{ requirement.reason }}</p>
}
</div>
<span class="shrink-0 rounded-full bg-primary/10 px-2 py-0.5 text-xs font-semibold text-primary">Required</span>
<span class="shrink-0 rounded-full bg-primary/10 px-2 py-0.5 text-xs font-semibold text-primary">{{
'servers.plugins.requiredBadge' | translate
}}</span>
</div>
@if (requirement.manifest?.capabilities; as capabilities) {
<details class="rounded-md border border-border bg-secondary/40 px-2 py-1.5 text-xs text-muted-foreground">
<summary class="cursor-pointer font-semibold text-foreground">Capabilities</summary>
<summary class="cursor-pointer font-semibold text-foreground">{{ 'servers.plugins.capabilities' | translate }}</summary>
<div class="mt-2 flex flex-wrap gap-1.5">
@for (capability of capabilities; track capability) {
<span class="rounded-full bg-background px-2 py-0.5 font-mono text-[11px] text-muted-foreground">{{ capability }}</span>
@@ -350,7 +352,7 @@
name="lucideExternalLink"
class="h-3.5 w-3.5"
/>
Source
{{ 'servers.plugins.source' | translate }}
</button>
}
@@ -365,7 +367,7 @@
name="lucideFileText"
class="h-3.5 w-3.5"
/>
{{ pluginConsentReadmeLoadingId() === requirement.pluginId ? 'Loading' : 'Readme' }}
{{ pluginReadmeButtonLabel(requirement.pluginId) }}
</button>
}
</div>
@@ -376,7 +378,7 @@
@if (dialog.optional.length > 0) {
<section class="grid gap-2">
<h3 class="text-sm font-semibold">Optional plugins</h3>
<h3 class="text-sm font-semibold">{{ 'servers.plugins.optionalTitle' | translate }}</h3>
@for (requirement of dialog.optional; track requirement.pluginId) {
<div class="grid gap-3 rounded-lg border border-border bg-background/50 px-3 py-2">
<label class="flex items-start gap-3">
@@ -397,7 +399,7 @@
@if (requirement.manifest?.capabilities; as capabilities) {
<details class="rounded-md border border-border bg-secondary/40 px-2 py-1.5 text-xs text-muted-foreground">
<summary class="cursor-pointer font-semibold text-foreground">Capabilities</summary>
<summary class="cursor-pointer font-semibold text-foreground">{{ 'servers.plugins.capabilities' | translate }}</summary>
<div class="mt-2 flex flex-wrap gap-1.5">
@for (capability of capabilities; track capability) {
<span class="rounded-full bg-background px-2 py-0.5 font-mono text-[11px] text-muted-foreground">{{ capability }}</span>
@@ -417,7 +419,7 @@
name="lucideExternalLink"
class="h-3.5 w-3.5"
/>
Source
{{ 'servers.plugins.source' | translate }}
</button>
}
@@ -432,7 +434,7 @@
name="lucideFileText"
class="h-3.5 w-3.5"
/>
{{ pluginConsentReadmeLoadingId() === requirement.pluginId ? 'Loading' : 'Readme' }}
{{ pluginReadmeButtonLabel(requirement.pluginId) }}
</button>
}
</div>
@@ -457,7 +459,7 @@
[disabled]="pluginConsentBusy()"
class="inline-flex min-h-8 items-center justify-center rounded-lg border border-border bg-card px-3 py-1.5 text-sm font-semibold transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-55"
>
Cancel join
{{ 'servers.plugins.cancelJoin' | translate }}
</button>
<button
type="button"
@@ -465,7 +467,7 @@
[disabled]="pluginConsentBusy()"
class="inline-flex min-h-8 items-center justify-center rounded-lg border border-primary bg-primary px-3 py-1.5 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-55"
>
{{ pluginConsentBusy() ? 'Downloading' : dialog.required.length > 0 ? 'Accept and join' : 'Join' }}
{{ pluginConsentConfirmLabel(dialog.required.length) }}
</button>
</footer>
</section>
@@ -473,7 +475,7 @@
@if (pluginConsentReadme(); as readme) {
<app-modal-backdrop
[zIndex]="52"
ariaLabel="Close readme"
[ariaLabel]="'servers.plugins.closeReadme' | translate"
(dismissed)="closePluginConsentReadme()"
/>
<section
@@ -484,7 +486,7 @@
>
<header class="flex items-start justify-between gap-3 border-b border-border p-4">
<div class="min-w-0">
<p class="text-sm text-muted-foreground">Plugin readme</p>
<p class="text-sm text-muted-foreground">{{ 'servers.plugins.readmeEyebrow' | translate }}</p>
<h2
id="join-plugin-readme-title"
class="mt-1 truncate text-lg font-semibold"
@@ -496,7 +498,7 @@
type="button"
(click)="closePluginConsentReadme()"
class="grid h-8 w-8 shrink-0 place-items-center rounded-lg border border-border text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground"
title="Close readme"
[title]="'common.actions.close' | translate"
>
X
</button>