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

@@ -9,7 +9,7 @@
name="lucidePhone"
class="h-5 w-5"
/>
Join call
{{ 'call.joinCall' | translate }}
</button>
}
@@ -24,8 +24,8 @@
[class.hover:bg-destructive/15]="muted()"
[disabled]="!connected()"
(click)="muteToggled.emit()"
[attr.aria-label]="muted() ? 'Unmute' : 'Mute'"
[title]="muted() ? 'Unmute' : 'Mute'"
[attr.aria-label]="(muted() ? 'call.unmute' : 'call.mute') | translate"
[title]="(muted() ? 'call.unmute' : 'call.mute') | translate"
>
<ng-icon
[name]="muted() ? 'lucideMicOff' : 'lucideMic'"
@@ -44,8 +44,8 @@
[class.hover:bg-destructive/15]="deafened()"
[disabled]="!connected()"
(click)="deafenToggled.emit()"
[attr.aria-label]="deafened() ? 'Undeafen' : 'Deafen'"
[title]="deafened() ? 'Undeafen' : 'Deafen'"
[attr.aria-label]="(deafened() ? 'call.undeafen' : 'call.deafen') | translate"
[title]="(deafened() ? 'call.undeafen' : 'call.deafen') | translate"
>
<ng-icon
name="lucideHeadphones"
@@ -61,8 +61,8 @@
[class.ring-primary]="speakerphoneEnabled()"
[disabled]="!connected()"
(click)="speakerphoneToggled.emit()"
[attr.aria-label]="speakerphoneEnabled() ? 'Use earpiece' : 'Use speakerphone'"
[title]="speakerphoneEnabled() ? 'Use earpiece' : 'Use speakerphone'"
[attr.aria-label]="(speakerphoneEnabled() ? 'call.useEarpiece' : 'call.useSpeakerphone') | translate"
[title]="(speakerphoneEnabled() ? 'call.useEarpiece' : 'call.useSpeakerphone') | translate"
>
<ng-icon
name="lucideVolume2"
@@ -76,8 +76,8 @@
class="grid h-12 w-12 place-items-center rounded-full bg-secondary text-foreground transition-colors hover:bg-secondary/80 disabled:opacity-45"
[disabled]="!connected()"
(click)="cameraToggled.emit()"
[attr.aria-label]="cameraEnabled() ? 'Turn camera off' : 'Turn camera on'"
[title]="cameraEnabled() ? 'Turn camera off' : 'Turn camera on'"
[attr.aria-label]="(cameraEnabled() ? 'call.turnCameraOff' : 'call.turnCameraOn') | translate"
[title]="(cameraEnabled() ? 'call.turnCameraOff' : 'call.turnCameraOn') | translate"
>
<ng-icon
[name]="cameraEnabled() ? 'lucideVideoOff' : 'lucideVideo'"
@@ -90,8 +90,8 @@
class="grid h-12 w-12 place-items-center rounded-full bg-secondary text-foreground transition-colors hover:bg-secondary/80 disabled:opacity-45"
[disabled]="!connected()"
(click)="screenShareToggled.emit()"
[attr.aria-label]="screenSharing() ? 'Stop sharing screen' : 'Share screen'"
[title]="screenSharing() ? 'Stop sharing screen' : 'Share screen'"
[attr.aria-label]="(screenSharing() ? 'call.stopSharingScreen' : 'call.shareScreen') | translate"
[title]="(screenSharing() ? 'call.stopSharingScreen' : 'call.shareScreen') | translate"
>
<ng-icon
[name]="screenSharing() ? 'lucideMonitorOff' : 'lucideMonitor'"
@@ -104,8 +104,8 @@
class="grid h-12 w-12 place-items-center rounded-full bg-destructive/10 text-destructive transition-colors hover:bg-destructive/15 disabled:opacity-45"
[disabled]="!connected()"
(click)="leaveRequested.emit()"
aria-label="Leave call"
title="Leave call"
[attr.aria-label]="'call.leaveCall' | translate"
[title]="'call.leaveCall' | translate"
>
<ng-icon
name="lucidePhoneOff"

View File

