diff --git a/.storybook/handlers.ts b/.storybook/handlers.ts index fa5544eced..c8e485285a 100644 --- a/.storybook/handlers.ts +++ b/.storybook/handlers.ts @@ -91,3 +91,68 @@ export const pdsUsersHandler = http.get('/api/atproto/pds-users', () => { }, ]) }) + +export const i18nStatusHandler = http.get('/lunaria/status.json', () => { + return HttpResponse.json({ + generatedAt: '2026-01-22T10:07:07.000Z', + sourceLocale: { + lang: 'en', + label: 'English', + totalKeys: 500, + }, + locales: [ + { + lang: 'en-GB', + label: 'English (UK)', + dir: 'ltr', + totalKeys: 500, + completedKeys: 423, + percentComplete: 84, + missingKeys: [ + 'settings.background_themes.label', + 'settings.enable_graph_pulse_loop', + 'settings.enable_graph_pulse_loop_description', + 'settings.data_source.algolia_description', + 'settings.data_source.npm_description', + 'i18n.contribute_hint', + 'i18n.copy_keys', + ], + githubEditUrl: 'https://github.com/npmx-dev/npmx.dev/edit/main/i18n/locales/en-GB.json', + githubHistoryUrl: + 'https://github.com/npmx-dev/npmx.dev/commits/main/i18n/locales/en-GB.json', + }, + { + lang: 'fr-FR', + label: 'Français', + dir: 'ltr', + totalKeys: 500, + completedKeys: 423, + percentComplete: 84, + missingKeys: [ + 'settings.background_themes.label', + 'settings.enable_graph_pulse_loop', + 'settings.enable_graph_pulse_loop_description', + 'settings.data_source.algolia_description', + 'settings.data_source.npm_description', + 'i18n.contribute_hint', + 'i18n.copy_keys', + ], + githubEditUrl: 'https://github.com/npmx-dev/npmx.dev/edit/main/i18n/locales/fr-FR.json', + githubHistoryUrl: + 'https://github.com/npmx-dev/npmx.dev/commits/main/i18n/locales/fr-FR.json', + }, + { + lang: 'de-DE', + label: 'Deutsch', + dir: 'ltr', + totalKeys: 500, + completedKeys: 500, + percentComplete: 100, + missingKeys: [], + githubEditUrl: 'https://github.com/npmx-dev/npmx.dev/edit/main/i18n/locales/de-DE.json', + githubHistoryUrl: + 'https://github.com/npmx-dev/npmx.dev/commits/main/i18n/locales/de-DE.json', + }, + ], + }) +}) diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index f644bb7435..c626f23ef8 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -61,3 +61,19 @@ background-color: var(--bg, oklch(0.171 0 0)) !important; } + diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 30d8f6cfcb..1c810cf948 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -10,16 +10,6 @@ import npmxDark from './theme' initialize() -// related: https://github.com/npmx-dev/npmx.dev/blob/1431d24be555bca5e1ae6264434d49ca15173c43/test/nuxt/setup.ts#L12-L26 -// Stub Nuxt specific globals -// @ts-expect-error - dynamic global name -globalThis['__NUXT_COLOR_MODE__'] ??= { - preference: 'system', - value: 'dark', - getColorScheme: fn(() => 'dark'), - addColorScheme: fn(), - removeColorScheme: fn(), -} // @ts-expect-error - dynamic global name globalThis.defineOgImageComponent = fn() diff --git a/app/pages/settings.stories.ts b/app/pages/settings.stories.ts new file mode 100644 index 0000000000..1deaecbc19 --- /dev/null +++ b/app/pages/settings.stories.ts @@ -0,0 +1,55 @@ +import Settings from './settings.vue' +import type { Meta, StoryObj } from '@storybook-vue/nuxt' +import { userEvent, expect } from 'storybook/test' +import { pageDecorator } from '../../.storybook/decorators' +import { i18nStatusHandler } from '../../.storybook/handlers' + +const meta = { + component: Settings, + globals: { + locale: 'en-US', + }, + beforeEach: () => localStorage.removeItem('npmx-settings'), + parameters: { + layout: 'fullscreen', + msw: { + handlers: [i18nStatusHandler], + }, + }, + decorators: [pageDecorator], +} satisfies Meta + +export default meta +type Story = StoryObj + +/** English locale (default). The Language section shows a GitHub link to help translate the site. */ +export const Default: Story = {} + +export const NpmRegistryDataSource: Story = { + play: async ({ canvas, step }) => { + await step('Select npm registry as the data source', async () => { + const select = await canvas.findByRole('combobox', { name: /data source/i }) + await userEvent.selectOptions(select, 'npm') + await expect(select).toHaveValue('npm') + }) + }, +} + +/** Non-English locale with incomplete translations. The Language section shows `SettingsTranslationHelper` with a progress bar and list of missing translation keys. `/lunaria/status.json` is intercepted by MSW to provide mock translation status data. */ +export const NonEnglishTranslationHelper: Story = { + globals: { + locale: 'fr-FR', + }, +} + +/** Non-English locale without translations API response. The Language section shows a GitHub link to help translate the site. */ +export const WithoutTranslationHelper: Story = { + globals: { + locale: 'fr-FR', + }, + parameters: { + msw: { + handlers: [], + }, + }, +} diff --git a/nuxt.config.ts b/nuxt.config.ts index 3e51ad515f..943eb902dd 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -15,7 +15,8 @@ export default defineNuxtConfig({ '@vite-pwa/nuxt', '@vueuse/nuxt', '@nuxtjs/i18n', - ...(isStorybook ? [] : ['@nuxt/fonts', '@nuxtjs/color-mode']), + '@nuxtjs/color-mode', + ...(isStorybook ? [] : ['@nuxt/fonts']), ], $test: {