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

@@ -44,6 +44,7 @@ import { hasDedicatedChatEmbed } from '../../domains/chat/domain/rules/link-embe
import { LinkMetadataService } from '../../domains/chat/application/services/link-metadata.service';
import { TimeSyncService } from '../../core/services/time-sync.service';
import { PlatformService } from '../../core/platform';
import { AppI18nService } from '../../core/i18n';
import {
DELETED_MESSAGE_CONTENT,
Message,
@@ -71,6 +72,7 @@ export class MessagesEffects {
private readonly timeSync = inject(TimeSyncService);
private readonly linkMetadata = inject(LinkMetadataService);
private readonly platform = inject(PlatformService);
private readonly i18n = inject(AppI18nService);
/** Loads messages for a room from the local database, hydrating reactions. */
loadMessages$ = createEffect(() =>
@@ -230,7 +232,7 @@ export class MessagesEffects {
currentRoom
]) => {
if (!currentUser || !currentRoom) {
return of(MessagesActions.sendMessageFailure({ error: 'Not connected to a room' }));
return of(MessagesActions.sendMessageFailure({ error: this.i18n.instant('chat.effects.notConnectedToRoom') }));
}
const message: Message = {
@@ -278,17 +280,17 @@ export class MessagesEffects {
withLatestFrom(this.store.select(selectCurrentUser)),
switchMap(([{ messageId, content }, currentUser]) => {
if (!currentUser) {
return of(MessagesActions.editMessageFailure({ error: 'Not logged in' }));
return of(MessagesActions.editMessageFailure({ error: this.i18n.instant('chat.effects.notLoggedIn') }));
}
return from(this.db.getMessageById(messageId)).pipe(
mergeMap((existing) => {
if (!existing) {
return of(MessagesActions.editMessageFailure({ error: 'Message not found' }));
return of(MessagesActions.editMessageFailure({ error: this.i18n.instant('chat.effects.messageNotFound') }));
}
if (!canEditMessage(existing, currentUser.id)) {
return of(MessagesActions.editMessageFailure({ error: 'Cannot edit others messages' }));
return of(MessagesActions.editMessageFailure({ error: this.i18n.instant('chat.effects.cannotEditOthers') }));
}
const editedAt = this.timeSync.now();
@@ -329,17 +331,17 @@ export class MessagesEffects {
withLatestFrom(this.store.select(selectCurrentUser)),
switchMap(([{ messageId }, currentUser]) => {
if (!currentUser) {
return of(MessagesActions.deleteMessageFailure({ error: 'Not logged in' }));
return of(MessagesActions.deleteMessageFailure({ error: this.i18n.instant('chat.effects.notLoggedIn') }));
}
return from(this.db.getMessageById(messageId)).pipe(
mergeMap((existing) => {
if (!existing) {
return of(MessagesActions.deleteMessageFailure({ error: 'Message not found' }));
return of(MessagesActions.deleteMessageFailure({ error: this.i18n.instant('chat.effects.messageNotFound') }));
}
if (!canEditMessage(existing, currentUser.id)) {
return of(MessagesActions.deleteMessageFailure({ error: 'Cannot delete others messages' }));
return of(MessagesActions.deleteMessageFailure({ error: this.i18n.instant('chat.effects.cannotDeleteOthers') }));
}
const deletedAt = this.timeSync.now();
@@ -388,13 +390,13 @@ export class MessagesEffects {
currentRoom
]) => {
if (!currentUser) {
return of(MessagesActions.deleteMessageFailure({ error: 'Not logged in' }));
return of(MessagesActions.deleteMessageFailure({ error: this.i18n.instant('chat.effects.notLoggedIn') }));
}
const hasPermission = !!currentRoom && resolveRoomPermission(currentRoom, currentUser, 'deleteMessages');
if (!hasPermission) {
return of(MessagesActions.deleteMessageFailure({ error: 'Permission denied' }));
return of(MessagesActions.deleteMessageFailure({ error: this.i18n.instant('chat.effects.permissionDenied') }));
}
const deletedAt = this.timeSync.now();

View File

@@ -41,6 +41,7 @@ import {
loadLastViewedChatFromStorage,
saveLastViewedChatToStorage
} from '../../infrastructure/persistence';
import { AppI18nService } from '../../core/i18n';
import { ServerDirectoryFacade } from '../../domains/server-directory';
import { hasRoomBanForUser } from '../../domains/access-control';
import { Room } from '../../shared-kernel';
@@ -71,6 +72,7 @@ export class RoomsEffects {
private db = inject(DatabaseService);
private webrtc = inject(RealtimeSessionFacade);
private serverDirectory = inject(ServerDirectoryFacade);
private readonly i18n = inject(AppI18nService);
private readonly signalingConnection = new RoomSignalingConnection(
this.webrtc,
@@ -203,7 +205,7 @@ export class RoomsEffects {
withLatestFrom(this.store.select(selectCurrentUser)),
switchMap(([{ name, description, topic, isPrivate, password, sourceId, sourceUrl }, currentUser]) => {
if (!currentUser) {
return of(RoomsActions.createRoomFailure({ error: 'Not logged in' }));
return of(RoomsActions.createRoomFailure({ error: this.i18n.instant('servers.errors.notLoggedIn') }));
}
const allEndpoints = this.serverDirectory.servers();
@@ -276,7 +278,7 @@ export class RoomsEffects {
withLatestFrom(this.store.select(selectCurrentUser)),
switchMap(([{ roomId, password: _password, serverInfo }, currentUser]) => {
if (!currentUser) {
return of(RoomsActions.joinRoomFailure({ error: 'Not logged in' }));
return of(RoomsActions.joinRoomFailure({ error: this.i18n.instant('servers.errors.notLoggedIn') }));
}
return from(this.getBlockedRoomAccessActions(roomId, currentUser)).pipe(
@@ -417,9 +419,9 @@ export class RoomsEffects {
return of(RoomsActions.joinRoomSuccess({ room: newRoom }));
}
return of(RoomsActions.joinRoomFailure({ error: 'Room not found' }));
return of(RoomsActions.joinRoomFailure({ error: this.i18n.instant('servers.errors.roomNotFound') }));
}),
catchError(() => of(RoomsActions.joinRoomFailure({ error: 'Room not found' })))
catchError(() => of(RoomsActions.joinRoomFailure({ error: this.i18n.instant('servers.errors.roomNotFound') })))
);
}),
catchError((error) => of(RoomsActions.joinRoomFailure({ error: error.message })))
@@ -619,7 +621,7 @@ export class RoomsEffects {
savedRooms
]) => {
if (!user) {
return of(RoomsActions.joinRoomFailure({ error: 'Not logged in' }));
return of(RoomsActions.joinRoomFailure({ error: this.i18n.instant('servers.errors.notLoggedIn') }));
}
const activateViewedRoom = () => {
@@ -883,7 +885,7 @@ export class RoomsEffects {
return [];
}
const blockedActions: BlockedRoomAccessAction[] = [RoomsActions.joinRoomFailure({ error: 'You are banned from this server' })];
const blockedActions: BlockedRoomAccessAction[] = [RoomsActions.joinRoomFailure({ error: this.i18n.instant('servers.errors.banned') })];
const storedRoom = await this.db.getRoom(roomId);
if (storedRoom) {

View File

@@ -49,6 +49,7 @@ import {
} from '../../shared-kernel';
import { setStoredCurrentUserId } from '../../core/storage/current-user-storage';
import { findRoomMember, removeRoomMember } from '../rooms/room-members.helpers';
import { AppI18nService } from '../../core/i18n';
type IncomingModerationExtraAction =
| ReturnType<typeof RoomsActions.forgetRoom>
@@ -66,6 +67,7 @@ export class UsersEffects {
private db = inject(DatabaseService);
private serverDirectory = inject(ServerDirectoryFacade);
private webrtc = inject(RealtimeSessionFacade);
private readonly i18n = inject(AppI18nService);
/** Prepares persisted state for a successful login before exposing the user in-memory. */
authenticateUser$ = createEffect(() =>
@@ -81,7 +83,9 @@ export class UsersEffects {
RoomsActions.loadRooms()
]),
catchError((error: Error) =>
of(UsersActions.loadCurrentUserFailure({ error: error.message || 'Failed to prepare local user state.' }))
of(UsersActions.loadCurrentUserFailure({
error: error.message || this.i18n.instant('auth.users.prepareStateFailed')
}))
)
)
)
@@ -97,7 +101,7 @@ export class UsersEffects {
from(this.db.getCurrentUser()).pipe(
switchMap((user) => {
if (!user) {
return of(UsersActions.loadCurrentUserFailure({ error: 'No current user' }));
return of(UsersActions.loadCurrentUserFailure({ error: this.i18n.instant('auth.users.noCurrentUser') }));
}
const sanitizedUser = this.clearStartupVoiceConnection(user);
@@ -514,7 +518,7 @@ export class UsersEffects {
return displayName;
}
return user.username?.trim() || 'User';
return user.username?.trim() || this.i18n.instant('common.defaultUser');
}
private toSourceSelector(room: Room): { sourceId?: string; sourceUrl?: string } {