diff --git a/bin/build-pages.mjs b/bin/build-pages.mjs index fe1d2981..d607b902 100644 --- a/bin/build-pages.mjs +++ b/bin/build-pages.mjs @@ -832,7 +832,9 @@ ${langOptions}
.json`. The site shell has
diff --git a/public/history-recent.js b/public/history-recent.js
index 6865ce97..254441f5 100644
--- a/public/history-recent.js
+++ b/public/history-recent.js
@@ -78,7 +78,11 @@
// not depend on a script having run.
var resume = document.createElement('a');
resume.className = 'recent-resume';
- resume.href = '/editor?saved=' + encodeURIComponent(doc.id) + suffix;
+ // Keep the page's language on the way into the editor: this script runs
+ // on /ja/, /pt/ and the rest, and the editor resolves ?locale= first.
+ var pageLang = document.documentElement.lang || 'en';
+ var localeParam = pageLang && pageLang !== 'en' ? '&locale=' + encodeURIComponent(pageLang) : '';
+ resume.href = '/editor?saved=' + encodeURIComponent(doc.id) + localeParam + suffix;
resume.textContent = (slot.getAttribute('data-recent-label') || 'Continue') + ' ' + doc.title;
slot.appendChild(resume);
diff --git a/public/lang-switch.js b/public/lang-switch.js
index 2aa8df33..05d4843a 100644
--- a/public/lang-switch.js
+++ b/public/lang-switch.js
@@ -16,6 +16,18 @@ document.addEventListener('DOMContentLoaded', function () {
if (!value) return;
var option = select.querySelector('r-option[value="' + value + '"]');
var href = option && option.getAttribute('data-href');
- if (href && href !== location.pathname) location.href = href;
+ if (!href || href === location.pathname) return;
+ // Remember the choice before navigating. The static pages carry their
+ // language in the URL, but /editor and /history are one app that reads
+ // its language from (in order) ?locale=, this cookie, localStorage and
+ // the browser. Without the cookie, picking 日本語 on the homepage and
+ // then opening the saved-documents page landed the reader back in
+ // English -- the choice existed only as the path they were standing on.
+ try {
+ document.cookie = 'locale=' + encodeURIComponent(value) + ';path=/;max-age=31536000;samesite=lax';
+ } catch (e) {
+ /* cookies disabled: the URL still carries the language */
+ }
+ location.href = href;
});
});
diff --git a/test/e2e/language-menu.spec.ts b/test/e2e/language-menu.spec.ts
index 7d10c42e..312cfff6 100644
--- a/test/e2e/language-menu.spec.ts
+++ b/test/e2e/language-menu.spec.ts
@@ -92,6 +92,36 @@ test.describe('language menu', () => {
});
});
+test.describe('a chosen language follows the reader', () => {
+ /**
+ * The site is seven directories of static pages plus one app (/editor and
+ * /history) that resolves its language from ?locale=, then a cookie, then
+ * localStorage, then the browser. Picking a language on a static page used
+ * to be nothing but a navigation: the choice lived in the path, and the
+ * moment a reader stepped off it -- "saved documents" from the Japanese
+ * homepage -- they were back in English.
+ */
+ test('picking a language on a static page reaches the app pages too', async ({ page }) => {
+ await page.goto('/');
+ // Choose 日本語 through the menu, the way a reader does.
+ await page.locator('r-select.lang-select').first().click();
+ await page.locator('r-dropdown-item[value="ja"]').first().click();
+ await page.waitForURL('**/ja/');
+ expect(await page.evaluate(() => document.documentElement.lang)).toBe('ja');
+
+ // The link into the app carries it...
+ const historyHref = await page.locator('a.recent-all').getAttribute('href');
+ expect(historyHref).toContain('locale=ja');
+
+ // ...and so does the app itself, even at a bare URL, because the choice
+ // was remembered rather than only navigated to.
+ await page.goto('/history');
+ await expect.poll(() => page.evaluate(() => document.documentElement.lang)).toBe('ja');
+ await page.goto('/editor?new=docx');
+ await expect.poll(() => page.evaluate(() => document.documentElement.lang)).toBe('ja');
+ });
+});
+
test.describe('page chrome', () => {
/**
* The browser's default 8px body margin. landing.css always cleared it;
diff --git a/test/unit/landing-pages.test.ts b/test/unit/landing-pages.test.ts
index 98dec821..0c514302 100644
--- a/test/unit/landing-pages.test.ts
+++ b/test/unit/landing-pages.test.ts
@@ -166,6 +166,21 @@ describe('landing pages', () => {
expect(alternates.sort(), `${route} og:locale:alternate`).toEqual(expected.sort());
});
+ /**
+ * /editor and /history are one app serving every language, so a link from
+ * a translated page into them has to say which one. Picking 日本語 on the
+ * homepage and then opening the saved-documents page used to land the
+ * reader back in English: the choice existed only as the path they were
+ * standing on, and /history is not under it.
+ */
+ it('carries the language on every link into the app', () => {
+ const appLinks = [...html.matchAll(/(?:href|data-open-local)="(\/(?:editor|history)[^"]*)"/g)].map((m) => m[1]);
+ for (const link of appLinks) {
+ const carries = link.includes(`locale=${locale}`);
+ expect(locale === 'en' ? !link.includes('locale=') : carries, `${route}: ${link}`).toBe(true);
+ }
+ });
+
it('offers every translation it has in the language switch', () => {
for (const other of Object.keys(LOCALES)) {
const target = routeIn(other, enRoute);