diff --git a/index.html b/index.html index 1e0216af1..b8b83ce5d 100644 --- a/index.html +++ b/index.html @@ -28,6 +28,7 @@ + {{website_schema}} diff --git a/scripts/build-site.py b/scripts/build-site.py index 66afa6aff..98ab188c1 100644 --- a/scripts/build-site.py +++ b/scripts/build-site.py @@ -18,6 +18,10 @@ def locale_path(locale): return f"/{locale}/" +def public_path(locale): + return "/" if locale == "en-US" else locale_path(locale) + + def load_catalogs(): catalogs = {p.stem: json.loads(p.read_text()) for p in sorted((ROOT / "site/locales").glob("*.json"))} supported = {p.parent.name for p in (ROOT / "locales").glob("*/opencadstudio.ftl")} @@ -65,14 +69,14 @@ def build(output): script = "const SITE_LOCALES = " + json.dumps(list(catalogs)) + ";\n" + (ROOT / "site/site.js").read_text() (output / "site.js").write_text(script, encoding="utf-8") js_version = hashlib.sha256(script.encode()).hexdigest()[:12] - alternates = '\n'.join(f'' for lang in catalogs) + alternates = '\n'.join(f'' for lang in catalogs) alternates += f'\n' for locale, messages in catalogs.items(): - path = locale_path(locale) - destination = output / path.lstrip("/") + path = public_path(locale) + destination = output / locale_path(locale).lstrip("/") destination.mkdir(parents=True, exist_ok=True) language_links = '\n'.join( - f'{escape(labels["name"])}' for lang, labels in catalogs.items() ) @@ -85,6 +89,11 @@ def build(output): "downloadUrl": REPO + "/releases/latest", "installUrl": BASE + "/app/", "offers": {"@type": "Offer", "price": "0", "priceCurrency": "USD"}, } + website_schema = { + "@context": "https://schema.org", "@type": "WebSite", + "name": "Open CAD Studio", "alternateName": ["OpenCADStudio", "opencadstudio.com"], + "url": BASE + "/", + } values = {key: escape(value) for key, value in messages.items()} values.update(locale=locale, direction="rtl" if locale == "ar-SA" else "ltr", path=path, url=BASE + path, og_locale=locale.replace("-", "_"), @@ -92,6 +101,8 @@ def build(output): css_version=css_version, js_version=js_version, alternates=alternates, languages=escape(messages["languages"].format(count=len(catalogs))), language_links=language_links, + website_schema=(f'' + if locale == "en-US" else ""), schema=json.dumps(schema, ensure_ascii=False).replace("<", "\\u003c")) rendered = re.sub(r"\{\{(\w+)\}\}", lambda match: values[match[1]], template) (destination / "index.html").write_text(rendered, encoding="utf-8") @@ -101,7 +112,7 @@ def build(output): (destination / "site.webmanifest").write_text(json.dumps(localized_manifest, ensure_ascii=False, indent=2) + '\n', encoding="utf-8") for filename in ("index.html", "site.webmanifest"): shutil.copyfile(output / "en-US" / filename, output / filename) - urls = f'{BASE}/\n' + ''.join(f'{BASE}{locale_path(locale)}\n' for locale in catalogs) + urls = ''.join(f'{BASE}{public_path(locale)}\n' for locale in catalogs) (output / "sitemap.xml").write_text('\n\n' + urls + '\n') app = output / "app/index.html" diff --git a/scripts/test_site.py b/scripts/test_site.py index a13055589..a13ae82a8 100644 --- a/scripts/test_site.py +++ b/scripts/test_site.py @@ -57,7 +57,8 @@ with tempfile.TemporaryDirectory() as directory: links = [attrs for tag, attrs in page.tags if tag == 'link'] assert {link['href'] for link in links if link.get('rel') == 'icon'} == {'/favicon.ico', '/favicon.png', '/favicon.svg'} assert {link['hreflang'] for link in links if link.get('rel') == 'alternate'} == supported | {'x-default'} - assert next(link['href'] for link in links if link.get('rel') == 'canonical') == f'https://www.opencadstudio.com/{locale}/' + expected_path = '/' if locale == 'en-US' else f'/{locale}/' + assert next(link['href'] for link in links if link.get('rel') == 'canonical') == f'https://www.opencadstudio.com{expected_path}' choices = [attrs for tag, attrs in page.tags if tag == 'a' and 'hreflang' in attrs] assert {choice['hreflang'] for choice in choices} == supported assert [choice['hreflang'] for choice in choices if choice.get('aria-current') == 'page'] == [locale] @@ -74,7 +75,7 @@ with tempfile.TemporaryDirectory() as directory: path = output / urlsplit(target).path.lstrip('/') assert path.exists(), (locale, target) manifest = json.loads((output / locale / 'site.webmanifest').read_text()) - assert manifest['lang'] == locale and manifest['start_url'] == f'/{locale}/' + assert manifest['lang'] == locale and manifest['start_url'] == expected_path for icon in manifest['icons']: assert (output / icon['src'].lstrip('/')).exists() if locale != 'en-US': @@ -87,7 +88,13 @@ with tempfile.TemporaryDirectory() as directory: assert icon == '/favicon.svg' assert (output / icon.lstrip('/')).read_bytes() == (ROOT / 'site/favicon.svg').read_bytes() sitemap = ET.parse(output / 'sitemap.xml') - assert len(sitemap.getroot()) == len(supported) + 1 + locations = {url[0].text for url in sitemap.getroot()} + assert len(locations) == len(supported) + assert 'https://www.opencadstudio.com/' in locations + assert 'https://www.opencadstudio.com/en-US/' not in locations assert (output / 'index.html').read_bytes() == (output / 'en-US/index.html').read_bytes() + homepage_schemas = [json.loads(value) for value in re.findall(r'', (output / 'index.html').read_text())] + website = next(schema for schema in homepage_schemas if schema['@type'] == 'WebSite') + assert website['url'] == 'https://www.opencadstudio.com/' and 'OpenCADStudio' in website['alternateName'] subprocess.run(['node', 'scripts/test_site.cjs', str(output / 'site.js')], cwd=ROOT, check=True) print(f'All {len(supported)} translations, metadata, local links, charts, manifests and icons passed')