<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kaan Demir</title>
    <description>The latest articles on DEV Community by Kaan Demir (@kaahndemir).</description>
    <link>https://dev.to/kaahndemir</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4187517%2F7c1d9ce9-80bf-4926-b391-c307ce3d6273.png</url>
      <title>DEV Community: Kaan Demir</title>
      <link>https://dev.to/kaahndemir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaahndemir"/>
    <language>en</language>
    <item>
      <title>A bilingual site needs a route model, not just a language switch</title>
      <dc:creator>Kaan Demir</dc:creator>
      <pubDate>Thu, 15 Oct 2026 22:21:59 +0000</pubDate>
      <link>https://dev.to/kaahndemir/a-bilingual-site-needs-a-route-model-not-just-a-language-switch-25b8</link>
      <guid>https://dev.to/kaahndemir/a-bilingual-site-needs-a-route-model-not-just-a-language-switch-25b8</guid>
      <description>&lt;p&gt;A language switch changes what a visitor sees. A route model decides what a visitor can share, where navigation leads and which language is present in the HTML before browser code runs.&lt;/p&gt;

&lt;p&gt;For a small bilingual site, these decisions can live in one explicit mapping. The current kaahn.com implementation uses separate Turkish and English addresses and generates readable HTML for each. The useful engineering lesson is the relationship between content identity, language and URL. This is an implementation case study, not evidence of a ranking increase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give each page a stable identity
&lt;/h2&gt;

&lt;p&gt;Consider an About page. Its Turkish address is &lt;code&gt;/hakkimda/&lt;/code&gt;; its English address is &lt;code&gt;/en/about/&lt;/code&gt;. Those are two language versions of the same subject. Switching from the English About page should lead to the Turkish About page, rather than restarting at the Turkish home page.&lt;/p&gt;

&lt;p&gt;The same relationship is useful in navigation, breadcrumbs and a sitemap. Copying paths independently into each template makes it easy to update one place and leave an old address somewhere else. A shared mapping makes the relationship inspectable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;about&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/hakkimda/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/en/about/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;projects&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/projeler/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/en/projects/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Duplicate route: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;about&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/hakkimda/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simplified example validates route shape, language coverage and collisions. It does not validate the translation itself. A publishing check still needs to confirm that the English page actually contains English prose, including its title, description and navigation labels.&lt;/p&gt;

&lt;p&gt;The site’s production build extracts the shared header and footer, selects a page body, applies the corresponding translations and writes an HTML file at each address. Browser JavaScript can add interaction afterwards. The main heading and body do not depend on a language toggle running successfully.&lt;/p&gt;

&lt;p&gt;Google describes crawling, rendering and indexing as distinct stages for JavaScript pages. Preparing stable content at build time makes the published file straightforward to inspect. It does not imply that every client-rendered application is unsuitable for search, or that replacing a framework guarantees better results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model two different relationships
&lt;/h2&gt;

&lt;p&gt;A canonical URL identifies the preferred address for a page. A language alternative identifies another language version. Treating these as the same relationship creates confusing metadata.&lt;/p&gt;

&lt;p&gt;For this site, the English About page has an English self-canonical. Both language versions declare the same Turkish and English alternatives. The Turkish page is the default-language alternative. A shortened example of the English page’s metadata is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://kaahn.com/en/about/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"tr"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://kaahn.com/hakkimda/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://kaahn.com/en/about/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"x-default"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://kaahn.com/hakkimda/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google’s localized-versions guidance calls for reciprocal relationships. Inspecting only the English file is therefore insufficient. The Turkish file must identify both versions too, and every target should resolve to the intended page.&lt;/p&gt;

&lt;p&gt;This also explains why a language switch should use the current page identity. Guessing an English address by adding &lt;code&gt;/en/&lt;/code&gt; would fail for translated slugs such as &lt;code&gt;hakkimda&lt;/code&gt; and &lt;code&gt;about&lt;/code&gt;. The mapping already knows the correct pair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat publication as a contract
&lt;/h2&gt;

&lt;p&gt;A successful generator run proves only that files were produced. The server must serve those files at their public addresses, and the relationships between them must remain correct.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;th&gt;Failure it exposes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inspect the initial HTML&lt;/td&gt;
&lt;td&gt;Heading, body and navigation are present&lt;/td&gt;
&lt;td&gt;Content exists only in browser state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open both language versions&lt;/td&gt;
&lt;td&gt;Each points to both alternatives&lt;/td&gt;
&lt;td&gt;A missing or mismatched language pair&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow internal links&lt;/td&gt;
&lt;td&gt;Final URLs preserve the intended language&lt;/td&gt;
&lt;td&gt;Lost context or unnecessary redirects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compare sitemap and published routes&lt;/td&gt;
&lt;td&gt;Only published canonical addresses appear&lt;/td&gt;
&lt;td&gt;Draft or obsolete addresses exposed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For scheduled content, the sitemap and navigation should use the same publication decision as the page generator. Otherwise a future article can leak into a sitemap before its page exists, or appear as a link that leads to a missing page.&lt;/p&gt;

&lt;p&gt;The design fits a small portfolio with relatively stable content. Concurrent editing, per-user pages and updates that must appear within seconds introduce different requirements. Those requirements may justify a CMS or runtime rendering. Establishing page identity and language first makes that later change easier to reason about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources and original version&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kaahn.com/en/articles/statik-cok-dilli-site/" rel="noopener noreferrer"&gt;Original article on kaahn.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kaahn.com/en/about/" rel="noopener noreferrer"&gt;Published English About page&lt;/a&gt; and &lt;a href="https://kaahn.com/hakkimda/" rel="noopener noreferrer"&gt;Turkish counterpart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/specialty/international/localized-versions" rel="noopener noreferrer"&gt;Google: localized versions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics" rel="noopener noreferrer"&gt;Google: JavaScript SEO basics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI disclosure: This article was drafted by an AI agent from the site’s implementation and the linked documentation. It does not report a search-ranking experiment.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>python</category>
      <category>html</category>
    </item>
  </channel>
</rss>
