Skip to content

Commit 0a6da77

Browse files
committed
feat(i18n): translate CRM list-view & action labels via metadata API
Add a convention-based, framework-level translation pipeline that localizes view and action metadata at the REST boundary. Developers keep authoring plain English label/confirmText/successMessage strings; translations live in the per-locale translation bundle under `objects.<obj>._views.<view_name>.{label,description}` and `objects.<obj>._actions.<action_name>.{label,confirmText,successMessage}`, with object-less actions resolved via top-level `globalActions.<name>`. Spec changes: * Extend ObjectTranslationDataSchema with `_views` and `_actions` records. * Add top-level `globalActions` to TranslationDataSchema. * New i18n-resolver module with `resolveViewLabel`, `resolveViewDescription`, `resolveActionLabel`, `resolveActionConfirm`, `resolveActionSuccess`, `translateView`, `translateAction`, `translateMetadataDocument`. * Resolver fallback chain: requested locale → opts.fallbackChain (default ['en']) → literal label/name; never throws. REST wiring: * RestServer translates view/action metadata documents on the fly when an Accept-Language header (or `?locale=` query, or i18n service default) is supplied and the per-project kernel exposes an `i18n` service. * Adds `Vary: Accept-Language` to translated metadata responses so caches key on locale. CRM translations: * en/zh-CN/ja-JP/es-ES bundles populated with `_views` for all 11 objects (account, contact, lead, opportunity, case, contract, product, quote, task, campaign) and `_actions` for the 5 action files (contact.mark_primary/send_email, lead.convert_lead/create_campaign, case.escalate_case/close_case, opportunity.clone_opportunity/mass_update_stage). * Top-level globalActions for log_call + export_csv added in all 4 locales. * ja-JP/es-ES gain stub object entries for the 6 objects that previously only had label/pluralLabel translations in en.ts. Tests: * spec: 6840/6840 pass (added schema parsing + resolver fallback + translateMetadataDocument coverage). * rest: 53/53 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2e8e45a commit 0a6da77

9 files changed

Lines changed: 1222 additions & 3 deletions

File tree

examples/app-crm/src/translations/en.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ export const en: TranslationData = {
3939
is_active: { label: 'Active' },
4040
last_activity_date: { label: 'Last Activity Date' },
4141
},
42+
_views: {
43+
all_accounts: { label: 'All Accounts', description: 'Primary account list with revenue & industry summaries' },
44+
account_gallery: { label: 'Account Cards', description: 'Branded account cards with brand-color highlights' },
45+
account_map: { label: 'Accounts by Location', description: 'Geospatial distribution of accounts' },
46+
enterprise_accounts: { label: 'Enterprise Accounts', description: 'Accounts with the highest annual revenue' },
47+
my_accounts: { label: 'My Accounts', description: 'Accounts owned by the current user' },
48+
},
4249
},
4350

4451
contact: {
@@ -66,6 +73,21 @@ export const en: TranslationData = {
6673
description: { label: 'Description' },
6774
is_primary: { label: 'Primary Contact' },
6875
},
76+
_views: {
77+
all_contacts: { label: 'All Contacts' },
78+
contact_directory: { label: 'People Directory' },
79+
primary_contacts: { label: 'Primary Contacts' },
80+
},
81+
_actions: {
82+
mark_primary: {
83+
label: 'Mark as Primary Contact',
84+
confirmText: 'Mark this contact as the primary contact for the account?',
85+
successMessage: 'Contact marked as primary!',
86+
},
87+
send_email: {
88+
label: 'Send Email',
89+
},
90+
},
6991
},
7092

