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
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:
@@ -21,6 +21,7 @@ import { selectCurrentUser } from '../../../../store/users/users.selectors';
|
||||
import { DesktopNotificationService } from '../../infrastructure/services/desktop-notification.service';
|
||||
import { NotificationSettingsStorageService } from '../../infrastructure/services/notification-settings-storage.service';
|
||||
import { createDefaultNotificationSettings, type NotificationsSettings } from '../../domain/models/notification.model';
|
||||
import { initializeAppI18nForTests, provideAppI18nForTests } from '../../../../core/i18n/app-i18n.testing';
|
||||
import { NotificationsService } from './notifications.service';
|
||||
|
||||
const alice: User = {
|
||||
@@ -165,6 +166,7 @@ function createServiceContext(options: ServiceContextOptions): ServiceContext {
|
||||
|
||||
const injector = Injector.create({
|
||||
providers: [
|
||||
...provideAppI18nForTests(),
|
||||
{
|
||||
provide: DatabaseService,
|
||||
useValue: {
|
||||
@@ -228,6 +230,8 @@ function createServiceContext(options: ServiceContextOptions): ServiceContext {
|
||||
]
|
||||
});
|
||||
|
||||
initializeAppI18nForTests(injector);
|
||||
|
||||
return {
|
||||
service: runInInjectionContext(injector, () => new NotificationsService())
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { Store } from '@ngrx/store';
|
||||
import type { Message, Room } from '../../../../shared-kernel';
|
||||
import { NotificationAudioService, AppSound } from '../../../../core/services/notification-audio.service';
|
||||
import { AppI18nService } from '../../../../core/i18n';
|
||||
import { TimeSyncService } from '../../../../core/services/time-sync.service';
|
||||
import { DatabaseService } from '../../../../infrastructure/persistence';
|
||||
import {
|
||||
@@ -48,6 +49,7 @@ export class NotificationsService {
|
||||
private readonly store = inject(Store);
|
||||
private readonly db = inject(DatabaseService);
|
||||
private readonly audio = inject(NotificationAudioService);
|
||||
private readonly appI18n = inject(AppI18nService);
|
||||
private readonly timeSync = inject(TimeSyncService);
|
||||
private readonly desktopNotifications = inject(DesktopNotificationService);
|
||||
private readonly storage = inject(NotificationSettingsStorageService);
|
||||
@@ -221,7 +223,8 @@ export class NotificationsService {
|
||||
message,
|
||||
room,
|
||||
this._settings(),
|
||||
!context.isWindowFocused || !context.isDocumentVisible
|
||||
!context.isWindowFocused || !context.isDocumentVisible,
|
||||
(key, params) => this.appI18n.instant(key, params)
|
||||
);
|
||||
|
||||
if (this.shouldPlayNotificationSound()) {
|
||||
|
||||
@@ -10,6 +10,8 @@ export const DEFAULT_TEXT_CHANNEL_ID = 'general';
|
||||
|
||||
const MESSAGE_PREVIEW_LIMIT = 140;
|
||||
|
||||
export type AppTranslateFn = (key: string, params?: Record<string, string | number>) => string;
|
||||
|
||||
export function resolveMessageChannelId(message: Pick<Message, 'channelId'>): string {
|
||||
return message.channelId || DEFAULT_TEXT_CHANNEL_ID;
|
||||
}
|
||||
@@ -98,17 +100,18 @@ export function buildNotificationDisplayPayload(
|
||||
message: Pick<Message, 'channelId' | 'content' | 'senderName'>,
|
||||
room: Room | null,
|
||||
settings: NotificationsSettings,
|
||||
requestAttention: boolean
|
||||
requestAttention: boolean,
|
||||
translate: AppTranslateFn
|
||||
): NotificationDisplayPayload {
|
||||
const channelId = resolveMessageChannelId(message);
|
||||
const roomName = room?.name || 'Server';
|
||||
const roomName = room?.name || translate('notifications.display.defaultServerName');
|
||||
const channelLabel = getChannelLabel(room, channelId);
|
||||
|
||||
return {
|
||||
title: `${roomName} · #${channelLabel}`,
|
||||
body: settings.showPreview
|
||||
? formatMessagePreview(message.senderName, message.content)
|
||||
: `${message.senderName} sent a new message`,
|
||||
? formatMessagePreview(message.senderName, message.content, translate)
|
||||
: translate('notifications.display.newMessageHidden', { sender: message.senderName }),
|
||||
requestAttention
|
||||
};
|
||||
}
|
||||
@@ -147,16 +150,16 @@ export function calculateUnreadForRoom(
|
||||
};
|
||||
}
|
||||
|
||||
function formatMessagePreview(senderName: string, content: string): string {
|
||||
function formatMessagePreview(senderName: string, content: string, translate: AppTranslateFn): string {
|
||||
const normalisedContent = content.replace(/\s+/g, ' ').trim();
|
||||
|
||||
if (!normalisedContent) {
|
||||
return `${senderName} sent a new message`;
|
||||
return translate('notifications.display.newMessageEmpty', { sender: senderName });
|
||||
}
|
||||
|
||||
const preview = normalisedContent.length > MESSAGE_PREVIEW_LIMIT
|
||||
? `${normalisedContent.slice(0, MESSAGE_PREVIEW_LIMIT - 1)}...`
|
||||
: normalisedContent;
|
||||
|
||||
return `${senderName}: ${preview}`;
|
||||
return translate('notifications.display.preview', { sender: senderName, content: preview });
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<h4 class="text-base font-semibold text-foreground">Delivery</h4>
|
||||
<h4 class="text-base font-semibold text-foreground">{{ 'notifications.delivery.title' | translate }}</h4>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Desktop alerts use the system notification center on Linux and request taskbar attention when the app is not focused. Maximized app window
|
||||
suppress system popups, and only play the configured notification sound while the app is in the background.
|
||||
{{ 'notifications.delivery.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,8 +19,8 @@
|
||||
<div class="mt-5 space-y-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p class="font-medium text-foreground">Enable notifications</p>
|
||||
<p class="text-sm text-muted-foreground">Mute every server and channel notification without affecting unread indicators.</p>
|
||||
<p class="font-medium text-foreground">{{ 'notifications.enable.label' | translate }}</p>
|
||||
<p class="text-sm text-muted-foreground">{{ 'notifications.enable.description' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<label class="relative inline-flex cursor-pointer items-center">
|
||||
@@ -45,8 +44,8 @@
|
||||
/>
|
||||
|
||||
<div>
|
||||
<p class="font-medium text-foreground">Show message preview</p>
|
||||
<p class="text-sm text-muted-foreground">Include a short message preview in desktop notifications when content privacy allows it.</p>
|
||||
<p class="font-medium text-foreground">{{ 'notifications.showPreview.label' | translate }}</p>
|
||||
<p class="text-sm text-muted-foreground">{{ 'notifications.showPreview.description' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,8 +70,8 @@
|
||||
/>
|
||||
|
||||
<div>
|
||||
<p class="font-medium text-foreground">Respect busy status</p>
|
||||
<p class="text-sm text-muted-foreground">Suppress desktop alerts while your user presence is set to busy.</p>
|
||||
<p class="font-medium text-foreground">{{ 'notifications.respectBusy.label' | translate }}</p>
|
||||
<p class="text-sm text-muted-foreground">{{ 'notifications.respectBusy.description' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,16 +100,16 @@
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<h4 class="text-base font-semibold text-foreground">Server Overrides</h4>
|
||||
<h4 class="text-base font-semibold text-foreground">{{ 'notifications.serverOverrides.title' | translate }}</h4>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Right-click actions mirror these switches. Muted servers and channels still collect unread badges so you can catch up later.
|
||||
{{ 'notifications.serverOverrides.description' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (rooms().length === 0) {
|
||||
<div class="mt-5 rounded-lg border border-dashed border-border bg-secondary/20 p-4 text-sm text-muted-foreground">
|
||||
Join a server to configure notification overrides.
|
||||
{{ 'notifications.serverOverrides.empty' | translate }}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="mt-5 space-y-4">
|
||||
@@ -122,11 +121,13 @@
|
||||
<p class="truncate font-medium text-foreground">{{ room.name }}</p>
|
||||
@if (roomUnreadCount(room.id) > 0) {
|
||||
<span class="rounded-full bg-amber-400/20 px-2 py-0.5 text-[11px] font-semibold text-amber-300">
|
||||
{{ formatUnreadCount(roomUnreadCount(room.id)) }} unread
|
||||
{{ 'notifications.serverOverrides.unread' | translate: { count: formatUnreadCount(roomUnreadCount(room.id)) } }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-muted-foreground">{{ room.description || 'Notifications for every text channel in this server.' }}</p>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
{{ room.description || ('notifications.serverOverrides.defaultRoomDescription' | translate) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label class="relative inline-flex cursor-pointer items-center">
|
||||
@@ -144,8 +145,8 @@
|
||||
|
||||
<div class="mt-4 rounded-lg border border-border/70 bg-background/50 p-3">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Channels</p>
|
||||
<p class="text-xs text-muted-foreground">Unread badges remain visible even if muted.</p>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">{{ 'notifications.channels.title' | translate }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ 'notifications.channels.mutedHint' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
|
||||
@@ -16,11 +16,12 @@ import {
|
||||
import { selectSavedRooms } from '../../../../../store/rooms/rooms.selectors';
|
||||
import type { Room } from '../../../../../shared-kernel';
|
||||
import { NotificationsFacade } from '../../../application/facades/notifications.facade';
|
||||
import { APP_TRANSLATE_IMPORTS } from '../../../../../core/i18n';
|
||||
|
||||
@Component({
|
||||
selector: 'app-notifications-settings',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgIcon],
|
||||
imports: [CommonModule, NgIcon, ...APP_TRANSLATE_IMPORTS],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
lucideBell,
|
||||
|
||||
Reference in New Issue
Block a user