Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Support for dynamically setting the lang attribute #1695

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 32 additions & 12 deletions runtime/src/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
HydratedTarget,
Target,
Redirect,
BranchSegment,
Branch,
Page,
InitialData
Expand Down Expand Up @@ -136,6 +137,11 @@ async function handle_target(dest: Target): Promise<void> {
const { props, branch } = hydrated_target;
await render(branch, props, buildPageContext(props, dest.page));
}


const { lang } = hydrated_target;

if (lang) document.querySelector('html').setAttribute('lang', lang);
}

async function render(branch: Branch, props: any, page: PageContext) {
Expand Down Expand Up @@ -207,14 +213,21 @@ export async function hydrate_target(dest: Target): Promise<HydratedTarget> {
}
};

let lang: string;

if (!root_preloaded) {
const root_preload = root_comp.preload || (() => ({}));
root_preloaded = initial_data.preloaded[0] || root_preload.call(preload_context, {
const page_context = {
host: page.host,
path: page.path,
query: page.query,
params: {}
}, $session);
};
root_preloaded = initial_data.preloaded[0] || root_preload.call(preload_context, page_context, $session);

if (root_comp.lang) {
lang = root_comp.lang(page_context);
}
}

let branch: Branch;
Expand All @@ -236,31 +249,38 @@ export async function hydrate_target(dest: Target): Promise<HydratedTarget> {

const j = l++;

let result;
let result: BranchSegment;

if (!session_dirty && !segment_dirty && current_branch[i] && current_branch[i].part === part.i) {
result = current_branch[i];
lang = result.lang || lang;
} else {
segment_dirty = false;

const { default: component, preload } = await components[part.i].js();
const { default: component, preload, lang: get_lang } = await components[part.i].js();

const page_context = {
host: page.host,
path: page.path,
query: page.query,
params: part.params ? part.params(dest.match) : {}
};

let preloaded: object;

if (ready || !initial_data.preloaded[i + 1]) {
preloaded = preload
? await preload.call(preload_context, {
host: page.host,
path: page.path,
query: page.query,
params: part.params ? part.params(dest.match) : {}
}, $session)
? await preload.call(preload_context, page_context, $session)
: {};
} else {
preloaded = initial_data.preloaded[i + 1];
}

result = { component, props: preloaded, segment, match, part: part.i };
if (get_lang) {
lang = get_lang(page_context);
}

result = { component, props: preloaded, segment, match, part: part.i, lang };
}

return (props[`level${j}`] = result);
Expand All @@ -271,5 +291,5 @@ export async function hydrate_target(dest: Target): Promise<HydratedTarget> {
branch = [];
}

return { redirect, props, branch };
return { redirect, props, branch, lang };
}
9 changes: 7 additions & 2 deletions runtime/src/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ export interface HydratedTarget {
preload_error?: any;
props: any;
branch: Branch;
lang?: string;
}

export type Branch = Array<{
export interface BranchSegment {
segment: string;
props?: object;
match?: RegExpExecArray;
component?: DOMComponentConstructor;
part?: number;
}>;
lang?: string;
}

export type Branch = BranchSegment[];

export type InitialData = {
session: any;
Expand Down
5 changes: 3 additions & 2 deletions runtime/src/internal/manifest-client.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { PageParams } from '@sapper/common';
import { PageContext, PageParams } from '@sapper/common';
import {
Preload
} from './shared';

export interface DOMComponentModule {
default: DOMComponentConstructor;
preload?: Preload;
lang?: (ctx: PageContext) => string;
}

export interface DOMComponent {
Expand All @@ -32,5 +33,5 @@ export interface Route {
export const ErrorComponent: DOMComponentConstructor;
export const components: DOMComponentLoader[];
export const ignore: RegExp[];
export const root_comp: { preload: Preload };
export const root_comp: { preload: Preload; lang?: (ctx: PageContext) => string };
export const routes: Route[];
4 changes: 3 additions & 1 deletion runtime/src/internal/manifest-server.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Preload
Preload,
PageContext
} from './shared';

export const src_dir: string;
Expand All @@ -12,6 +13,7 @@ export { SapperRequest, SapperResponse } from '@sapper/server';
export interface SSRComponentModule {
default: SSRComponent;
preload?: Preload;
lang?: (ctx: PageContext) => string;
}

export interface SSRComponent {
Expand Down
34 changes: 22 additions & 12 deletions runtime/src/server/middleware/get_page_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,21 +172,30 @@ export function get_page_handler(
let match: RegExpExecArray;
let params: Record<string,string>;

let lang = 'en';

const page_context: PageContext = {
host: req.headers.host,
path: req.path,
query: req.query,
params: {}
};

try {
const root_preload = manifest.root_comp.preload || (() => {});
const root_preloaded: PreloadResult = detectClientOnlyReferences(() =>
root_preload.call(
preload_context,
{
host: req.headers.host,
path: req.path,
query: req.query,
params: {}
},
page_context,
session
)
);

if (manifest.root_comp.lang) {
lang = detectClientOnlyReferences(() =>
manifest.root_comp.lang(page_context));
}

match = error ? null : page.pattern.exec(req.path);

let toPreload: PreloadResult[] = [root_preloaded];
Expand All @@ -197,16 +206,16 @@ export function get_page_handler(
// the deepest level is used below, to initialise the store
params = part.params ? part.params(match) : {};

if (part.component.lang) {
lang = detectClientOnlyReferences(() =>
part.component.lang({ ...page_context, params }));
}

return part.component.preload
? detectClientOnlyReferences(() =>
part.component.preload.call(
preload_context,
{
host: req.headers.host,
path: req.path,
query: req.query,
params
},
{ ...page_context, params },
session
)
)
Expand Down Expand Up @@ -381,6 +390,7 @@ export function get_page_handler(
.replace('%sapper.html%', () => html)
.replace('%sapper.head%', () => head)
.replace('%sapper.styles%', () => styles)
.replace('%sapper.lang%', () => lang)
.replace(/%sapper\.cspnonce%/g, () => nonce_value);

res.statusCode = status;
Expand Down
2 changes: 1 addition & 1 deletion site/src/template.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctype html>
<html lang='en' class="theme-default typo-default">
<html lang='%sapper.lang%' class="theme-default typo-default">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
Expand Down
8 changes: 8 additions & 0 deletions test/apps/basics/src/routes/lang/[lang].svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script context="module">
export function lang(context) {
return context.params.lang;
}
</script>

<a id="sv" href="/lang/sv">sv</a>
<a id="en" href="/lang/en">en</a>
2 changes: 1 addition & 1 deletion test/apps/basics/src/template.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="%sapper.lang%">
<head>
<meta charset='utf-8'>

Expand Down
12 changes: 12 additions & 0 deletions test/apps/basics/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,18 @@ describe('basics', function() {
assert.equal(html.indexOf('%sapper'), -1);
});

it('replaces %sapper.lang%', async () => {
await r.load('/lang/sv');
await r.sapper.start();

const get_document_lang = () => r.page.evaluate(() => document.documentElement.lang);
assert.equal(await get_document_lang(), 'sv');

await r.page.click('#en');
await r.wait();
assert.equal(await get_document_lang(), 'en');
});

it('navigates between routes with empty parts', async () => {
await r.load('/dirs/foo');
await r.sapper.start();
Expand Down