7193
lead: {
@@ -96,6 +118,25 @@ export const en: TranslationData = {
96118
is_converted: { label: 'Converted' },
97119
description: { label: 'Description' },
98120
},
121+
_views: {
122+
all_leads: { label: 'All Leads' },
123+
kanban_by_status: { label: 'Lead Pipeline' },
124+
calendar_by_created: { label: 'Lead Calendar' },
125+
gallery_view: { label: 'Lead Cards' },
126+
my_leads: { label: 'My Leads' },
127+
high_priority: { label: 'High Priority' },
128+
},
129+
_actions: {
130+
convert_lead: {
131+
label: 'Convert Lead',
132+
confirmText: 'Are you sure you want to convert this lead?',
133+
successMessage: 'Lead converted successfully!',
134+
},
135+
create_campaign: {
136+
label: 'Add to Campaign',
137+
successMessage: 'Leads added to campaign!',
138+
},
139+
},
99140
},
100141

101142
opportunity: {
@@ -137,6 +178,114 @@ export const en: TranslationData = {
137178
description: { label: 'Description' },
138179
next_step: { label: 'Next Step' },
139180
},
181+
_views: {
182+
all_opportunities: { label: 'All Opportunities' },
183+
pipeline_kanban: { label: 'Sales Pipeline' },
184+
close_date_calendar: { label: 'Forecast Calendar' },
185+
deal_timeline: { label: 'Deal Timeline' },
186+
deal_gallery: { label: 'Deal Cards' },
187+
my_open_deals: { label: 'My Open Deals' },
188+
},
189+
_actions: {
190+
clone_opportunity: {
191+
label: 'Clone Opportunity',
192+
successMessage: 'Opportunity cloned successfully!',
193+
},
194+
mass_update_stage: {
195+
label: 'Update Stage',
196+
successMessage: 'Opportunities updated successfully!',
197+
},
198+
},
199+
},
200+
201+
case: {
202+
label: 'Case',
203+
pluralLabel: 'Cases',
204+
_views: {
205+
all_cases: { label: 'All Cases' },
206+
case_workflow: { label: 'Service Workflow' },
207+
sla_calendar: { label: 'SLA Calendar' },
208+
case_timeline: { label: 'Case Timeline' },
209+
escalated_cases: { label: 'Escalated Cases' },
210+
},
211+
_actions: {
212+
escalate_case: {
213+
label: 'Escalate Case',
214+
confirmText: 'This will escalate the case to the escalation team. Continue?',
215+
successMessage: 'Case escalated successfully!',
216+
},
217+
close_case: {
218+
label: 'Close Case',
219+
confirmText: 'Are you sure you want to close this case?',
220+
successMessage: 'Case closed successfully!',
221+
},
222+
},
223+
},
224+
225+
contract: {
226+
label: 'Contract',
227+
pluralLabel: 'Contracts',
228+
_views: {
229+
all_contracts: { label: 'All Contracts' },
230+
renewal_calendar: { label: 'Renewal Calendar' },
231+
contract_gantt: { label: 'Contract Terms' },
232+
contract_timeline: { label: 'Contract Timeline' },
233+
},
234+
},
235+
236+
product: {
237+
label: 'Product',
238+
pluralLabel: 'Products',
239+
_views: {
240+
all_products: { label: 'All Products' },
241+
product_catalog: { label: 'Product Catalog' },
242+
low_stock: { label: 'Low Stock' },
243+
},
244+
},
245+
246+
quote: {
247+
label: 'Quote',
248+
pluralLabel: 'Quotes',
249+
_views: {
250+
all_quotes: { label: 'All Quotes' },
251+
quote_pipeline: { label: 'Quote Pipeline' },
252+
quote_calendar: { label: 'Quote Calendar' },
253+
},
254+
},
255+
256+
task: {
257+
label: 'Task',
258+
pluralLabel: 'Tasks',
259+
_views: {
260+
all_tasks: { label: 'All Tasks' },
261+
task_board: { label: 'Task Board' },
262+
task_calendar: { label: 'Task Schedule' },
263+
task_gantt: { label: 'Execution Plan' },
264+
task_timeline: { label: 'Worklog Timeline' },
265+
my_open_tasks: { label: 'My Open Tasks' },
266+
},
267+
},
268+
269+
campaign: {
270+
label: 'Campaign',
271+
pluralLabel: 'Campaigns',
272+
_views: {
273+
all_campaigns: { label: 'All Campaigns' },
274+
campaign_gantt: { label: 'Campaign Schedule' },
275+
campaign_calendar: { label: 'Launch Calendar' },
276+
campaign_timeline: { label: 'Marketing Timeline' },
277+
},
278+
},
279+
},
280+
281+
globalActions: {
282+
log_call: {
283+
label: 'Log a Call',
284+
successMessage: 'Call logged successfully!',
285+
},
286+
export_csv: {
287+
label: 'Export to CSV',
288+
successMessage: 'Export completed!',
140289
},
141290
},
142291

