Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix type inference for extended i18n schemas #1786

Merged
merged 3 commits into from Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/spotty-eggs-sit.md
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Fixes type inference for i18n strings added by extending the default schema
17 changes: 12 additions & 5 deletions packages/starlight/schemas/i18n.ts
Expand Up @@ -18,14 +18,21 @@ interface i18nSchemaOpts<T extends z.AnyZodObject = z.ZodObject<{}>> {
extend?: T;
}

const defaultI18nSchema = () =>
starlightI18nSchema().merge(pagefindI18nSchema()).merge(expressiveCodeI18nSchema());
/** Type of Starlight’s default i18n schema, including extensions from Pagefind and Expressive Code. */
type DefaultI18nSchema = ReturnType<typeof defaultI18nSchema>;

/** 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>
: 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> = {}) {
return starlightI18nSchema()
.merge(pagefindI18nSchema())
.merge(expressiveCodeI18nSchema())
.merge(extend);
}: i18nSchemaOpts<T> = {}): ExtendedSchema<T> {
return defaultI18nSchema().merge(extend) as ExtendedSchema<T>;
}
export type i18nSchemaOutput = z.output<ReturnType<typeof i18nSchema>>;

Expand Down
18 changes: 10 additions & 8 deletions packages/starlight/utils/createTranslationSystem.ts
Expand Up @@ -2,8 +2,8 @@ import type { i18nSchemaOutput } from '../schemas/i18n';
import builtinTranslations from '../translations/index';
import type { StarlightConfig } from './user-config';

export function createTranslationSystem(
userTranslations: Record<string, i18nSchemaOutput>,
export function createTranslationSystem<T extends i18nSchemaOutput>(
userTranslations: Record<string, T>,
config: Pick<StarlightConfig, 'defaultLocale' | 'locales'>
) {
/** User-configured default locale. */
Expand Down Expand Up @@ -67,18 +67,20 @@ function localeToLang(
return lang || defaultLang || 'en';
}

type BuiltInStrings = (typeof builtinTranslations)['en'];

/** Build a dictionary by layering preferred translation sources. */
function buildDictionary(
base: (typeof builtinTranslations)[string],
...dictionaries: (i18nSchemaOutput | undefined)[]
) {
function buildDictionary<T extends Record<string, string | undefined>>(
base: BuiltInStrings,
...dictionaries: (T | BuiltInStrings | undefined)[]
): BuiltInStrings & T {
const dictionary = { ...base };
// Iterate over alternate dictionaries to avoid overwriting preceding values with `undefined`.
for (const dict of dictionaries) {
for (const key in dict) {
const value = dict[key];
const value = dict[key as keyof typeof dict];
if (value) dictionary[key as keyof typeof dictionary] = value;
}
}
return dictionary;
return dictionary as BuiltInStrings & T;
}
8 changes: 6 additions & 2 deletions packages/starlight/utils/translations.ts
@@ -1,11 +1,15 @@
import { getCollection } from 'astro:content';
import { getCollection, type CollectionEntry, type DataCollectionKey } from 'astro:content';
import config from 'virtual:starlight/user-config';
import type { i18nSchemaOutput } from '../schemas/i18n';
import { createTranslationSystem } from './createTranslationSystem';

type UserI18nSchema = 'i18n' extends DataCollectionKey
? CollectionEntry<'i18n'>['data']
: i18nSchemaOutput;

/** Get all translation data from the i18n collection, keyed by `id`, which matches locale. */
async function loadTranslations() {
let userTranslations: Record<string, i18nSchemaOutput> = {};
let userTranslations: Record<string, UserI18nSchema> = {};
// Briefly override `console.warn()` to silence logging when a project has no i18n collection.
const warn = console.warn;
console.warn = () => {};
Expand Down