fix(assistant): answer Brazilian Portuguese users in Portuguese, not Breton

TREK's code for Brazilian Portuguese is 'br', which Intl reads as Breton.
The assistant passed it straight to Intl.DisplayNames for the system
prompt's answer language and to the card formatters, so the model was told
to answer in Breton. Convert TREK codes to their BCP-47 tag with
getIntlLanguage before handing them to Intl.

Closes #2800
This commit is contained in:
Allan Braun
2026-10-25 17:14:16 -03:00
committed by Maurice
parent d07c17ded6
commit 69ff295184
5 changed files with 20 additions and 2 deletions
@@ -1,3 +1,4 @@
import { intlLocale } from './assistant-words';
import { fileKeyOf } from './cards/card-values';
import type { AssistantFocus, AssistantMention } from '@trek/shared';
@@ -412,7 +413,7 @@ export function todayIn(timeZone: string, now: Date): string {
/** The English name of a locale's language, for the instruction to answer in it. */
export function languageName(locale: string): string {
try {
return new Intl.DisplayNames(['en'], { type: 'language' }).of(locale) ?? locale;
return new Intl.DisplayNames(['en'], { type: 'language' }).of(intlLocale(locale)) ?? locale;
} catch {
return locale;
}
@@ -17,6 +17,15 @@ export function supportedLocale(locale: string): string {
return SUPPORTED_LANGUAGE_CODES.includes(language) ? language : 'en';
}
/**
* The BCP 47 tag of a request's locale, for Intl: a TREK code reads as its
* language ('br' is Brazilian Portuguese here, Breton to Intl), anything else
* is passed through.
*/
export function intlLocale(locale: string): string {
return SUPPORTED_LANGUAGE_CODES.includes(locale) ? getIntlLanguage(locale) : locale;
}
/** The words of one locale's table, or none when it cannot be read. */
export async function localeTable(code: string): Promise<Table> {
try {
@@ -1,3 +1,4 @@
import { intlLocale } from '../assistant-words';
import type { PreviewContext } from './card-types';
import type { AssistantChangeLabelKey, AssistantChangeRow } from '@trek/shared';
@@ -57,7 +58,7 @@ export class Rows {
/** A locale Intl accepts, the user's when it can. */
export function localeOf(ctx: Pick<PreviewContext, 'locale'>): string {
try {
return Intl.getCanonicalLocales(ctx.locale)[0] ?? 'en';
return Intl.getCanonicalLocales(intlLocale(ctx.locale))[0] ?? 'en';
} catch {
return 'en';
}
@@ -231,6 +231,12 @@ describe('the turn context', () => {
expect(languageName('!!')).toBe('!!');
});
it('ASTCTX-033: reads TREK language codes as their language, not as the BCP 47 tag they spell', () => {
expect(languageName('br')).toBe('Brazilian Portuguese');
expect(languageName('gr')).toBe('Greek');
expect(languageName('pt-BR')).toBe('Brazilian Portuguese');
});
it('ASTCTX-022: carries the date, the units the user prefers and the card', () => {
const card = buildTripCard(1, 7, SUMMARY, PLACES, null);
const context = turnContext(card, 'Asia/Tokyo', new Date('2026-06-11T23:30:00Z'), {
@@ -69,6 +69,7 @@ describe('dayLabel', () => {
expect(dayLabel(ctx, 99)).toBe('#99');
expect(dayLabel({ ...ctx, locale: 'de' }, 32)).toBe('Fr., 12. Juni');
expect(dayLabel({ ...ctx, locale: 'not a locale!' }, 32)).toBe('Fri, Jun 12');
expect(dayLabel({ ...ctx, locale: 'br' }, 32)).toBe(dayLabel({ ...ctx, locale: 'pt-BR' }, 32));
});
});