Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/utils/i18n/client.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { type Locale, de, enUS, es, fr, it, pl, pt, ptBR, sv } from 'date-fns/locale';

export interface SupportedLanguage {
code: string;
name: string;
dateLocale: Locale;
}

export const getSupportedLanguages = (): SupportedLanguage[] => [
{ code: 'en', name: 'English' },
{ code: 'de', name: 'Deutsch' },
{ code: 'fr', name: 'Français' },
{ code: 'it', name: 'Italiano' },
{ code: 'pl', name: 'Polski' },
{ code: 'pt-PT', name: 'Portuguese (Portugal)' },
{ code: 'pt-BR', name: 'Portuguese (Brazil)' },
{ code: 'sv', name: 'Svenska' },
{ code: 'es-MX', name: 'Spanish (Mexico)' },
{ code: 'es-AR', name: 'Spanish (Argentina)' },
{ code: 'en', name: 'English', dateLocale: enUS },
{ code: 'de', name: 'Deutsch', dateLocale: de },
{ code: 'fr', name: 'Français', dateLocale: fr },
{ code: 'it', name: 'Italiano', dateLocale: it },
{ code: 'pl', name: 'Polski', dateLocale: pl },
{ code: 'pt-PT', name: 'Portuguese (Portugal)', dateLocale: pt },
{ code: 'pt-BR', name: 'Portuguese (Brazil)', dateLocale: ptBR },
{ code: 'sv', name: 'Svenska', dateLocale: sv },
{ code: 'es-MX', name: 'Spanish (Mexico)', dateLocale: es },
{ code: 'es-AR', name: 'Spanish (Argentina)', dateLocale: es },
];
21 changes: 19 additions & 2 deletions src/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { format, isToday } from 'date-fns';
import { type TFunction } from 'next-i18next';
import { type CurrencyCode } from '~/lib/currency';
import { type AddExpenseState, type Participant } from '~/store/addStore';
import { getSupportedLanguages } from './i18n/client';

export type ParametersExceptTranslation<F> = F extends (t: TFunction, ...rest: infer R) => any
? R
Expand All @@ -24,8 +25,24 @@ export const toUIDate = (
t: TFunction,
date: Date,
{ useToday = false, year = false } = {},
): string =>
useToday && isToday(date) ? t('ui.today') : format(date, year ? 'dd MMM yyyy' : 'MMM dd');
): string => {
const todayTranslation = t('ui.today', { returnDetails: true });

if (useToday && isToday(date)) {
return todayTranslation.res;
}

if (year) {
return Intl.DateTimeFormat(todayTranslation.usedLng, {
dateStyle: 'long',
}).format(date);
}

return format(date, 'MMM dd', {
locale: getSupportedLanguages().find((lang) => lang.code === todayTranslation.usedLng)
?.dateLocale,
});
Comment on lines +41 to +44
Copy link
Collaborator

@krokosik krokosik Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we could just use new Intl.DateTimeFormat(todayTranslation.usedLng, {day: '2-digit', month: 'short'}).format(date)?

Copy link
Contributor Author

@rodrigost23 rodrigost23 Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that, but this Intl API is kind of weird. At least for pt-BR and pt-PT, the results are awful, so I'm not sure what other languages would look like to native speakers.

pt-PT:

I don't know why it would format "short" month to "11" (we have "numeric" for that, right?). 

pt-BR:

The word "de" here is just a preposition that we can totally ignore when we want it to be shorter.


Maybe we could have a specific function only for this expense date prefix and use the Intl for other places, which would probably make this look better:
 

Copy link
Collaborator

@krokosik krokosik Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a native speaker in either of these, but maybe it is simply the proper way to format this in Portugal? For the brazilian version, yeah it looks kind of bad.

Personally I would prefer fixing locale specific edge cases rather than to import the bulky date-fns locale and pass it around.

For the expense list, we could format the day and month separately and remove dots for now?

new Intl.DateTimeFormat('pt-PT', {month: 'short'}).format(new Date()).replace('.', '')

Actually returns the month name, so we could just built this in a consistent format. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting day and month separately makes sense.

Do we need to care about the order, though? Some locales use MM/DD, others DD/MM.

There’s a way to detect that order if we really want to (expand for an example)
function formatDayMonth(locale: string, date: Date): string {
  // Format day and short month separately
  const day = new Intl.DateTimeFormat(locale, { day: 'numeric' }).format(date);
  const monthName = new Intl.DateTimeFormat(locale, { month: 'short' }).format(date)
    .replace('.', '');

  // Detect whether day comes before month for this locale
  const orderParts = new Intl.DateTimeFormat(locale, {
    day: 'numeric',
    month: 'numeric'
  }).formatToParts(date);

  const dayIndex = orderParts.findIndex(p => p.type === 'day');
  const monthIndex = orderParts.findIndex(p => p.type === 'month');

  const isDayFirst = dayIndex < monthIndex;

  return isDayFirst ? `${day} ${monthName}` : `${monthName} ${day}`;
}

// Examples:
formatDayMonth('pt-BR', new Date('2025-10-11'));
// "11 out"
formatDayMonth('pt-PT', new Date('2025-10-11'));
// "11 out"
formatDayMonth('de', new Date('2025-10-11'));
// "11 Okt" 
formatDayMonth('en-GB', new Date('2025-10-11'))
// "11 Oct" 
formatDayMonth('en-US', new Date('2025-10-11'));
// "Oct 11"
formatDayMonth('en', new Date('2025-10-11'));
// "Oct 11" 
formatDayMonth('zh', new Date('2025-10-11'))
// "10月 11日"

Feels a bit over-engineered, but it works.

In pt-BR I wouldn't mind if it stayed as "MM DD", since the layout stacks them on top of each other in the expense list.

Not sure how that reads in other locales...

What do you think? We just keep "MM DD" specifically in the expenses list for now? In that case, the following should be enough:

const day = new Intl.DateTimeFormat(todayTranslation.usedLng, { day: 'numeric' }).format(date);
const monthName = new Intl.DateTimeFormat(todayTranslation.usedLng, { month: 'short' }).format(date).replace('.', '');

return `${monthName} ${day}`;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. One change would be to change day to 2-digit maybe?

};

export function generateSplitDescription(
t: TFunction,
Expand Down