<?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 timeout is an unknown outcome: handling live AI acknowledgements</title>
      <dc:creator>Kaan Demir</dc:creator>
      <pubDate>Tue, 27 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/kaahndemir/a-timeout-is-an-unknown-outcome-handling-live-ai-acknowledgements-58cd</link>
      <guid>https://dev.to/kaahndemir/a-timeout-is-an-unknown-outcome-handling-live-ai-acknowledgements-58cd</guid>
      <description>&lt;p&gt;When an external service does not answer, retrying can feel like the obvious recovery step. But a missing acknowledgement does not prove that the service failed to act. If the action generates paid live video, sending it again can produce another effect while the first one is still in progress.&lt;/p&gt;

&lt;p&gt;Inscribi Director’s public bridge makes that uncertainty explicit. An audience vote on Monad testnet determines a direction for a live video. The server records the direction; the stage browser owns the provider connection and reports what happens next. The implementation is useful to study because it separates several events that a single &lt;code&gt;completed&lt;/code&gt; flag could obscure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the facts you can observe
&lt;/h2&gt;

&lt;p&gt;Finalizing a vote establishes which option won. It does not establish that the video provider accepted the new prompt, produced a matching chunk or presented that chunk on the audience’s display.&lt;/p&gt;

&lt;p&gt;The bridge records the round, selected option, prompt and increasing prompt version together. A direction is saved in a &lt;code&gt;waiting&lt;/code&gt; state before it becomes available to the stage. Processing the same round again returns its existing entry. While an earlier direction remains unresolved, the bridge does not create the next one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;th&gt;State recorded&lt;/th&gt;
&lt;th&gt;What it establishes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A round result arrives&lt;/td&gt;
&lt;td&gt;A persisted waiting direction&lt;/td&gt;
&lt;td&gt;The application has a direction to send&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider reports &lt;code&gt;prompt_applied&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Applied&lt;/td&gt;
&lt;td&gt;The provider acknowledged that version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider reports rejection&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;td&gt;That direction is no longer pending&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A matching chunk is reported&lt;/td&gt;
&lt;td&gt;First-chunk timing; applied if previously waiting&lt;/td&gt;
&lt;td&gt;Output for that prompt version was reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No reply arrives&lt;/td&gt;
&lt;td&gt;Still waiting&lt;/td&gt;
&lt;td&gt;The outcome remains uncertain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operator abandons the direction&lt;/td&gt;
&lt;td&gt;Abandoned&lt;/td&gt;
&lt;td&gt;The application may deliberately continue&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keeping work identity separate from delivery attempts is the transferable part. A repeated notification about a round should not quietly become a new command with a new identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve uncertainty instead of hiding it in a retry
&lt;/h2&gt;

&lt;p&gt;The bridge does not automatically resend a direction after a missing acknowledgement. This favors avoiding a duplicate generation command, at the cost of sometimes requiring operator intervention. The operator can abandon a waiting direction so the show can continue.&lt;/p&gt;

&lt;p&gt;Applied, rejected and abandoned entries are terminal for acknowledgement handling. A late response does not rewrite a final decision. A stale prompt-version error is treated as rejection. These rules make the result of delayed messages easier to reason about than a generic “try again” button.&lt;/p&gt;

&lt;p&gt;This is application-level duplicate handling. It is not proof of absolute exactly-once execution across the server, browser and provider. Abandoning an entry also does not prove that a remote effect was cancelled; it records how the local workflow will proceed.&lt;/p&gt;

&lt;p&gt;A matching chunk offers another piece of evidence. The stage reports its prompt version, playback duration and buffer depth. If the corresponding entry is still waiting, that report can settle it as applied. The bridge records when the chunk is expected to start after the existing buffer has played.&lt;/p&gt;

&lt;p&gt;That timestamp is derived from browser reports, not a measurement that a person watched the screen. A later chunk does not move the recorded first-chunk presentation time. Separating provider acceptance from playback timing keeps the user interface from promising a visible result too early.&lt;/p&gt;

&lt;p&gt;The reviewed test source covers duplicate rounds, late replies and presentation timing. Those test definitions are evidence of the intended contract; this article does not claim a newly completed live-event rehearsal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give session recovery its own rules
&lt;/h2&gt;

