diff --git a/build/date-fns-locales-plugin.mts b/build/date-fns-locales-plugin.mts new file mode 100644 index 0000000000..7317149dac --- /dev/null +++ b/build/date-fns-locales-plugin.mts @@ -0,0 +1,62 @@ +/*! + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: CC0-1.0 + */ +import type { Plugin } from 'vite' + +import { exactRegex } from '@rolldown/pluginutils' +import { glob } from 'fs/promises' +import { createRequire } from 'module' +import { dirname, join } from 'path' + +/** + * This plugin generates a module that can be used to dynamically load locales from `date-fns`. + * It generates it in a way that it works with the externalization of the `date-fns` locales + * and with Vite and Webpack apps. + * + * A plugin is used because Vite's dynamic imports and glob imports cannot generate imports in such a way. + */ +export default () => { + let localeFiles: string[] = [] + const virtualModuleId = 'virtual:date-fns-locales' + const resolvedVirtualModuleId = '\0' + virtualModuleId + return { + name: 'date-fns-locales-plugin', + enforce: 'pre', + async buildStart() { + this.info('Collectioning locales in date-fns/locales') + const require = createRequire(import.meta.url) + const pkgJsonPath = require.resolve('date-fns/package.json') + const localeDir = join(dirname(pkgJsonPath), 'locale') + localeFiles = await Array.fromAsync(glob('*.js', { + cwd: localeDir, + exclude: ['types.js', 'en-US.js', 'cdn.js', 'cdn.min.js'], + })) + }, + resolveId: { + filter: { id: /dateFnsLocaleLoader\.ts$/ }, + handler() { + return resolvedVirtualModuleId + }, + }, + load: { + filter: { id: exactRegex(resolvedVirtualModuleId) }, + handler() { + let content = '' + content += 'const loader = {}\n' + content += 'export default loader\n' + for (const localeFile of localeFiles) { + // e.g., "pt-BR.js" -> "pt-BR" + const localeCode = localeFile.slice(0, -3) + let exportName = localeCode.replaceAll('-', '') + // One locale is named inconsistently... + if (localeCode === 'be-tarask') { + exportName = 'beTarasak' + } + content += `loader['${localeCode}'] = async () => (await import('date-fns/locale/${localeCode}')).${exportName}\n` + } + return content + }, + }, + } as Plugin +} diff --git a/package-lock.json b/package-lock.json index c32f23b0c7..cda17acad1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "@playwright/test": "^1.61.1", "@types/gettext-parser": "^9.0.0", "@types/node": "^26.0.1", + "@types/webpack-env": "^1.18.8", "@vitest/coverage-v8": "^4.1.9", "@vue/test-utils": "^2.4.6", "@vue/tsconfig": "^0.9.1", @@ -5863,6 +5864,13 @@ "source-map": "^0.6.0" } }, + "node_modules/@types/webpack-env": { + "version": "1.18.8", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.8.tgz", + "integrity": "sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.3.tgz", diff --git a/package.json b/package.json index c7b941c252..b581e4674b 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "@playwright/test": "^1.61.1", "@types/gettext-parser": "^9.0.0", "@types/node": "^26.0.1", + "@types/webpack-env": "^1.18.8", "@vitest/coverage-v8": "^4.1.9", "@vue/test-utils": "^2.4.6", "@vue/tsconfig": "^0.9.1", diff --git a/playwright.config.ts b/playwright.config.ts index 96cfd7b28e..c8c0253707 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -64,6 +64,8 @@ export default defineConfig({ ctViteConfig: async () => ({ plugins: [ + // Add to support multiple locales + (await import('./build/date-fns-locales-plugin.mts')).default(), // normally added by default but we overwrite the plugins so we need to add it back manually (await import('@vitejs/plugin-vue')).default(), // We do have some dependencies that use node modules -> we need to polyfill diff --git a/src/components/NcDateTimePicker/NcDateTimePicker.vue b/src/components/NcDateTimePicker/NcDateTimePicker.vue index 1a9cd8d235..b0f56b0af9 100644 --- a/src/components/NcDateTimePicker/NcDateTimePicker.vue +++ b/src/components/NcDateTimePicker/NcDateTimePicker.vue @@ -293,6 +293,7 @@ import NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue' import NcTimezonePicker from '../NcTimezonePicker/NcTimezonePicker.vue' import { t } from '../../l10n.ts' import NcButton from '../NcButton/index.ts' +import useDateFnsLocale from './useDateFnsLocale.ts' type LibraryFormatOptions = VueDatePickerProps['format'] @@ -539,6 +540,8 @@ const placeholderFallback = computed(() => { return t('Select date and time') }) +const dateFnsLocale = useDateFnsLocale(realLocale) + /** * The date (time) formatting to be used by the library. * We use the provided format if possible, otherwise we provide a formatting function @@ -775,7 +778,12 @@ function sameDay(a: Date, b: Date): boolean {