-
-
Notifications
You must be signed in to change notification settings - Fork 538
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
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 36ef34f The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for astro-starlight ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
@@ -152,7 +151,7 @@ export function getStarlightEcConfigPreprocessor({ | |||
}, | |||
...otherStyleOverrides, | |||
}, | |||
getBlockLocale: ({ file }) => pathToLocale(file.path, { starlightConfig, astroConfig }), | |||
getBlockLocale: ({ file }) => pathToLang(file.path, { astroConfig, starlightConfig }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expressive Code technically expects a Starlight language here so I changed it but this is not a bug fix (no changeset) as Expressive Code lower-cases the language identifier anyway.
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great @HiDeoo! Left some suggestions/questions/discussions, but they’re honestly all around the “outside” of the core functionality or on the docs. I think overall this seems very solid 🚀
.changeset/large-balloons-compete.md
Outdated
name: 'plugin-with-translations', | ||
hooks: { | ||
- setup({ injectTranslations }) { | ||
+ init({ injectTranslations }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the hook naming, I was wondering if we should follow the colon-separated pattern used by Astro integrations? Both of these hooks are essentially “setup” hooks, so the new hook would become i18n:setup()
or translations:setup()
or something. i18n
is nice because it’s short, but maybe a bit misleading if you don’t set up i18n config here, only translations.
Ideally maybe we’d even align both hooks like this, although that’s a bit of an annoying breaking change to impose on everyone:
interface Plugin {
name: string;
hooks: {
'i18n:setup': (opts: I18nSetupOptions) => void;
'config:setup': (opts: ConfigSetupOptions) => void;
}
}
That said, I don’t know if there’s a future where this starts to be silly. Like is there anything Starlight would do/need that is not “setup”? I guess we might have a config:done
hook like Astro does that gives access to the final resolved Starlight config?
I guess the flipside of an i18n
-focused naming is if we think this “early” initialization phase is going to be used for other things (which your current docs kind of suggests: “initialization function called before Starlight is initialized (during the astro:config:setup
integration hook)”). In that case something more focused on lifecycle like pre:setup
(😅) or whatever could make sense. I find it hard to evaluate the probability of that, so open to hear opinions. The i18n
approach does kind of help communicate use case up front as well, so maybe it’s OK.
(If we do rename setup()
it doesn’t have to be in this PR and can be done in a backwards compatible way where we still call setup()
if defined but mark it as deprecated.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the hook naming, I was wondering if we should follow the colon-separated pattern used by Astro integrations?
I personally like that idea.
but maybe a bit misleading if you don’t set up i18n config here, only translations.
I guess, altho if we compare with our configuration file, we use the locales
key to configure i18n, not i18n
so I think it's fine.
I guess we might have a config:done hook like Astro does that gives access to the final resolved Starlight config?
Maybe, I don't really see the need for it right now and I'm not even sure what you would do with it.
which your current docs kind of suggests: “initialization function called before Starlight is initialized (during the astro:config:setup integration hook)”
Well, to be fair, like I said, I didn't really put any thought at all about the naming of the hook, and I documented it mostly to give an overview of all the changes and mimick what we had before.
I think if we ever need something else to be done before config:setup
that is not i18n
, we could still add an xyz:setup
and call it before config:setup
like we do with i18n:setup
and it would be fine.
If we do rename setup() it doesn’t have to be in this PR and can be done in a backwards compatible way where we still call setup() if defined but mark it as deprecated.
I think we maybe do want to do that at the same time, would be easier to communicate and organize docs? And we can still soft deprecate the setup
method in the code so it's still run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, that sounds good! So if you think i18n
is not problematic, I think it’s nice to have that concise hook name. And +1 to do the backwards compatible deprecation of setup
in favour of config:setup
.
Regarding what you might do with a config:done
: most often I see the astro:config:done
hook used to share the final config with some other code via the integration closure, e.g.
function integration() {
const lazy: { config?: AstroConfig } = {};
return {
name: 'example',
hooks: {
'astro:config:setup'() {
// add something referencing `lazy` e.g. a Vite plugin
// that will run _after_ the done hook but wants the
// final config shape
},
'astro:config:done'({config}) {
lazy.config = config;
},
},
};
}
But not suggesting we add that now. No point until some more clear needs emerge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, I'll get started asap on the new changes, thanks again.
@@ -63,6 +59,34 @@ export default defineVitestConfig({ | |||
}, | |||
}); | |||
}, | |||
async setup({ config, updateConfig, useTranslations, pathToLang }) { | |||
await Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this here for a reason or is it just left over from some debugging?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh… I see it was already there in the previous version of this hook. I guess my question still stands though 😅
Maybe it’s ensuring the hook is really async to test that? Whatever it is, if it’s important to keep, it’s probably a good idea to add a comment for next time I’m reviewing this and confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a comment, this is mostly to ensure type-wise it supports async setup()
while avoiding to add a type test for it as type tests are still a bit broken within a Vitest workspace setup like we have.
import type { createTranslationSystemFromFs } from '../../utils/translations-fs'; | ||
import { pathToLocale } from '../shared/pathToLocale'; | ||
import type { StarlightConfig, StarlightPlugin } from '../../types'; | ||
import { pathToLang } from '../shared/pathToLang'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remind me why EC uses this rather than the one passed from the plugins instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this deserves a comment somewhere, but it's for the <Code>
component basically which runs in a different context.
// strings. We let TypeScript merge them into a single union type so that plugins with a TypeScript | ||
// configuration preventing `UserI18nKeys` to be properly inferred can still get auto-completion | ||
// for built-in UI strings. | ||
export type I18nKeys = keyof BuiltInStrings | UserI18nKeys | keyof StarlightApp.I18n; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhhhhh, nice fix! 🧠
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. | ||
|
||
### `hooks.init` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do adopt the colon-style of naming, I’d probably recommend removing hooks.
in these subheadings so we just have something like :
## `hooks`
...
### `i18n:setup`
...
### `config:setup`
...
Plugin initialization function called before Starlight is initialized (during the [`astro:config:setup`](https://docs.astro.build/en/reference/integrations-reference/#astroconfigsetup) integration hook). | ||
The `init` hook can currently only be used to inject translations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do go with an i18n
-focused hook name, I’m sure we can clean this up (and maybe makes it easier to explain as well vs a hook for “initializing before stuff is initialized”). Will hold off suggesting anything while we discuss the hook naming.
A callback function to generate a utility function to access UI strings for a given language. | ||
To learn more about the generated utility function and all the APIs available, see the [“Using UI translations”](/guides/i18n/#using-ui-translations) guide. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggesting some tweaks to avoid some of the repeated use of “function” and make it a bit clearer that the return is the same as t()
rather than just a “generated utility function” which I think communicates a bit less what this does.
A callback function to generate a utility function to access UI strings for a given language. | |
To learn more about the generated utility function and all the APIs available, see the [“Using UI translations”](/guides/i18n/#using-ui-translations) guide. | |
Call `useTranslations()` with a 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. |
|
||
You can also infer the types for the `StarlightApp.I18n` interface from a source file if you have an object containing your translations. | ||
A callback function to get the language for a given full file path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aligning this with the previous suggestion, which I also like because it makes clear the arguments to pass as well as the purpose.
A callback function to get the language for a given full file path. | |
Call `pathToLang()` with a file path to get the language for that file. |
One question here though: in general these docs always say “full” file path. What happens if I do pathToLang('src/content/docs/en/foo.md')
? Or even pathToLang('en/foo.md')
? Is it dependent on being an absolute path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add a few test cases for these, but they all fallback to the default locale. I tried to use the same naming convention (full path) as the one used in the vfile docs for file.path
as this would be I assume on the of the most common scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I noticed “full path” used in the vFile docs when I clicked through too. Personally, I find that term a little unclear, vs something like “absolute path” which I think is the same as what they mean by “full”?
I’m almost tempted to suggest the helper should even be called absolutePathToLang()
to drive this home. Or to have some less friendly behaviour like erroring if the path doesn’t match what we expect (so people don’t get silent fallback locales without realising they used it wrong), although I don’t know if that’s possible technically. Basically I want to avoid people doing pathToLang('en/foo')
and expecting it to work/not noticing it isn’t working.
This can be particularly useful in [remark or rehype plugins](https://docs.astro.build/en/guides/markdown-content/#markdown-plugins) that a Starlight plugin may use to process Markdown or MDX files. | ||
The [virtual file format](https://github.com/vfile/vfile) used by such plugins includes the [full path](https://github.com/vfile/vfile#filepath) of the file being processed, which can be used with `pathToLang` 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggesting some tiny tweaks here:
- removed the “Starlight plugin” part as it helps simplify the sentence and hopefully it’s obvious on this page we’re referring to plugins
- added
()
to the mention of the functions like we usually do
This can be particularly useful in [remark or rehype plugins](https://docs.astro.build/en/guides/markdown-content/#markdown-plugins) that a Starlight plugin may use to process Markdown or MDX files. | |
The [virtual file format](https://github.com/vfile/vfile) used by such plugins includes the [full path](https://github.com/vfile/vfile#filepath) of the file being processed, which can be used with `pathToLang` 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. | |
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 [full path](https://github.com/vfile/vfile#filepath) of the file being processed, which can be used with `pathToLang()` 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. |
Thanks for the review 🙌 Holding off on the suggested changes so I can tackle them all at once when we have a clearer idea of what we want to do regarding the name. |
* main: (27 commits) i18n(ko-KR): update `site-search.mdx` [ci] release (withastro#2590) Support passing more options to the DocSearch component (withastro#2589) [ci] release (withastro#2587) Add changeset for withastro#2252 (withastro#2588) [ci] format Update dev dependencies (withastro#2582) Build performance optimizations for projects with large sidebars (withastro#2252) Fix a11y CI workflow (withastro#2503) Update `astro-expressive-code` to v0.38 (withastro#2551) Added social icon for Nostr (withastro#2579) [ci] format docs(showcase): add docs.reactbricks.com to showcase (withastro#2586) i18n(ja): Update site-search.mdx (withastro#2577) i18n(ja): Update pages.mdx (withastro#2576) i18n(fr): Update `reference/plugins.mdx` from withastro#2549 (withastro#2574) [ci] format docs: update showcase-sites.astro (withastro#2562) Throw an error if a showcase image does not have the required dimensions (withastro#2573) i18n(ja): Update frontmatter.md (withastro#2566) ...
Co-authored-by: Chris Swithinbank <[email protected]>
Updated the branch with the changes discussed above, hope I did not forget anything:
I have a question: this PR adds multiple changesets but I think |
Description
This plugin exposes the file system translation system to Starlight plugins throught the
setup
hook using a newuseTranslations()
callback function.This change requires the addition of a new plugin hook (currently named
init
, I picked the first thing coming to mind to get started) which now exposes theinjectTranslations()
callback function (which was previously available in thesetup
hook).The documentation so far is only a rough draft to get an idea of everything changed.
Remaining tasks
docs/astro.config.mjs
filedocs/package.json
filedocs/src/content/i18n/zh-CN.json
filepackages/demo-plugin/
directorypnpm i
to remove mention of thestarlight-demo-plugin
package in the lockfile// TODO(HiDeoo)
comments (if any)