&lt;p&gt;The session manager addresses a different boundary: who owns the active stage session and when a paid session may be opened. It tracks the stage owner, provider-session claim, attempts, duration and budget. Heartbeats make the disappearance of the stage browser an explicit event.&lt;/p&gt;

&lt;p&gt;On restart, a persisted live session becomes interrupted instead of automatically opening another paid session. Recovering stored state and resuming external activity are separate decisions. A file that says “live” is insufficient evidence that the provider connection still exists or that starting again is financially equivalent.&lt;/p&gt;

&lt;p&gt;The price constants in the repository are implementation assumptions at that revision. They are not current provider pricing, and a locally recorded end is not a final invoice. A new integration still needs to reconcile its lifecycle with the provider’s actual stop behavior and billing.&lt;/p&gt;

&lt;p&gt;For a similar integration, define these four things before implementing retries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A stable identity for work that can be observed more than once.&lt;/li&gt;
&lt;li&gt;The record persisted before the external action becomes available.&lt;/li&gt;
&lt;li&gt;An explicit state for an outcome that cannot yet be established.&lt;/li&gt;
&lt;li&gt;The actor and evidence allowed to resolve that state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then exercise lost replies, late replies, browser loss and process restart as distinct cases. The useful property is that uncertainty has a name in the implementation and an understandable consequence in the operator workflow.&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/canli-ai-yonlendirme-durum-makinesi/" rel="noopener noreferrer"&gt;Original article on kaahn.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kaahndemir/inscribi-director/blob/9ab72e8d47e3e2f3e9ce2369f725c13cc788b50f/src/server/bridge.mjs" rel="noopener noreferrer"&gt;Bridge implementation at the reviewed revision&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kaahndemir/inscribi-director/blob/9ab72e8d47e3e2f3e9ce2369f725c13cc788b50f/src/server/session.mjs" rel="noopener noreferrer"&gt;Session lifecycle at the same revision&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kaahndemir/inscribi-director/blob/9ab72e8d47e3e2f3e9ce2369f725c13cc788b50f/test/unit/bridge.test.mjs" rel="noopener noreferrer"&gt;Bridge test definitions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI disclosure: This article was drafted by an AI agent from the linked source code. It does not report firsthand event observations, current provider prices or a new live-system benchmark.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>architecture</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A CLIP score of 0.85 is not 85% listing accuracy</title>
      <dc:creator>Kaan Demir</dc:creator>
      <pubDate>Tue, 20 Oct 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/kaahndemir/a-clip-score-of-085-is-not-85-listing-accuracy-1joo</link>
      <guid>https://dev.to/kaahndemir/a-clip-score-of-085-is-not-85-listing-accuracy-1joo</guid>
      <description>&lt;p&gt;A number named &lt;code&gt;confidence_score&lt;/code&gt; can look more authoritative than the experiment behind it. In a marketplace filter, a high image-text similarity score does not prove that the seller is honest, that an appliance works or that the photographed item is available.&lt;/p&gt;

&lt;p&gt;Eşya Bul’s public verifier provides a concrete example. It compares a listing image with a small set of descriptions using CLIP, then keeps the listing if the best positive score exceeds &lt;code&gt;0.85&lt;/code&gt;. Reading the code helps separate a useful visual filtering operation from a much broader trust claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identify the actual question
&lt;/h2&gt;

&lt;p&gt;The inspected revision loads &lt;code&gt;openai/clip-vit-base-patch32&lt;/code&gt;. It builds candidate product groups from an existing category tag and title keywords. For a refrigerator, the positive description is a photograph of a refrigerator; alternatives include parts, repair services and a broken fridge.&lt;/p&gt;

&lt;p&gt;For each candidate group, the code sends the image and descriptions to the model, applies softmax to the image logits and takes the score of the first, positive description. If more than one group is considered, it keeps the group with the highest positive score. The retained result includes that group and the score.&lt;/p&gt;

&lt;p&gt;The narrow question is: which of these supplied descriptions best matches this photograph? That can help remove a replacement handle from a search for a complete refrigerator. It cannot establish ownership, working condition, delivery reliability or payment safety. Those require different evidence.&lt;/p&gt;

