Build1 distinct publisher3 min readUpdated
DukoTools hardcoded its locale list in four separate files and none of them checked whether a translation existed. The audit that followed found a larger gap in a different namespace.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
Follow any of these and your For You feed starts watching them — no settings page required.
The multiplication is worth doing, because the build was doing it silently. A catalogue of 112 tools across five locales is 560 generated tool URLs, 448 of them non-English [13]. The heading audit found 103 tools whose H1 fell back to English [8], and since every locale combination was being built [4], that is roughly 412 URLs carrying an English title under a lang attribute that said otherwise [14]. Ninety-two percent of the catalogue [16].
The 22 and the 103 are not the same defect, and the difference decides whether the fix holds. The 22 came from missing entries in the SEO namespaces, where `??` quietly substituted the English copy [5][2]. The 103 came from `tools[slug].name`, a separate namespace nobody populated after the original nine-tool launch batch [9]. The new gate asks `hasRealTranslation(slug, locale)` [6], and the writeup does not say which namespace that predicate reads. If it reads the SEO copy alone, then 81 tools clear the gate with a translated FAQ sitting under an English heading [15], which is the original bug at smaller scale. One source of truth centralises the answer. It does not decide what counts as translated.
The localised 404 page becomes load-bearing at the moment the gate ships, not before. Previously an untranslated locale returned 200 [3]; now it calls `notFound()` [7], so the volume of real 404s under `/ar/` and `/ur/` rises sharply on deploy day. That is exactly when the old default error page, English, LTR, and carrying no `noindex`, would have been picked up as content [10]. Shipping the availability gate without the not-found rewrite would have swapped one indexable falsehood for another. Omitting `alternates` from the 404 metadata [11] is the same discipline applied a second time: do not annotate a page as the counterpart of something that does not exist.
One inconsistency in the account is instructive. The post says the locale logic lived in five places, then lists four files and calls it four copies [18]. The likely fifth is the content component holding the `??` fallback [5], which is the code that actually produced the English body. The four arrays only agreed that the URL should exist. That split is why the bug had no symptom: the builders and the renderer each behaved correctly on their own terms, and no layer owned the question of whether the combination was honest.
Untranslated means 404, not English in a foreign-language wrapper [12]. Cheap to write down, expensive to enforce, because enforcement needs a single predicate that knows every namespace a page renders from. Get that wrong and the build keeps producing well-formed, confident pages that no visitor will ever think to report.
Ranked by verification strength, evidence, and original report placement.
DukoTools supports 5 languages (English, Spanish, Arabic, French, Urdu), has 112 tools, a language switcher in the header, and hreflang tags on every page pointing to the Spanish, Arabic and other versions.
A page can return HTTP 200, declare lang="ar", claim via hreflang to be the authoritative Arabic version of itself, and still render 100% English body text. That is a soft 404: invisible to a human visitor, while a crawler sees a page claiming to be something it isn't, and search engines treat that mismatch as a trust signal of the wrong kind.
Auditing every tool's messages.tools[slug].name (the actual page heading) against all 5 locale files found that 9 out of 112 tools had a translated heading; the other 103 fell back to English for the H1 regardless of which language a visitor selected, since launch.
The heading lives in a different namespace (tools[slug]) from the SEO content (toolAbout, toolFeatures and similar), and nobody had populated it for anything past the original 9-tool launch batch; a user could select Urdu and see a fully translated FAQ section under an English title.
The author assumed a page existing at /ar/tools/some-tool meant the Arabic translation existed; that assumption was wrong for 22 tools, and he only found out because someone left a comment on his previous post.
The content component did `const localeAbout = messages.toolAbout?.[slug]; const displayAbout = localeAbout ?? content.about;` which the author describes as a silent English fallback and as reasonable-looking code that is exactly how you build a page that lies about its own language.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Self-reported but code-level and specific
The mechanism claims are unusually concrete for a single-source post: real file names, the duplicated LOCALES arrays, the `??` fallback line, getAvailableLocales, dynamicParams=false, and the not-found metadata block are all quoted. Counts (112 tools, 22 broken, 9 of 112 headings, 15 of 22 fixed) come solely from the author's own audit and his own curl checks, with no Search Console, crawl or third-party verification, and the post contradicts itself on whether the locale array appeared in four or five places. Enough to trust the pattern; not enough to trust the numbers precisely or the asserted SEO consequences.
One solo site, partial remediation
Adoption evidence is confined to a single unfunded solo project. The fix is deployed there — single-source locale gate, static-params rejection, runtime 404 guard, localized noindex 404 page — and remediation is partial: 15 of 22 broken tools translated, 7 correctly absent, and the 103-tool heading gap still outstanding at publication. No other project, team, library or vendor is reported to use the pattern, and there are no downloads, stars, users or traffic figures.
Mildly overstated consequences, understated caveats
Tone is self-critical rather than promotional, which keeps the gap small. It is positive rather than zero because the causal harm — that search engines read the lang/hreflang mismatch as a bad trust signal and that the site 'under-delivered for months' — is asserted with zero index, impression or ranking data, while the headline framing ('lied to every crawler') is stronger than the demonstrated facts of English fallback text. The post also leaves its own four-versus-five duplication count unreconciled and does not flag the de-indexing risk of pulling previously buildable non-English URLs from the sitemap.
Personal-brand and project promotion, no vendor stake
The author is writing about his own product and closes with a promotional sign-off ('Building DukoTools solo, 106+ free tools, no ads, 5 languages'), and the piece is explicitly framed as the follow-up to his own prior indexing post — a serialised dev.to audience play. That biases toward a tidy narrative and toward figures no one else can check. Counterweights are real: the post is an admission of a months-long self-inflicted defect, discloses incomplete remediation, and the project is free with no ads, so there is no pricing, licensing or funding interest at stake.
Low-moderate: one publisher, one self-audited site
Single source, single publisher, single project, and every quantitative claim traces to the author's own inspection of his own repository. Confidence in the reusable engineering pattern — duplicated locale lists plus silent fallbacks manufacture soft 404s, and headings can live in a namespace the availability gate never consults — is reasonably high because the code is shown. Confidence in the counts, the timeline, and any search-impact interpretation is low, and one internal inconsistency remains unresolved.
build
The NestJS default path puts the query inside the business rule, and nothing fails when it moves1 distinct publisher
build
The third answer: a dead-code tool allowed to say "not traced yet"1 distinct publisher
build
A build step instead of a backend: 1,025 records, 8 locales, no runtime API1 distinct publisher
build
Allow-list the closed set, block-list the open one: 193 thin geo pages, one gate1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 23, 2026