@escape-game-over/atlas 0.1.84 → 0.1.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/docs/client-scripts.md +6 -0
- package/package.json +2 -2
- package/src/astro/youtube.ts +19 -3
- package/src/i18n/per-language.ts +45 -0
- package/src/site/define.ts +12 -0
package/README.md
CHANGED
|
@@ -109,6 +109,10 @@ export const { project, redirects } = atlas.project(baseMessages, baseRoutes, {.
|
|
|
109
109
|
export const site = atlas.site(baseMessages, baseRoutes, project)
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
A site that publishes one language under several regions writes the words once:
|
|
113
|
+
`atlas.perLanguage({ en: "Home", fr: "Accueil" })` fills in `en-GB`, `en-IE` and
|
|
114
|
+
`fr-FR` for one entry of `atlas.messages`, with placeholders still checked.
|
|
115
|
+
|
|
112
116
|
Step 4 returns `t()`, `rich()`, `plain()`, `pathFor()`, `urlFor()`, `fileUrl()`,
|
|
113
117
|
`localeLinksFor()`, `metaFor()`, `breadcrumbFor()`,
|
|
114
118
|
`getStaticPaths()`, `routes`, `sitemap()`, `robots()` and `llms()` already
|
package/docs/client-scripts.md
CHANGED
|
@@ -521,6 +521,12 @@ is what every embed needs and has no taste in:
|
|
|
521
521
|
- **Focus moves to the player.** The button that had it has just left the page,
|
|
522
522
|
which would drop a keyboard reader back at the top of the document.
|
|
523
523
|
|
|
524
|
+
Two choices stay with the project. `rel` lets the player suggest videos from
|
|
525
|
+
other channels, and is off by default, so `rel=0` keeps the suggestions to the
|
|
526
|
+
channel's own; YouTube has no setting that removes them altogether. `playsInline`
|
|
527
|
+
keeps the video in the page on an iPhone rather than going full screen on the
|
|
528
|
+
press.
|
|
529
|
+
|
|
524
530
|
`frame` is called after all of that and before the iframe is in the page, so its
|
|
525
531
|
word is the last one and nothing is drawn unstyled first.
|
|
526
532
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escape-game-over/atlas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.86",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
|
|
6
6
|
"private": false,
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"vite": ">= 8"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@biomejs/biome": "2.6.
|
|
61
|
+
"@biomejs/biome": "2.6.5",
|
|
62
62
|
"@types/node": "26.6.6",
|
|
63
63
|
"@vitest/coverage-istanbul": "5.1.0",
|
|
64
64
|
"astro": "7.4.5",
|
package/src/astro/youtube.ts
CHANGED
|
@@ -55,6 +55,19 @@ export interface YouTubeEmbedOptions {
|
|
|
55
55
|
* player has no way to tell which video it is.
|
|
56
56
|
*/
|
|
57
57
|
readonly title: string;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the player may suggest videos from other channels. Off unless
|
|
60
|
+
* asked for: with `rel=0` it suggests only the channel's own, so a trailer
|
|
61
|
+
* on a project's page does not end on someone else's video.
|
|
62
|
+
*
|
|
63
|
+
* YouTube has no setting that removes the suggestions altogether.
|
|
64
|
+
*/
|
|
65
|
+
readonly rel?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Whether an iPhone plays the video in the page rather than taking it
|
|
68
|
+
* full screen on the press.
|
|
69
|
+
*/
|
|
70
|
+
readonly playsInline?: boolean;
|
|
58
71
|
/**
|
|
59
72
|
* Anything the project wants on the iframe — classes, above all.
|
|
60
73
|
*
|
|
@@ -106,7 +119,7 @@ const ALLOW =
|
|
|
106
119
|
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share";
|
|
107
120
|
|
|
108
121
|
export function youtubeEmbed(options: YouTubeEmbedOptions): YouTubeEmbed {
|
|
109
|
-
const { id, title, frame } = options;
|
|
122
|
+
const { id, title, rel = false, playsInline = false, frame } = options;
|
|
110
123
|
|
|
111
124
|
if (!VIDEO_ID.test(id)) {
|
|
112
125
|
throw new Error(
|
|
@@ -122,7 +135,10 @@ export function youtubeEmbed(options: YouTubeEmbedOptions): YouTubeEmbed {
|
|
|
122
135
|
// `youtube-nocookie.com` rather than `youtube.com`: the same player, which
|
|
123
136
|
// sets no cookie until the video is played — and here it is only ever
|
|
124
137
|
// loaded to be played, so nothing is lost by it.
|
|
125
|
-
const
|
|
138
|
+
const url = new URL(`https://www.youtube-nocookie.com/embed/${id}`);
|
|
139
|
+
url.searchParams.set("autoplay", "1");
|
|
140
|
+
url.searchParams.set("rel", rel ? "1" : "0");
|
|
141
|
+
if (playsInline) url.searchParams.set("playsinline", "1");
|
|
126
142
|
|
|
127
143
|
return {
|
|
128
144
|
attach(trigger, container) {
|
|
@@ -133,7 +149,7 @@ export function youtubeEmbed(options: YouTubeEmbedOptions): YouTubeEmbed {
|
|
|
133
149
|
() => {
|
|
134
150
|
const iframe =
|
|
135
151
|
container.ownerDocument.createElement("iframe");
|
|
136
|
-
iframe.src =
|
|
152
|
+
iframe.src = url.href;
|
|
137
153
|
iframe.title = title;
|
|
138
154
|
iframe.allow = ALLOW;
|
|
139
155
|
iframe.allowFullscreen = true;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** `"en"` for `"en-GB"`: everything before the first dash. */
|
|
2
|
+
export type LanguageOf<L extends string> =
|
|
3
|
+
L extends `${infer Language}-${string}` ? Language : never;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Every locale, holding its language's text.
|
|
7
|
+
*
|
|
8
|
+
* Keeps each string's literal type, so a message built this way has its
|
|
9
|
+
* placeholders checked exactly like one written out by hand.
|
|
10
|
+
*/
|
|
11
|
+
export type PerLanguage<L extends string, T> = {
|
|
12
|
+
readonly [K in L]: T[LanguageOf<K> & keyof T];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One message, written once per language and filled in for each of its tags.
|
|
17
|
+
*
|
|
18
|
+
* For a site that publishes one language under several regions, where `en-GB`
|
|
19
|
+
* and `en-IE` are different tags for `hreflang` but the same words. Checked here
|
|
20
|
+
* as well as in the types, because the cast below stops the compiler seeing
|
|
21
|
+
* this body, and a caller's `as any` would otherwise publish `undefined`.
|
|
22
|
+
*/
|
|
23
|
+
export function expandLanguages<L extends string, T>(
|
|
24
|
+
locales: readonly L[],
|
|
25
|
+
texts: T
|
|
26
|
+
): PerLanguage<L, T> {
|
|
27
|
+
const given = texts as Readonly<Record<string, string>>;
|
|
28
|
+
const languages = new Set(locales.map((tag) => tag.split("-")[0]));
|
|
29
|
+
for (const language of Object.keys(given)) {
|
|
30
|
+
if (!languages.has(language)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`perLanguage: "${language}" is not a language this site declares.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const entries = locales.map((tag) => {
|
|
37
|
+
const language = tag.split("-")[0] as string;
|
|
38
|
+
const text = given[language];
|
|
39
|
+
if (text === undefined) {
|
|
40
|
+
throw new Error(`perLanguage: no text for "${language}" (${tag}).`);
|
|
41
|
+
}
|
|
42
|
+
return [tag, text];
|
|
43
|
+
});
|
|
44
|
+
return Object.fromEntries(entries) as PerLanguage<L, T>;
|
|
45
|
+
}
|
package/src/site/define.ts
CHANGED
|
@@ -10,6 +10,11 @@ import {
|
|
|
10
10
|
type NoExtraLocales,
|
|
11
11
|
type ValidateBase,
|
|
12
12
|
} from "../i18n/define.ts";
|
|
13
|
+
import {
|
|
14
|
+
expandLanguages,
|
|
15
|
+
type LanguageOf,
|
|
16
|
+
type PerLanguage,
|
|
17
|
+
} from "../i18n/per-language.ts";
|
|
13
18
|
import {
|
|
14
19
|
bindRedirects,
|
|
15
20
|
type DefinedProject,
|
|
@@ -86,6 +91,13 @@ export function defineSite<
|
|
|
86
91
|
return defineMessages(config, catalog as never) as T;
|
|
87
92
|
},
|
|
88
93
|
|
|
94
|
+
/** One message per language, filled in for each of that language's tags. */
|
|
95
|
+
perLanguage<const T extends Readonly<Record<LanguageOf<L>, string>>>(
|
|
96
|
+
texts: T & Readonly<Record<Exclude<keyof T, LanguageOf<L>>, never>>
|
|
97
|
+
): PerLanguage<L, T> {
|
|
98
|
+
return expandLanguages(Object.keys(config.locales) as L[], texts);
|
|
99
|
+
},
|
|
100
|
+
|
|
89
101
|
/** One deployment: what differs from the defaults. */
|
|
90
102
|
project<
|
|
91
103
|
const Catalog extends BaseCatalog<L>,
|