Skip to content

Make the site findable, in both languages - #64

Merged
temoki merged 2 commits into
mainfrom
site/found-by-search
Aug 27, 2026
Merged

Make the site findable, in both languages#64
temoki merged 2 commits into
mainfrom
site/found-by-search

Conversation

@temoki

@temoki temoki commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Two commits, because they are two different things. Neither changes a word
the reader sees, and neither changes how the site works.

The Japanese page did not exist

A crawler renders the page, which means it negotiates like a browser.
Googlebot's navigator.languages is en-US, so the script resolves en,
html[data-lang] section[lang] { display: none } hides the whole Japanese
<section>, and that happens on the one URL anybody links to. The Japanese
page was not ranking badly — there was nothing there to rank.

Verified in headless Chrome against a local copy of site/, which is the
same renderer Google uses:

URL data-lang title
/ (Accept-Language en-US) en Snap blocks together, watch the tortoise draw
/?lang=en en Snap blocks together, watch the tortoise draw
/?lang=ja ja ブロックをならべて、カメに絵をかいてもらおう

So the mechanism was already there — ?lang= is what App Store Connect's
per-localization URLs point at, and it renders a genuinely different page.
Nothing declared it. This adds the three things that do: hreflang alternates
naming ?lang=en, ?lang=ja and the bare URL as x-default; real
<a href="?lang=…"> links in both footers, because a <select> is not
something a crawler can follow and the Japanese URL was otherwise reachable
from nowhere; and a per-language <meta name="description">, carried on the
section beside the data-title that was already there.

No rel=canonical, deliberately. It would have to differ per URL, and one
file cannot say three things: a static one pointing anywhere folds the other
two into it and un-indexes the Japanese page, while a script-written one is
the case Google says not to rely on. hreflang carries the relationship.

The no-JS rule still holds, though it is argued from the source rather than
rendered — Chrome cannot --dump-dom with scripting disabled. The served
<html> tag has no data-lang, that attribute is written only by the inline
script, and it is what the hiding rule keys on; this change touches no CSS
and no <html> tag.

A sitemap and a structured-data block

site/sitemap.xml lists six URLs rather than two: an hreflang set names every
variant from every variant. No lastmod — the site is deployed by hand at
release time, so it would go stale the first time someone forgot it, and
Google ignores a lastmod it cannot trust.

No robots.txt, and that is not an oversight. A robots.txt is only read
at the host root, temoki.github.io/robots.txt, which belongs to no
repository here. One added to site/ would be served, ignored, and easy to
mistake for working. The sitemap gets submitted in Search Console instead — a
sitemap may sit at any level at or above the URLs it lists.

The JSON-LD is the weakest of the four items and is written to stay checkable:
price, family rating and seller from the App Store listing, the license from
LICENSE, the requirements from the specs table further down the page. It
carries no aggregateRating and must not grow one nobody left. An
alternateName in katakana was written and then removed — the app's Japanese
App Store name is "Tortoise Blocks", so the katakana form was invented.

Deploying

pages.yml is dispatch-only, so this needs a run against main after merge.
main's site/ is what is serving now, so that deploy publishes exactly these
changes. The sitemap can only be submitted after that, since Search Console
fetches it to accept it.

temoki added 2 commits August 27, 2026 20:17
Both pages already read in one language and already take it from `?lang=`,
which is where App Store Connect's per-localization URLs point. What was
missing is that nothing said so. A crawler renders the page, the script
negotiates from navigator.languages — `en-US` for Googlebot — and the
Japanese half goes display:none, on the one URL anybody links to. So the
Japanese page was not ranked badly; it did not exist.

Three things fix that, and none of them changes how the site works.

hreflang alternates in both heads, naming `?lang=en`, `?lang=ja` and the bare
URL as x-default. The set is identical on every variant, self-reference
included, which is what lets one file serve all three. There is deliberately
no rel=canonical: it would have to differ per URL, and a static one pointing
anywhere folds the other two into it and un-indexes the Japanese page, while
a script-written one is the case Google says not to rely on.

Real links in both footers. The picker is a <select>, which a crawler cannot
follow, so until now the Japanese URL was declared in the head and reachable
from nowhere.

And a per-language meta description, carried on the section like the title
already was, so adding a language is still a code, an <option> and a
translated <section>. The static one stays bilingual, because that is what
the bare URL and a script-less reader get.

The OG card stays single and stays English, and the comment saying why now
says the real reason: a social scraper does not run the script that picks a
language, so per-language cards would have to be written by JavaScript
nothing would execute.
A sitemap and a structured-data block, which is the rest of what the search
turned up. Neither changes a word the reader sees.

site/sitemap.xml lists six URLs rather than two: an hreflang set names every
variant from every variant, so each of the three addresses each page answers
to gets an entry carrying the whole set. It has no lastmod, changefreq or
priority — Google ignores the last two, and lastmod only counts while it is
honest, which a hand-deployed site would not keep it.

It is meant to be submitted in Search Console rather than announced from a
robots.txt, and that is not a preference. A robots.txt is only read at the
host root — temoki.github.io/robots.txt — which belongs to no repository
here, so one added to site/ would be served, ignored, and easy to mistake
for working. A sitemap may sit at any level at or above what it lists.

The JSON-LD is the weakest item of the four and is written to stay checkable:
the price, the family rating and the seller come from the App Store listing,
the license from LICENSE, the requirements from the specs table further down
the page. It carries no aggregateRating, and must not grow one that nobody
left.
@temoki
temoki merged commit d94bcf8 into main Aug 27, 2026
7 checks passed
@temoki
temoki deleted the site/found-by-search branch August 27, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant