mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 00:06:23 +00:00
fix: show localized system folder names in the filter rule folder picker
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
// The filter rule's "Move to folder" picker listed the server's folder names
|
||||||
|
// ("Sent Items", "Junk Mail", "Deleted Items") where the rest of the app
|
||||||
|
// shows the localized role names (localizeMailboxName).
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
vi.mock('expo-localization', () => ({ getLocales: () => [] }));
|
||||||
|
|
||||||
|
import { translate } from '../../i18n';
|
||||||
|
import { buildMailboxTargets } from '../../lib/sieve/rule-actions';
|
||||||
|
import { folderOptionLabel, mailboxesByTargetId } from '../filters/folder-option-label';
|
||||||
|
import type { Mailbox } from '../../api/types';
|
||||||
|
|
||||||
|
const mb = (id: string, name: string, role: string | null, parentId: string | null = null): Mailbox => ({
|
||||||
|
id, name, role, parentId, sortOrder: 0, totalEmails: 0, unreadEmails: 0, totalThreads: 0, unreadThreads: 0,
|
||||||
|
myRights: {} as Mailbox['myRights'], isSubscribed: true,
|
||||||
|
} as unknown as Mailbox);
|
||||||
|
|
||||||
|
const mailboxes = [
|
||||||
|
mb('i', 'Inbox', 'inbox'),
|
||||||
|
mb('s', 'Sent Items', 'sent'),
|
||||||
|
mb('j', 'Junk Mail', 'junk'),
|
||||||
|
mb('d', 'Deleted Items', 'trash'),
|
||||||
|
mb('p', 'Projects', null),
|
||||||
|
mb('a', 'Alpha', null, 'p'),
|
||||||
|
];
|
||||||
|
|
||||||
|
function labels(locale: 'en' | 'de') {
|
||||||
|
const t = (key: string, fallback?: string) => translate(locale, key, fallback);
|
||||||
|
const byId = mailboxesByTargetId(mailboxes);
|
||||||
|
return Object.fromEntries(buildMailboxTargets(mailboxes).map((target) => [target.id, folderOptionLabel(target, byId.get(target.id), t)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('filter rule folder picker labels', () => {
|
||||||
|
it('names system folders by role, like the sidebar', () => {
|
||||||
|
const en = labels('en');
|
||||||
|
expect(en.s).toBe(translate('en', 'sidebar.mailboxes.sent'));
|
||||||
|
expect(en.j).toBe(translate('en', 'sidebar.mailboxes.spam'));
|
||||||
|
expect(en.d).toBe(translate('en', 'sidebar.mailboxes.trash'));
|
||||||
|
expect(en.s).not.toBe('Sent Items');
|
||||||
|
expect(labels('de').d).toBe(translate('de', 'sidebar.mailboxes.trash'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps user folders and their indent', () => {
|
||||||
|
const en = labels('en');
|
||||||
|
expect(en.p).toBe('Projects');
|
||||||
|
expect(en.a).toBe(' Alpha');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is what the rule editor lists', () => {
|
||||||
|
const source = fs.readFileSync(path.resolve(__dirname, '../filters/FilterRuleModal.tsx'), 'utf8');
|
||||||
|
expect(source).toContain('label: folderOptionLabel(target, mailboxById.get(target.id), t)');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -45,6 +45,7 @@ import { SafeAreaModal } from '../SafeAreaModal';
|
|||||||
import { useFilterStore } from '../../stores/filter-store';
|
import { useFilterStore } from '../../stores/filter-store';
|
||||||
import { forwardsOfEditedRule, ruleForwardLimit } from '../../lib/sieve/forward-limit';
|
import { forwardsOfEditedRule, ruleForwardLimit } from '../../lib/sieve/forward-limit';
|
||||||
import { MODAL_ORIENTATIONS } from '../../lib/modal-orientations';
|
import { MODAL_ORIENTATIONS } from '../../lib/modal-orientations';
|
||||||
|
import { folderOptionLabel, mailboxesByTargetId } from './folder-option-label';
|
||||||
import { useModalKeyboardPadding } from '../../lib/sheet-bottom-inset';
|
import { useModalKeyboardPadding } from '../../lib/sheet-bottom-inset';
|
||||||
|
|
||||||
const ALL_FIELDS: FilterConditionField[] = ['from', 'to', 'cc', 'subject', 'header', 'size', 'body', 'attachment', 'all'];
|
const ALL_FIELDS: FilterConditionField[] = ['from', 'to', 'cc', 'subject', 'header', 'size', 'body', 'attachment', 'all'];
|
||||||
@@ -131,6 +132,7 @@ export function FilterRuleModal({
|
|||||||
const periodProblem = periodOn ? periodError(activeFrom, activeUntil) : null;
|
const periodProblem = periodOn ? periodError(activeFrom, activeUntil) : null;
|
||||||
|
|
||||||
const mailboxTargets = useMemo(() => buildMailboxTargets(mailboxes), [mailboxes]);
|
const mailboxTargets = useMemo(() => buildMailboxTargets(mailboxes), [mailboxes]);
|
||||||
|
const mailboxById = useMemo(() => mailboxesByTargetId(mailboxes), [mailboxes]);
|
||||||
|
|
||||||
// A message can collect only so many forwards on the server (#1155, #1208).
|
// A message can collect only so many forwards on the server (#1155, #1208).
|
||||||
const listedRules = useFilterStore((s) => s.rules);
|
const listedRules = useFilterStore((s) => s.rules);
|
||||||
@@ -216,7 +218,10 @@ export function FilterRuleModal({
|
|||||||
// listed (deleted, or its account's folders not loaded) stays selectable
|
// listed (deleted, or its account's folders not loaded) stays selectable
|
||||||
// under its path, so saving doesn't silently retarget the rule.
|
// under its path, so saving doesn't silently retarget the rule.
|
||||||
const folderOptions = (action: FilterAction) => {
|
const folderOptions = (action: FilterAction) => {
|
||||||
const options = mailboxTargets.map((target) => ({ value: target.id, label: target.label }));
|
const options = mailboxTargets.map((target) => ({
|
||||||
|
value: target.id,
|
||||||
|
label: folderOptionLabel(target, mailboxById.get(target.id), t),
|
||||||
|
}));
|
||||||
const selected = mailboxIdFor(action, mailboxTargets);
|
const selected = mailboxIdFor(action, mailboxTargets);
|
||||||
if (!options.some((o) => o.value === selected) && (selected || action.value)) {
|
if (!options.some((o) => o.value === selected) && (selected || action.value)) {
|
||||||
options.unshift({ value: selected, label: action.value || selected });
|
options.unshift({ value: selected, label: action.value || selected });
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { localizeMailboxName } from '../../lib/mailbox-label';
|
||||||
|
import type { MailboxTarget } from '../../lib/sieve/rule-actions';
|
||||||
|
import type { Mailbox } from '../../api/types';
|
||||||
|
|
||||||
|
type Translate = (key: string, fallback?: string) => string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter rule's folder picker row: the folder's indent and its name as the
|
||||||
|
* rest of the app shows it. The targets carry the server name, so system
|
||||||
|
* folders read "Sent Items", "Junk Mail" or "Deleted Items" here and
|
||||||
|
* "Sent", "Spam" or "Trash" everywhere else. The rule itself keeps the
|
||||||
|
* server path (fileinto needs it); only the label changes.
|
||||||
|
*/
|
||||||
|
export function folderOptionLabel(
|
||||||
|
target: MailboxTarget,
|
||||||
|
mailbox: Pick<Mailbox, 'role' | 'name'> | undefined,
|
||||||
|
t: Translate,
|
||||||
|
): string {
|
||||||
|
if (!mailbox?.role) return target.label;
|
||||||
|
const indent = /^ */.exec(target.label)?.[0] ?? '';
|
||||||
|
return indent + localizeMailboxName(mailbox.role, mailbox.name, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mailboxes by the id the targets use (the server id of a shared folder). */
|
||||||
|
export function mailboxesByTargetId(mailboxes: Mailbox[]): Map<string, Mailbox> {
|
||||||
|
return new Map(mailboxes.map((m) => [m.originalId ?? m.id, m]));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user