examples/app-crm/src/translations/es-ES.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ export const esES: TranslationData = {
3838
is_active: { label: 'Activo' },
3939
last_activity_date: { label: 'Fecha de Última Actividad' },
4040
},
41+
_views: {
42+
all_accounts: { label: 'Todas las Cuentas', description: 'Lista maestra de cuentas con ingresos e industria' },
43+
account_gallery: { label: 'Galería de Cuentas', description: 'Vista de tarjetas con colores de marca' },
44+
account_map: { label: 'Mapa de Cuentas', description: 'Distribución geográfica de cuentas' },
45+
enterprise_accounts: { label: 'Cuentas Empresariales', description: 'Cuentas con mayores ingresos anuales' },
46+
my_accounts: { label: 'Mis Cuentas', description: 'Cuentas asignadas al usuario actual' },
47+
},
4148
},
4249

4350
contact: {
@@ -65,6 +72,21 @@ export const esES: TranslationData = {
6572
description: { label: 'Descripción' },
6673
is_primary: { label: 'Contacto Principal' },
6774
},
75+
_views: {
76+
all_contacts: { label: 'Todos los Contactos' },
77+
contact_directory: { label: 'Directorio de Contactos' },
78+
primary_contacts: { label: 'Contactos Principales' },
79+
},
80+
_actions: {
81+
mark_primary: {
82+
label: 'Marcar como Principal',
83+
confirmText: '¿Establecer este contacto como contacto principal de la cuenta?',
84+
successMessage: '¡Establecido como contacto principal!',
85+
},
86+
send_email: {
87+
label: 'Enviar Correo',
88+
},
89+
},
6890
},
6991

7092
lead: {
@@ -95,6 +117,25 @@ export const esES: TranslationData = {
95117
is_converted: { label: 'Convertido' },
96118
description: { label: 'Descripción' },
97119
},
120+
_views: {
121+
all_leads: { label: 'Todos los Prospectos' },
122+
kanban_by_status: { label: 'Pipeline de Prospectos' },
123+
calendar_by_created: { label: 'Calendario de Prospectos' },
124+
gallery_view: { label: 'Galería de Prospectos' },
125+
my_leads: { label: 'Mis Prospectos' },
126+
high_priority: { label: 'Alta Prioridad' },
127+
},
128+
_actions: {
129+
convert_lead: {
130+
label: 'Convertir Prospecto',
131+
confirmText: '¿Está seguro de querer convertir este prospecto?',
132+
successMessage: '¡Prospecto convertido con éxito!',
133+
},
134+
create_campaign: {
135+
label: 'Agregar a Campaña',
136+
successMessage: '¡Prospecto agregado a la campaña!',
137+
},
138+
},
98139
},
99140