@@ -17,10 +17,12 @@ import {
lucideVolume2
} from '@ng-icons/lucide';
import { APP_TRANSLATE_IMPORTS } from '../../core/i18n';
@Component({
selector: 'app-private-call-controls',
standalone: true,
imports: [NgIcon],
imports: [NgIcon, ...APP_TRANSLATE_IMPORTS],
viewProviders: [
provideIcons({
lucideHeadphones,

View File

@@ -34,12 +34,12 @@
</div>
<div class="min-w-0">
<h1 class="truncate text-base font-semibold text-foreground">Private Call</h1>
<h1 class="truncate text-base font-semibold text-foreground">{{ 'call.private.title' | translate }}</h1>
<p class="truncate text-xs text-muted-foreground">
@if (session()) {
{{ participantUsers().length }} participants
{{ 'call.private.participants' | translate: { count: participantUsers().length } }}
} @else {
Call not found
{{ 'call.private.notFound' | translate }}
}
</p>
</div>
@@ -52,8 +52,8 @@
type="button"
class="grid h-10 w-10 place-items-center rounded-full bg-secondary text-foreground transition-colors hover:bg-secondary/80"
(click)="minimizeCall()"
aria-label="Minimize call"
title="Minimize call"
[attr.aria-label]="'call.private.minimize' | translate"
[title]="'call.private.minimize' | translate"
>
<ng-icon
name="lucideX"
@@ -65,9 +65,9 @@
class="hidden h-9 max-w-44 rounded-md border border-border bg-secondary px-2 text-sm text-foreground sm:block"
[ngModel]="inviteUserId()"
(ngModelChange)="inviteUserId.set($event)"
aria-label="Add user to call"
[attr.aria-label]="'call.private.addUserAria' | translate"
>
<option value="">Add user</option>
<option value="">{{ 'call.private.addUser' | translate }}</option>
@for (user of inviteCandidates(); track userKey(user)) {
<option [value]="userKey(user)">{{ user.displayName }}</option>
}
@@ -77,8 +77,8 @@
class="hidden h-9 w-9 place-items-center rounded-md bg-secondary text-foreground transition-colors hover:bg-secondary/80 disabled:opacity-50 sm:grid"
[disabled]="!inviteUserId()"
(click)="inviteSelectedUser()"
aria-label="Add user"
title="Add user"
[attr.aria-label]="'call.private.addUserButton' | translate"
[title]="'call.private.addUserButton' | translate"
>
<ng-icon
name="lucideUserPlus"
@@ -100,14 +100,14 @@
type="button"
data-testid="private-call-show-all-streams"
class="inline-flex h-10 items-center gap-2 rounded-full border border-white/10 bg-black/45 px-3 text-xs font-medium text-white/80 backdrop-blur transition hover:bg-black/65 hover:text-white"
title="Show all streams"
[title]="'call.private.showAllStreams' | translate"
(click)="showAllStreams()"
>
<ng-icon
name="lucideUsers"
class="h-3.5 w-3.5"
/>
All streams
{{ 'call.private.allStreams' | translate }}
</button>
</div>
}
@@ -212,7 +212,7 @@
</div>
</div>
} @else {
<div class="flex flex-1 items-center justify-center px-6 text-sm text-muted-foreground">No active call for this route.</div>
<div class="flex flex-1 items-center justify-center px-6 text-sm text-muted-foreground">{{ 'call.private.noActiveCall' | translate }}</div>
}
</main>
@@ -221,7 +221,7 @@
class="group absolute inset-y-0 left-0 z-10 w-3 -translate-x-1/2 cursor-col-resize bg-transparent"
role="separator"
aria-orientation="vertical"
title="Resize chat"
[title]="'call.private.resizeChat' | translate"
data-testid="private-call-chat-resizer"
(mousedown)="startChatResize($event)"
>

View File

@@ -24,6 +24,7 @@ import {
lucideUserPlus
} from '@ng-icons/lucide';
import { map } from 'rxjs';
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../core/i18n';
import {
DirectCallService,
participantToUser,
@@ -63,7 +64,8 @@ import { PrivateCallParticipantCardComponent } from './private-call-participant-
PrivateCallControlsComponent,
PrivateCallParticipantCardComponent,
ScreenShareQualityDialogComponent,
VoiceWorkspaceStreamTileComponent
VoiceWorkspaceStreamTileComponent,
...APP_TRANSLATE_IMPORTS
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
host: { class: 'block h-full w-full' },
@@ -91,6 +93,7 @@ export class PrivateCallComponent {
private readonly mobilePlatform = inject(MobilePlatformService);
private readonly mobileMedia = inject(MobileMediaService);
private chatResizing = false;
private readonly i18n = inject(AppI18nService);
readonly allUsers = this.store.selectSignal(selectAllUsers);
readonly currentUser = this.store.selectSignal(selectCurrentUser);
@@ -446,7 +449,7 @@ export class PrivateCallComponent {
}
participantIssueLabel(user: User): string | null {
return this.isParticipantConnected(user) ? null : 'Waiting';
return this.isParticipantConnected(user) ? null : this.i18n.instant('call.private.waiting');
}
streamLabel(share: VoiceWorkspaceStreamItem): string {
@@ -454,7 +457,9 @@ export class PrivateCallComponent {
return share.user.displayName;
}
return share.kind === 'camera' ? 'Your camera' : 'Your screen';
return share.kind === 'camera'
? this.i18n.instant('call.private.yourCamera')
: this.i18n.instant('call.private.yourScreen');
}
focusShare(shareId: string): void {