From 4bee06addbe6436cf02ef526a5d119b3e81637b3 Mon Sep 17 00:00:00 2001 From: Chris Chen Date: Fri, 29 May 2026 22:04:18 -0700 Subject: [PATCH] feat(i18n): add central bilingual option lists for enum dropdowns --- APP/src/app/shared/i18n/option-lists.spec.ts | 37 +++++++++++++ APP/src/app/shared/i18n/option-lists.ts | 57 ++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 APP/src/app/shared/i18n/option-lists.spec.ts create mode 100644 APP/src/app/shared/i18n/option-lists.ts diff --git a/APP/src/app/shared/i18n/option-lists.spec.ts b/APP/src/app/shared/i18n/option-lists.spec.ts new file mode 100644 index 0000000..a5f1d91 --- /dev/null +++ b/APP/src/app/shared/i18n/option-lists.spec.ts @@ -0,0 +1,37 @@ +import { + PAYMENT_METHOD_OPTIONS, EXPENSE_STATUS_OPTIONS, MEMBER_STATUS_OPTIONS, + GENDER_OPTIONS, LANGUAGE_OPTIONS, ROLE_OPTIONS, +} from './option-lists'; + +describe('option-lists', () => { + it('payment methods preserve raw values and show bilingual labels', () => { + expect(PAYMENT_METHOD_OPTIONS.map(o => o.value)) + .toEqual(['Cash', 'Check', 'Zelle', 'PayPal', 'Other']); + expect(PAYMENT_METHOD_OPTIONS.find(o => o.value === 'Cash')!.label).toBe('Cash/現金'); + expect(PAYMENT_METHOD_OPTIONS.find(o => o.value === 'Zelle')!.label).toBe('Zelle'); + }); + + it('expense statuses preserve raw enum values', () => { + expect(EXPENSE_STATUS_OPTIONS.map(o => o.value)) + .toEqual(['Draft', 'PendingApproval', 'Approved', 'Paid', 'Rejected']); + }); + + it('member statuses preserve raw values', () => { + expect(MEMBER_STATUS_OPTIONS.map(o => o.value)) + .toEqual(['Member', 'Visitor', 'Inactive', 'Former']); + }); + + it('gender values stay M/F/Other', () => { + expect(GENDER_OPTIONS.map(o => o.value)).toEqual(['M', 'F', 'Other']); + }); + + it('language values stay en/zh-TW', () => { + expect(LANGUAGE_OPTIONS.map(o => o.value)).toEqual(['en', 'zh-TW']); + }); + + it('role options cover all 13 role codes', () => { + expect(ROLE_OPTIONS.length).toBe(13); + expect(ROLE_OPTIONS.map(o => o.value)).toContain('super_admin'); + expect(ROLE_OPTIONS.map(o => o.value)).toContain('visitor'); + }); +}); diff --git a/APP/src/app/shared/i18n/option-lists.ts b/APP/src/app/shared/i18n/option-lists.ts new file mode 100644 index 0000000..9a63166 --- /dev/null +++ b/APP/src/app/shared/i18n/option-lists.ts @@ -0,0 +1,57 @@ +/** + * Central bilingual option lists for hard-coded enum dropdowns. + * `value` is the raw value stored/sent to the API (unchanged from before); + * `label` is the display string `英文/中文`. Bind in templates with + * textField="label" valueField="value" [valuePrimitive]="true". + */ +export interface BilingualOption { readonly value: string; readonly label: string; } + +export const PAYMENT_METHOD_OPTIONS: readonly BilingualOption[] = [ + { value: 'Cash', label: 'Cash/現金' }, + { value: 'Check', label: 'Check/支票' }, + { value: 'Zelle', label: 'Zelle' }, + { value: 'PayPal', label: 'PayPal' }, + { value: 'Other', label: 'Other/其他' }, +]; + +export const EXPENSE_STATUS_OPTIONS: readonly BilingualOption[] = [ + { value: 'Draft', label: 'Draft/草稿' }, + { value: 'PendingApproval', label: 'PendingApproval/待審核' }, + { value: 'Approved', label: 'Approved/已核准' }, + { value: 'Paid', label: 'Paid/已付款' }, + { value: 'Rejected', label: 'Rejected/已拒絕' }, +]; + +export const MEMBER_STATUS_OPTIONS: readonly BilingualOption[] = [ + { value: 'Member', label: 'Member/會友' }, + { value: 'Visitor', label: 'Visitor/訪客' }, + { value: 'Inactive', label: 'Inactive/未活躍' }, + { value: 'Former', label: 'Former/已離開' }, +]; + +export const GENDER_OPTIONS: readonly BilingualOption[] = [ + { value: 'M', label: 'Male/男' }, + { value: 'F', label: 'Female/女' }, + { value: 'Other', label: 'Other/其他' }, +]; + +export const LANGUAGE_OPTIONS: readonly BilingualOption[] = [ + { value: 'en', label: 'English/英文' }, + { value: 'zh-TW', label: '中文/Chinese' }, +]; + +export const ROLE_OPTIONS: readonly BilingualOption[] = [ + { value: 'super_admin', label: 'Super Admin/系統管理員' }, + { value: 'pastor', label: 'Pastor/牧師' }, + { value: 'board_member', label: 'Board Member/理事' }, + { value: 'coworker_chair', label: 'Coworker Chair/同工會主席' }, + { value: 'ministry_leader', label: 'Ministry Leader/事工領袖' }, + { value: 'district_leader', label: 'District Leader/區長' }, + { value: 'cell_leader', label: 'Cell Leader/小組長' }, + { value: 'coworker', label: 'Coworker/同工' }, + { value: 'finance', label: 'Finance/財務同工' }, + { value: 'secretary', label: 'Secretary/行政秘書' }, + { value: 'worship_leader', label: 'Worship Leader/敬拜領袖' }, + { value: 'member', label: 'Member/一般教友' }, + { value: 'visitor', label: 'Visitor/訪客' }, +];