100141
opportunity: {
@@ -136,6 +177,114 @@ export const esES: TranslationData = {
136177
description: { label: 'Descripción' },
137178
next_step: { label: 'Próximo Paso' },
138179
},
180+
_views: {
181+
all_opportunities: { label: 'Todas las Oportunidades' },
182+
pipeline_kanban: { label: 'Pipeline de Ventas' },
183+
close_date_calendar: { label: 'Calendario de Pronóstico' },
184+
deal_timeline: { label: 'Línea de Tiempo' },
185+
deal_gallery: { label: 'Galería de Negocios' },
186+
my_open_deals: { label: 'Mis Negocios Abiertos' },
187+
},
188+
_actions: {
189+
clone_opportunity: {
190+
label: 'Clonar Oportunidad',
191+
successMessage: '¡Oportunidad clonada con éxito!',
192+
},
193+
mass_update_stage: {
194+
label: 'Actualizar Etapa',
195+
successMessage: '¡Etapa de oportunidad actualizada!',
196+
},
197+
},
198+
},
199+
200+
case: {
201+
label: 'Caso',
202+
pluralLabel: 'Casos',
203+
_views: {
204+
all_cases: { label: 'Todos los Casos' },
205+
case_workflow: { label: 'Flujo de Servicio' },
206+
sla_calendar: { label: 'Calendario SLA' },
207+
case_timeline: { label: 'Línea de Tiempo de Casos' },
208+
escalated_cases: { label: 'Casos Escalados' },
209+
},
210+
_actions: {
211+
escalate_case: {
212+
label: 'Escalar Caso',
213+
confirmText: 'Esto enviará el caso al equipo de escalación. ¿Continuar?',
214+
successMessage: '¡Caso escalado con éxito!',
215+
},
216+
close_case: {
217+
label: 'Cerrar Caso',
218+
confirmText: '¿Está seguro de querer cerrar este caso?',
219+
successMessage: '¡Caso cerrado con éxito!',
220+
},
221+
},
222+
},
223+
224+
contract: {
225+
label: 'Contrato',
226+
pluralLabel: 'Contratos',
227+
_views: {
228+
all_contracts: { label: 'Todos los Contratos' },
229+
renewal_calendar: { label: 'Calendario de Renovación' },
230+
contract_gantt: { label: 'Plazos del Contrato' },
231+
contract_timeline: { label: 'Línea de Tiempo' },
232+
},
233+
},
234+
235+
product: {
236+
label: 'Producto',
237+
pluralLabel: 'Productos',
238+
_views: {
239+
all_products: { label: 'Todos los Productos' },
240+
product_catalog: { label: 'Catálogo de Productos' },
241+
low_stock: { label: 'Stock Bajo' },
242+
},
243+
},
244+
245+
quote: {
246+
label: 'Cotización',
247+
pluralLabel: 'Cotizaciones',
248+
_views: {
249+
all_quotes: { label: 'Todas las Cotizaciones' },
250+
quote_pipeline: { label: 'Pipeline de Cotizaciones' },
251+
quote_calendar: { label: 'Calendario de Cotizaciones' },
252+
},
253+
},
254+
255+
task: {
256+
label: 'Tarea',
257+
pluralLabel: 'Tareas',
258+
_views: {
259+
all_tasks: { label: 'Todas las Tareas' },
260+
task_board: { label: 'Tablero de Tareas' },
261+
task_calendar: { label: 'Calendario de Tareas' },
262+
task_gantt: { label: 'Plan de Ejecución' },
263+
task_timeline: { label: 'Línea de Tiempo' },
264+
my_open_tasks: { label: 'Mis Tareas Abiertas' },
265+
},
266+
},
267+
268+
campaign: {
269+
label: 'Campaña',
270+
pluralLabel: 'Campañas',
271+
_views: {
272+
all_campaigns: { label: 'Todas las Campañas' },
273+
campaign_gantt: { label: 'Programación de Campañas' },
274+
campaign_calendar: { label: 'Calendario de Campañas' },
275+
campaign_timeline: { label: 'Línea de Tiempo de Marketing' },
276+
},
277+
},
278+
},
279+
280+
globalActions: {
281+
log_call: {
282+
label: 'Registrar Llamada',
283+
successMessage: '¡Llamada registrada con éxito!',
284+
},
285+
export_csv: {
286+
label: 'Exportar CSV',
287+
successMessage: '¡Exportación completada!',
139288
},
140289
},
141290

0 commit comments

Comments
 (0)