I work on OnePassApps, the team behind app.onepassapps.com. This post is about the language layer of our own site, and every number in it was measured on 9 November 2026 over plain HTTPS from outside the browser - one build, one network, signed out, one fetch per URL, no session and no cookies.
The markup declares one locale, and the server never changes it
All 56 URLs in our sitemap answer with lang="en-GB" on the html element and og:locale set to en_GB. Our head-contract post from 8 November already recorded the missing hreflang link and the locale value. What that post did not look at is what happens when a client asks for a different language.
Ask for French:
# request shape: no Accept header, or Accept: text/html
GET /apps/word-counter 200 19,346 bytes
GET /apps/word-counter Accept-Language: fr-FR,fr;q=0.9 200 19,346 bytes
GET /apps/word-counter?lang=fr 200 19,346 bytes
The response is the same length as the plain request (19,346 bytes in the request shape used above) and its visible text is identical to it. The html element still says lang="en-GB". No response of ours carries a Content-Language header, and none carries a Vary header - 0 of 56 and 0 of 56 respectively. Nothing on the wire varies by the language the visitor asked for.
A query string does not change it either. /apps/word-counter?lang=fr comes back with the same visible text and the same length as /apps/word-counter, and /?lang=fr comes back with the same visible text and the same length as the home page without it.
Putting the locale in the path does not work either, because those paths do not exist. /en/, /en-GB/, /fr/ and /en-GB/apps/word-counter all answer 404 with the same missing-page document we serve for any missing URL. That document declares lang="en-GB" and og:locale="en_GB" as well - so even the page that is not there claims a locale.
The copy matches the declaration
Strip the scripts and the tags out of all 56 documents and count the British and American spellings of thirteen word pairs in the visible text:
| British | count | American | count |
|---|---|---|---|
| colour | 12 | color | 0 |
| catalogue | 14 | catalog | 0 |
| licence | 11 | license | 1 |
| organise | 8 | organize | 0 |
| organisation | 3 | organization | 0 |
| labelled | 2 | labeled | 0 |
| cancelled | 1 | canceled | 0 |
| metre | 2 | meter | 0 |
| practise | 1 | practice | 0 |
| whilst | 0 | while | 8 |
| centre | 0 | center | 0 |
| defence | 0 | defense | 0 |
| grey | 0 | gray | 0 |
Every American column is zero except one. The single license is the verb in our terms page - "These grants do not license other film imagery" - and in British English the noun is licence and the verb is license, so that is the correct form, not an American spelling. licence itself appears 11 times.
One pair says nothing in either direction: while appears 8 times and whilst 0 times. while is standard British English too, so the site is not quoting a stereotype - it just does not use the word whilst.
Where the British spellings sit is where you would expect: colour is on the colour studio route and on the pages that talk about colour - /, /studios/colour, /apps/screen-planner, /apps/social-card, /apps/qr-code-maker, /guides/screen-planner, /guides/social-card, /guides/qr-code-maker - and catalogue appears 14 times, organise 8 times, organisation 3 times, labelled 2 times, cancelled 1 times, metre 2 times and practise 1 time, that last one in the teleprompter guide's "Pause to revise or practise again".
One number in this post moves with the request headers
The byte length of a page on this site depends on the Accept header the client sends. /apps/word-counter came back 19,346 bytes when the request carried no Accept header or Accept: text/html, and 18,979 bytes when it carried Accept: */* - the header curl sends by default - a difference of 367 bytes. The missing-page document moves by the same 367 bytes (14,173 against 13,806), and so does the home page (25,011 against 24,644). The visible text is identical in every one of those cases. The documents are not byte-identical to themselves either: fetching the same URL twice with the same headers returns two copies that differ, because this site serves per-request content - Cloudflare rewrites the protected email address on every request, and the encoded form it writes is not the same length each time. So the byte length of a page here depends on the request headers and on the request itself.
The lengths in this post are therefore quoted as measured, in one request shape, and what the comparison is actually about is that the three requests come back the same length as each other - not that a page has one fixed size.
What this adds up to
The language of the site is declared in one place, in the markup, and it is declared consistently: 56 pages, one value each, and a copy that reads in that language. The HTTP layer adds nothing to it - no negotiation, no header, no locale-prefixed route, no alternate link - so a crawler is told one language and gets one language, and a cache is never asked to vary on a request header that changes nothing.
Limits
One build, one day (9 November 2026), one fetch per URL, plain HTTPS from one network, signed out, and no browser opened - so the "visible text" in this post is the served HTML with scripts, styles and tags removed, not what a browser paints. The 56 URLs are the ones in our sitemap and are not an inventory of the site. The byte lengths in this post depend on the Accept header sent with the request, as the section above explains. The thirteen word pairs are the ones we chose to count, not a dictionary audit of every British and American variant. We sent one non-English request (fr-FR) and one query string, and we did not test whether a browser would ask the server for a different language before sending the request. The missing-page document is the one we serve for every URL that does not exist.
Try it yourself: https://app.onepassapps.com?utm_source=devto&utm_medium=post&utm_campaign=locale-layer&utm_content=home-link
The guides list every app we have: https://app.onepassapps.com/guides?utm_source=devto&utm_medium=post&utm_campaign=locale-layer&utm_content=guides-link
Top comments (0)