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

Expose FS translation system to plugins #2578

Merged
merged 32 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5eb0121
feat: exposes fs translation system to plugins
HiDeoo Nov 5, 2024
9844dc1
feat: update ec `getBlockLocale()` with the change of 2551
HiDeoo Nov 6, 2024
3b79cce
refactor: rename plugin hooks
HiDeoo Nov 9, 2024
8f0acd8
Merge branch 'main' into hd-refactor-inject-translations
HiDeoo Nov 9, 2024
0d73832
fix: demo plugin lock file
HiDeoo Nov 9, 2024
8ddfb98
fix: add async hook comment
HiDeoo Nov 9, 2024
850d3f1
fix: add ec `pathToLang` comment
HiDeoo Nov 9, 2024
57e8a06
docs: tweak `useTranslations` documentation
HiDeoo Nov 9, 2024
c40f599
refactor `pathToLang()` → `absolutePathToLang()`
HiDeoo Nov 10, 2024
fe6e68c
test: add test to ensure DocSearch plugin use `config:setup` hook
HiDeoo Nov 10, 2024
5ddf2f1
feat: add `HookParameters` utility type
HiDeoo Nov 10, 2024
6f41499
fix: remove `absolutePathToLang()` error
HiDeoo Nov 10, 2024
60dbc9c
fix: add `starlight-links-validator` patch
HiDeoo Nov 10, 2024
b1cb641
chore: add `HookParameters` changeset
HiDeoo Nov 10, 2024
71cbdb5
fix: remove temporary `starlight-links-validator` patch
HiDeoo Nov 10, 2024
5aab231
docs: update `useTranslations()` example
HiDeoo Nov 11, 2024
36ef34f
docs: mention BCP-47 in `useTranslations()` description
HiDeoo Nov 11, 2024
80af330
Merge branch 'main' into pr/2578
delucis Dec 20, 2024
56baf37
Update Astro version in demo plugin
delucis Dec 20, 2024
f11ec45
Discard changes to pnpm-lock.yaml
delucis Dec 20, 2024
8f62051
Fix lock file
delucis Dec 20, 2024
3cd90c5
Merge branch 'main' into hd-refactor-inject-translations
delucis Jan 8, 2025
8bd4a1d
docs: apply suggestions
HiDeoo Jan 9, 2025
e00a3b1
refactor: destructure some variables
HiDeoo Jan 9, 2025
94927f3
feat: use `config:setup` hook in docsearch plugin
HiDeoo Jan 9, 2025
8e87088
chore: remove demo plugin
HiDeoo Jan 9, 2025
f543ed9
chore: remove unused import
HiDeoo Jan 9, 2025
e80caa4
chore: bump starlight peer dependency version in docsearch plugin
HiDeoo Jan 9, 2025
498c268
Merge branch 'main' into hd-refactor-inject-translations
delucis Feb 6, 2025
1c4b186
Merge branch 'main' into hd-refactor-inject-translations
delucis Feb 12, 2025
37398b2
Merge branch 'main' into hd-refactor-inject-translations
delucis Feb 15, 2025
a6661b3
Bump Starlight peer dep for Doscsearch package to 0.32
delucis Feb 15, 2025
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
5 changes: 5 additions & 0 deletions .changeset/chatty-jars-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': minor
---

Adds a new [`HookParameters`](https://starlight.astro.build/reference/plugins/#hooks) utility type to get the type of a plugin hook’s arguments.
29 changes: 29 additions & 0 deletions .changeset/large-balloons-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@astrojs/starlight': minor
---

Exposes the built-in localization system in the Starlight plugin `config:setup` hook.

⚠️ **BREAKING CHANGE:**

This addition changes how Starlight plugins add or update translation strings used in Starlight’s localization APIs.
Plugins previously using the [`injectTranslations()`](https://starlight.astro.build/reference/plugins/#injecttranslations) callback function from the plugin [`config:setup`](https://starlight.astro.build/reference/plugins/#configsetup) hook should now use the same function available in the [`i18n:setup`](https://starlight.astro.build/reference/plugins/#i18nsetup) hook.

```diff
export default {
name: 'plugin-with-translations',
hooks: {
- 'config:setup'({ injectTranslations }) {
+ 'i18n:setup'({ injectTranslations }) {
injectTranslations({
en: {
'myPlugin.doThing': 'Do the thing',
},
fr: {
'myPlugin.doThing': 'Faire le truc',
},
});
},
},
};
```
5 changes: 5 additions & 0 deletions .changeset/loud-wolves-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': minor
---

Adds a new [`useTranslations()`](https://starlight.astro.build/reference/plugins/#usetranslations) callback function to the Starlight plugin `config:setup` hook to generate a utility function to access UI strings for a given language.
21 changes: 21 additions & 0 deletions .changeset/polite-fishes-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@astrojs/starlight': minor
---

Deprecates the Starlight plugin `setup` hook in favor of the new `config:setup` hook which provides the same functionality.

⚠️ **BREAKING CHANGE:**

The Starlight plugin `setup` hook is now deprecated and will be removed in a future release. Please update your plugins to use the new `config:setup` hook instead.

```diff
export default {
name: 'plugin-with-translations',
hooks: {
- 'setup'({ config }) {
+ 'config:setup'({ config }) {
// Your plugin configuration setup code
},
},
};
```
11 changes: 11 additions & 0 deletions .changeset/stupid-turkeys-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@astrojs/starlight-docsearch': minor
---

⚠️ **BREAKING CHANGE:** The minimum supported version of Starlight is now 0.32.0

Please use the `@astrojs/upgrade` command to upgrade your project:

```sh
npx @astrojs/upgrade
```
5 changes: 5 additions & 0 deletions .changeset/wet-cherries-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': minor
---

Adds a new [`absolutePathToLang()`](https://starlight.astro.build/reference/plugins/#absolutepathtolang) callback function to the Starlight plugin `config:setup` to get the language for a given absolute file path.
3 changes: 3 additions & 0 deletions docs/src/content/docs/guides/i18n.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ export const GET = (context) => {
};
```

In the context of a Starlight plugin, you can use the [`useTranslations()`](/reference/plugins/#usetranslations) helper to access this API for a specific language.
See the [plugins reference](/reference/plugins/) for more information.

### Rendering a UI string

Render UI strings using the `locals.t()` function.
Expand Down
210 changes: 156 additions & 54 deletions docs/src/content/docs/reference/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@ See below for details of the different properties and hook parameters.
interface StarlightPlugin {
name: string;
hooks: {
setup: (options: {
'i18n:setup'?: (options: {
injectTranslations: (
translations: Record<string, Record<string, string>>
) => void;
}) => void | Promise<void>;
'config:setup': (options: {
config: StarlightUserConfig;
updateConfig: (newConfig: StarlightUserConfig) => void;
addIntegration: (integration: AstroIntegration) => void;
astroConfig: AstroConfig;
command: 'dev' | 'build' | 'preview';
isRestart: boolean;
logger: AstroIntegrationLogger;
injectTranslations: (Record<string, Record<string, string>>) => void;
useTranslations: (lang: string) => I18nT;
absolutePathToLang: (path: string) => string;
}) => void | Promise<void>;
};
}
Expand All @@ -41,12 +47,100 @@ A plugin must provide a unique name that describes it. The name is used when [lo

## `hooks`

Hooks are functions which Starlight calls to run plugin code at specific times. Currently, Starlight supports a single `setup` hook.
Hooks are functions which Starlight calls to run plugin code at specific times.

To get the type of a hook's arguments, use the `HookParameters` utility type and pass in the hook name.
In the following example, the `options` parameter is typed to match the arguments passed to the `config:setup` hook:

```ts
import type { HookParameters } from '@astrojs/starlight/types';

function configSetup(options: HookParameters['config:setup']) {
options.useTranslations('en');
}
```

### `i18n:setup`

Plugin internationalization setup function called when Starlight is initialized.
The `i18n:setup` hook can be used to inject translation strings so a plugin can support different locales.
These translations will be available via [`useTranslations()`](#usetranslations) in the `config:setup` hook and in UI components via [`Astro.locals.t()`](/guides/i18n/#using-ui-translations).

The `i18n:setup` hook is called with the following options:

#### `injectTranslations`

**type:** `(translations: Record<string, Record<string, string>>) => void`

A callback function to add or update translation strings used in Starlight’s [localization APIs](/guides/i18n/#using-ui-translations).

In the following example, a plugin injects translations for a custom UI string named `myPlugin.doThing` for the `en` and `fr` locales:

```ts {6-13} /(injectTranslations)[^(]/
// plugin.ts
export default {
name: 'plugin-with-translations',
hooks: {
'i18n:setup'({ injectTranslations }) {
injectTranslations({
en: {
'myPlugin.doThing': 'Do the thing',
},
fr: {
'myPlugin.doThing': 'Faire le truc',
},
});
},
},
};
```

To use the injected translations in your plugin UI, follow the [“Using UI translations” guide](/guides/i18n/#using-ui-translations).
If you need to use UI strings in the context of the [`config:setup`](#configsetup) hook of your plugin, you can use the [`useTranslations()`](#usetranslations) callback.

Types for a plugin’s injected translation strings are generated automatically in a user’s project, but are not yet available when working in your plugin’s codebase.
To type the `locals.t` object in the context of your plugin, declare the following global namespaces in a TypeScript declaration file:

```ts
// env.d.ts
declare namespace App {
type StarlightLocals = import('@astrojs/starlight').StarlightLocals;
// Define the `locals.t` object in the context of a plugin.
interface Locals extends StarlightLocals {}
}

declare namespace StarlightApp {
// Define the additional plugin translations in the `I18n` interface.
interface I18n {
'myPlugin.doThing': string;
}
}
```

You can also infer the types for the `StarlightApp.I18n` interface from a source file if you have an object containing your translations.

For example, given the following source file:

```ts title="ui-strings.ts"
export const UIStrings = {
en: { 'myPlugin.doThing': 'Do the thing' },
fr: { 'myPlugin.doThing': 'Faire le truc' },
};
```

The following declaration would infer types from the English keys in the source file:

```ts title="env.d.ts"
declare namespace StarlightApp {
type UIStrings = typeof import('./ui-strings').UIStrings.en;
interface I18n extends UIStrings {}
}
```

### `hooks.setup`
### `config:setup`

Plugin setup function called when Starlight is initialized (during the [`astro:config:setup`](https://docs.astro.build/en/reference/integrations-reference/#astroconfigsetup) integration hook).
The `setup` hook can be used to update the Starlight configuration or add Astro integrations.
Plugin configuration setup function called when Starlight is initialized (during the [`astro:config:setup`](https://docs.astro.build/en/reference/integrations-reference/#astroconfigsetup) integration hook).
The `config:setup` hook can be used to update the Starlight configuration or add Astro integrations.

This hook is called with the following options:

Expand All @@ -73,7 +167,7 @@ In the following example, a new [`social`](/reference/configuration/#social) med
export default {
name: 'add-twitter-plugin',
hooks: {
setup({ config, updateConfig }) {
'config:setup'({ config, updateConfig }) {
updateConfig({
social: {
...config.social,
Expand All @@ -100,7 +194,7 @@ import react from '@astrojs/react';
export default {
name: 'plugin-using-react',
hooks: {
setup({ addIntegration, astroConfig }) {
'config:setup'({ addIntegration, astroConfig }) {
const isReactLoaded = astroConfig.integrations.find(
({ name }) => name === '@astrojs/react'
);
Expand Down Expand Up @@ -149,7 +243,7 @@ All logged messages will be prefixed with the plugin name.
export default {
name: 'long-process-plugin',
hooks: {
setup({ logger }) {
'config:setup'({ logger }) {
logger.info('Starting long process…');
// Some long process…
},
Expand All @@ -163,70 +257,78 @@ The example above will log a message that includes the provided info message:
[long-process-plugin] Starting long process…
```

#### `injectTranslations`

**type:** `(translations: Record<string, Record<string, string>>) => void`
#### `useTranslations`

A callback function to add or update translation strings used in Starlight’s [localization APIs](/guides/i18n/#using-ui-translations).
**type:** `(lang: string) => I18nT`

In the following example, a plugin injects translations for a custom UI string named `myPlugin.doThing` for the `en` and `fr` locales:
Call `useTranslations()` with a BCP-47 language tag to generate a utility function that provides access to UI strings for that language.
`useTranslations()` returns an equivalent of the `Astro.locals.t()` API that is available in Astro components.
To learn more about the available APIs, see the [“Using UI translations”](/guides/i18n/#using-ui-translations) guide.

```ts {6-13} /(injectTranslations)[^(]/
```ts {6}
// plugin.ts
export default {
name: 'plugin-with-translations',
name: 'plugin-use-translations',
hooks: {
setup({ injectTranslations }) {
injectTranslations({
en: {
'myPlugin.doThing': 'Do the thing',
},
fr: {
'myPlugin.doThing': 'Faire le truc',
},
});
'config:setup'({ useTranslations, logger }) {
const t = useTranslations('zh-CN');
logger.info(t('builtWithStarlight.label'));
},
},
};
```

To use the injected translations in your plugin UI, follow the [“Using UI translations” guide](/guides/i18n/#using-ui-translations).
The example above will log a message that includes a built-in UI string for the Simplified Chinese language:

Types for a plugin’s injected translation strings are generated automatically in a user’s project, but are not yet available when working in your plugin’s codebase.
To type the `locals.t` object in the context of your plugin, declare the following global namespaces in a TypeScript declaration file:
```shell
[plugin-use-translations] 基于 Starlight 构建
```

```ts
// env.d.ts
declare namespace App {
type StarlightLocals = import('@astrojs/starlight').StarlightLocals;
// Define the `locals.t` object in the context of a plugin.
interface Locals extends StarlightLocals {}
}
#### `absolutePathToLang`

declare namespace StarlightApp {
// Define the additional plugin translations in the `I18n` interface.
interface I18n {
'myPlugin.doThing': string;
}
}
```
**type:** `(path: string) => string`

You can also infer the types for the `StarlightApp.I18n` interface from a source file if you have an object containing your translations.
Call `absolutePathToLang()` with an absolute file path to get the language for that file.

For example, given the following source file:
This can be particularly useful when adding [remark or rehype plugins](https://docs.astro.build/en/guides/markdown-content/#markdown-plugins) to process Markdown or MDX files.
The [virtual file format](https://github.com/vfile/vfile) used by these plugins includes the [absolute path](https://github.com/vfile/vfile#filepath) of the file being processed, which can be used with `absolutePathToLang()` to determine the language of the file.
The returned language can be used with the [`useTranslations()`](#usetranslations) helper to get UI strings for that language.

```ts title="ui-strings.ts"
export const UIStrings = {
en: { 'myPlugin.doThing': 'Do the thing' },
fr: { 'myPlugin.doThing': 'Faire le truc' },
For example, given the following Starlight configuration:

```js
starlight({
title: 'My Docs',
defaultLocale: 'en',
locales: {
// English docs in `src/content/docs/en/`
en: { label: 'English' },
// French docs in `src/content/docs/fr/`
fr: { label: 'Français', lang: 'fr' },
},
});
```

A plugin can determine the language of a file using its absolute path:

```ts {6-8} /fr/
// plugin.ts
export default {
name: 'plugin-use-translations',
hooks: {
'config:setup'({ absolutePathToLang, useTranslations, logger }) {
const lang = absolutePathToLang(
'/absolute/path/to/project/src/content/docs/fr/index.mdx'
);
const t = useTranslations(lang);
logger.info(t('aside.tip'));
},
},
};
```

The following declaration would infer types from the English keys in the source file:
The example above will log a message that includes a built-in UI string for the French language:

```ts title="env.d.ts"
declare namespace StarlightApp {
type UIStrings = typeof import('./ui-strings').UIStrings.en;
interface I18n extends UIStrings {}
}
```shell
[plugin-use-translations] Astuce
```
2 changes: 1 addition & 1 deletion packages/docsearch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default function starlightDocSearch(userConfig: DocSearchUserConfig): Sta
return {
name: 'starlight-docsearch',
hooks: {
setup({ addIntegration, config, logger, updateConfig }) {
'config:setup'({ addIntegration, config, logger, updateConfig }) {
// If the user has already has a custom override for the Search component, don't override it.
if (config.components?.Search) {
logger.warn(
Expand Down
2 changes: 1 addition & 1 deletion packages/docsearch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"./schema": "./schema.ts"
},
"peerDependencies": {
"@astrojs/starlight": ">=0.30.0"
"@astrojs/starlight": ">=0.32.0"
},
"dependencies": {
"@docsearch/css": "^3.6.0",
Expand Down
Loading