Internationalization: 7 languages with live switching
Custom signal-based runtime (no i18n dependency): shared src/shared/i18n dictionaries, ui I18nService.t() reactive by construction so language switches live, and a backend i18n.ts for native menus + thrown errors. Every user-facing string keyed across templates, toasts, dialogs, file kinds, columns, macOS menu/context menus, and errors. Language picker in Options (auto-detects OS locale, falls back to English); switching relabels the webview and native chrome instantly. English/Spanish/French/ German fully drafted; Chinese/Japanese/Basque drafted, flagged for native review. Docs updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { I18nService } from '../i18n.service';
|
||||
import type { TransferSnapshot } from '@shared/types';
|
||||
import { formatSpeed } from '../format';
|
||||
import { PanelsService } from '../panels.service';
|
||||
@@ -17,26 +18,26 @@ import { TransfersService } from '../transfers.service';
|
||||
<button
|
||||
(click)="transfers.expanded.set(!transfers.expanded())"
|
||||
class="flex min-w-0 items-center gap-1.5 hover:text-foreground"
|
||||
title="Show transfer details"
|
||||
[title]="i18n.t('transfer.showDetails')"
|
||||
>
|
||||
@if (transfers.currentJob(); as job) {
|
||||
<ng-icon name="mynaSpinner" size="0.8rem" class="shrink-0 animate-spin text-primary" />
|
||||
<span class="truncate">
|
||||
Copying {{ job.name }} · {{ percent(job) }}%{{ speedSuffix(job) }}
|
||||
{{ i18n.t('transfer.copying', { name: job.name, percent: percent(job), speed: speedSuffix(job) }) }}
|
||||
@if (transfers.queuedCount(); as queued) {
|
||||
· {{ queued }} queued
|
||||
· {{ i18n.t('transfer.queued', { count: queued }) }}
|
||||
}
|
||||
</span>
|
||||
} @else if (transfers.failedJobs().length; as failed) {
|
||||
<ng-icon name="mynaXCircle" size="0.8rem" class="shrink-0 text-destructive" />
|
||||
<span class="text-destructive">
|
||||
{{ failed }} transfer{{ failed === 1 ? '' : 's' }} failed
|
||||
{{ i18n.t('transfer.failed', { count: failed }) }}
|
||||
</span>
|
||||
} @else if (transfers.hasJobs()) {
|
||||
<ng-icon name="mynaCheckCircle" size="0.8rem" class="shrink-0 text-success" />
|
||||
<span>Transfers finished</span>
|
||||
<span>{{ i18n.t('transfer.finished') }}</span>
|
||||
} @else {
|
||||
<span>Ready</span>
|
||||
<span>{{ i18n.t('transfer.ready') }}</span>
|
||||
}
|
||||
</button>
|
||||
|
||||
@@ -47,16 +48,19 @@ import { TransfersService } from '../transfers.service';
|
||||
size="0.8rem"
|
||||
[class.text-primary]="activePanel().sessionId() !== 'local'"
|
||||
/>
|
||||
<span class="max-w-56 truncate">{{ activePanel().title() }}</span>
|
||||
<span>· {{ activePanel().sorted().length }} items</span>
|
||||
<span class="max-w-56 truncate">
|
||||
{{ activePanel().sessionId() === 'local' ? i18n.t('common.local') : activePanel().title() }}
|
||||
</span>
|
||||
<span>· {{ i18n.t('transfer.items', { count: activePanel().sorted().length }) }}</span>
|
||||
@if (activePanel().selected().size; as count) {
|
||||
<span>· {{ count }} selected</span>
|
||||
<span>· {{ i18n.t('transfer.selected', { count }) }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class StatusBar {
|
||||
readonly i18n = inject(I18nService);
|
||||
readonly transfers = inject(TransfersService);
|
||||
readonly panels = inject(PanelsService);
|
||||
readonly settings = inject(SettingsService);
|
||||
|
||||
Reference in New Issue
Block a user