Skip to content

Commit

Permalink
Loosen i18n schema to pass through unknown keys by default (#2380)
Browse files Browse the repository at this point in the history
  • Loading branch information
delucis authored Sep 20, 2024
1 parent aa5be06 commit 7b451cf
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/quick-swans-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Loosen Starlight’s i18n schema to pass through unknown keys
8 changes: 2 additions & 6 deletions packages/starlight/__tests__/basics/translations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ describe('t()', async () => {
'test.nesting1': '$t(test.nesting2) is nested',
'test.nesting2': 'this UI string',
},
// We do not strip unknown translations in this test so that user-defined translations can
// override plugin translations like it would in a real- world scenario were the plugin
// would have provided a custom schema to extend the translations.
{ stripUnknown: false },
],
],
})
Expand Down Expand Up @@ -112,7 +108,7 @@ describe('t.all()', async () => {
// See the `t()` tests for an explanation of how the user-defined translations are mocked.
vi.doMock('astro:content', async () =>
(await import('../test-utils')).mockedAstroContent({
i18n: [['en', { 'test.foo': 'bar' }, { stripUnknown: false }]],
i18n: [['en', { 'test.foo': 'bar' }]],
})
);
vi.resetModules();
Expand All @@ -129,7 +125,7 @@ describe('t.exists()', async () => {
// See the `t()` tests for an explanation of how the user-defined translations are mocked.
vi.doMock('astro:content', async () =>
(await import('../test-utils')).mockedAstroContent({
i18n: [['en', { 'test.foo': 'bar' }, { stripUnknown: false }]],
i18n: [['en', { 'test.foo': 'bar' }]],
})
);
vi.resetModules();
Expand Down
2 changes: 1 addition & 1 deletion packages/starlight/__tests__/plugins/translations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ vi.mock('astro:content', async () =>
// We do not strip unknown translations in this test so that user-defined translations can
// override plugin translations like it would in a real- world scenario were the plugin would
// have provided a custom schema to extend the translations.
i18n: [['ar', { 'testPlugin3.doThing': 'افعل الشيء' }, { stripUnknown: false }]],
i18n: [['ar', { 'testPlugin3.doThing': 'افعل الشيء' }]],
})
);

Expand Down
10 changes: 2 additions & 8 deletions packages/starlight/__tests__/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,10 @@ function mockDoc(
};
}

function mockDict(
id: string,
data: z.input<ReturnType<typeof i18nSchema>>,
{ stripUnknown } = { stripUnknown: true }
) {
function mockDict(id: string, data: z.input<ReturnType<typeof i18nSchema>>) {
return {
id,
data: stripUnknown
? i18nSchema().parse(data)
: i18nSchema().and(z.record(z.string())).parse(data),
data: i18nSchema().parse(data),
};
}

Expand Down
16 changes: 14 additions & 2 deletions packages/starlight/schemas/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,28 @@ const defaultI18nSchema = () =>
/** Type of Starlight’s default i18n schema, including extensions from Pagefind and Expressive Code. */
type DefaultI18nSchema = ReturnType<typeof defaultI18nSchema>;

/**
* Based on the the return type of Zod’s `merge()` method. Merges the type of two `z.object()` schemas.
* Also sets them as “passthrough” schemas as that’s how we use them. In practice whether or not the types
* are passthrough or not doesn’t matter too much.
*
* @see https://github.com/colinhacks/zod/blob/3032e240a0c227692bb96eedf240ed493c53f54c/src/types.ts#L2656-L2660
*/
type MergeSchemas<A extends z.AnyZodObject, B extends z.AnyZodObject> = z.ZodObject<
z.objectUtil.extendShape<A['shape'], B['shape']>,
'passthrough',
B['_def']['catchall']
>;
/** Type that extends Starlight’s default i18n schema with an optional, user-defined schema. */
type ExtendedSchema<T extends z.AnyZodObject> = T extends z.AnyZodObject
? z.ZodIntersection<DefaultI18nSchema, T>
? MergeSchemas<DefaultI18nSchema, T>
: DefaultI18nSchema;

/** Content collection schema for Starlight’s optional `i18n` collection. */
export function i18nSchema<T extends z.AnyZodObject = z.ZodObject<{}>>({
extend = z.object({}) as T,
}: i18nSchemaOpts<T> = {}): ExtendedSchema<T> {
return defaultI18nSchema().merge(extend) as ExtendedSchema<T>;
return defaultI18nSchema().merge(extend).passthrough() as ExtendedSchema<T>;
}
export type i18nSchemaOutput = z.output<ReturnType<typeof i18nSchema>>;

Expand Down

0 comments on commit 7b451cf

Please sign in to comment.