&lt;p&gt;Category selection matters too. An inaccurate title or tag can make the model compare the image against the wrong alternatives. There is no general-purpose verification step hiding behind the word “verified” in the output filename.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the denominator in view
&lt;/h2&gt;

&lt;p&gt;Softmax normalizes a set of logits into shares that sum to one. The denominator depends on the candidates included in that particular comparison. Add a strong alternative and the positive candidate’s share can fall even when its own logit stays unchanged.&lt;/p&gt;

&lt;p&gt;Here is a self-contained illustration:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;positive_share&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;top&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;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;positive_share&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;# 0.936
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;positive_share&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 0.484
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are invented logits for an arithmetic demonstration, not model outputs or a benchmark. The example shows why a threshold cannot be interpreted without its candidate design. It does not show how accurate CLIP is on refrigerator listings.&lt;/p&gt;

&lt;p&gt;There is another comparison to handle carefully: the verifier selects the highest positive score across product groups with different alternatives. Each score was normalized within its own group. A larger number in one group is not automatically a calibrated probability comparable to every other group.&lt;/p&gt;

&lt;p&gt;OpenAI’s model card discusses sensitivity to category design and the need to evaluate a model in its intended context. Fine-grained distinctions and non-English use also have limitations. In this implementation, Turkish title keywords select groups, while the CLIP descriptions are English. Those two stages deserve separate error analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluate the decision the product actually needs
&lt;/h2&gt;

&lt;p&gt;Start with a label definition small enough for humans to apply consistently. “The image shows a complete refrigerator rather than a replacement part” is much more testable than “this is a safe listing.”&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Why it needs a separate label&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Visual match&lt;/td&gt;
&lt;td&gt;A complete appliance&lt;/td&gt;
&lt;td&gt;Find useful listings rejected by the filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual mismatch&lt;/td&gt;
&lt;td&gt;A handle or repair advertisement&lt;/td&gt;
&lt;td&gt;Find unwanted listings accepted by the filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ambiguous&lt;/td&gt;
&lt;td&gt;A collage containing an appliance and loose parts&lt;/td&gt;
&lt;td&gt;Preserve uncertainty rather than force a clean label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical failure&lt;/td&gt;
&lt;td&gt;An image cannot be downloaded&lt;/td&gt;
&lt;td&gt;Separate network failures from model decisions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The current script skips missing images, failed downloads and rejected classifications before writing its final list. A shorter output list therefore cannot establish better filtering. Add explicit outcome reasons before interpreting the disappearance of an item as model success.&lt;/p&gt;

&lt;p&gt;For an evaluation, keep near-duplicate photographs from the same listing on one side of the development/evaluation split. Record disagreements between human reviewers. Compare false accepts and false rejects at several thresholds, broken down by product group. Freeze the candidate descriptions for each reported run so that another person can reproduce the comparison.&lt;/p&gt;

&lt;p&gt;The appropriate threshold depends on the next action. A shortlist reviewed by a person may tolerate more false accepts to avoid hiding useful items. A decision that triggers an automated action can have a different error cost. No threshold is justified merely because its decimal representation resembles a familiar percentage.&lt;/p&gt;

&lt;p&gt;The defensible claim supported by this code is preliminary filtering for visual category consistency. This article does not report a new benchmark or establish a reliable fraud detector.&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/clip-ilan-dogrulama-sinirlari/" rel="noopener noreferrer"&gt;Original article on kaahn.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kaahndemir/esya-bul/blob/bd191c59baf5ccf78152149f547ae2612b0781ad/scraper/verify_listings.py" rel="noopener noreferrer"&gt;Eşya Bul verifier at the reviewed revision&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/openai/CLIP/blob/d05afc436d78f1c48dc0dbf8e5980a9d471f35f6/model-card.md" rel="noopener noreferrer"&gt;OpenAI CLIP model card at the reviewed revision&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2103.00020" rel="noopener noreferrer"&gt;Learning Transferable Visual Models From Natural Language Supervision&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI disclosure: This article was drafted by an AI agent from the linked code and documentation. The softmax example is illustrative; no new model evaluation was performed for this article.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>ai</category>
      <category>computervision</category>
    </item>
    <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>
