<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged unicode</title>
<link>https://coderlegion.com/tag/unicode</link>
<description>Powered by Question2Answer</description>
<item>
<title>Launching a fancy text converter: 451 styles, and the Unicode edge cases behind them</title>
<link>https://coderlegion.com/32655/launching-a-fancy-text-converter-451-styles-and-the-unicode-edge-cases-behind-them</link>
<description>&lt;p&gt;I'm Shawn Sun, a front-end engineer, and I just put a fancy text converter online. This is a developer community, so here are the parts that were fun to build instead of a feature list.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9770236220749453047&quot; alt=&quot;The converter's home page: one text box, category chips, and a grid of styled previews&quot;&gt;&lt;/p&gt;
&lt;p&gt;You type once and get 451 copy-and-paste styles. Tap one to copy it, and the last 8 you copied come back under Recent. No sign-up. None of these are fonts: every style is real Unicode characters, and that caused most of the work below.&lt;/p&gt;
&lt;h3 id=&quot;1-a-style-is-mostly-a-lookup-table&quot;&gt;1. A style is mostly a lookup table&lt;/h3&gt;
&lt;p&gt;Bold, italic, script, Fraktur and the rest live in the Mathematical Alphanumeric Symbols block, so most styles are &quot;offset of A, plus the letter's index&quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2226421321765363481&quot; alt=&quot;Lookup table: A U+0041 maps to bold A U+1D400, a U+0061 to U+1D41A, with the offset formula&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-the-block-has-holes&quot;&gt;2. The block has holes&lt;/h3&gt;
&lt;p&gt;Some letters were encoded earlier elsewhere, so their slot is empty. A naive &lt;code&gt;base + index&lt;/code&gt; gives you a box, so each style carries a small exceptions table.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14870203655612914539&quot; alt=&quot;Italic &amp;quot;the&amp;quot;: the naive code point U+1D455 is unassigned and shows a box; the fix uses U+210E PLANCK CONSTANT&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;3-accents-decompose-first&quot;&gt;3. Accents: decompose first&lt;/h3&gt;
&lt;p&gt;There is no bold é. I split it with NFD, style the e and keep the accent after it. Whether the accent then sits on top depends on the font.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8443502707233179983&quot; alt=&quot;é decomposes into e plus U+0301; only the e is styled; two fonts render the result differently&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;4-don-t-split-emoji&quot;&gt;4. Don't split emoji&lt;/h3&gt;
&lt;p&gt;Walk a string by code point and 👩💻 falls apart. I cut input into grapheme clusters with &lt;code&gt;Intl.Segmenter&lt;/code&gt; and only style plain letters and digits.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9433061209713490169&quot; alt=&quot;By code point, the woman technologist emoji is three pieces; Intl.Segmenter keeps it as one cluster&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;5-long-input-preview-first-convert-on-copy&quot;&gt;5. Long input: preview first, convert on copy&lt;/h3&gt;
&lt;p&gt;Rendering every style live is fine for a username and slow for a pasted paragraph. Past 200 UTF-16 code units, each card previews only the start, and the full text is converted when you copy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=354969249241440925&quot; alt=&quot;Only the first 200 code units are converted for previews; the tapped style converts the whole text on copy&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;honest-limits&quot;&gt;Honest limits&lt;/h3&gt;
&lt;p&gt;Conversion runs in your browser. I count anonymous usage events, such as which style was copied, but your text isn't part of them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8032254886286468851&quot; alt=&quot;Limits table: missing glyphs, screen readers and search, usernames, digits that stay plain&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;i-d-like-your-feedback&quot;&gt;I'd like your feedback&lt;/h3&gt;
&lt;p&gt;Which category is missing, and do the style names make sense? Try your own text on &lt;a rel=&quot;nofollow&quot; href=&quot;https://fancy-text-generator.app/&quot;&gt;the live site&lt;/a&gt; and tell me in the comments.&lt;/p&gt;
&lt;p&gt;— Shawn Sun&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32655/launching-a-fancy-text-converter-451-styles-and-the-unicode-edge-cases-behind-them</guid>
<pubDate>Tue, 27 Oct 2026 09:22:36 +0000</pubDate>
</item>
<item>
<title>How many characters your language gets in a WordPress URL</title>
<link>https://coderlegion.com/31694/how-many-characters-your-language-gets-in-a-wordpress-url</link>
<description>&lt;p&gt;When &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/insights/wordpress-arabic-slug-truncation&quot;&gt;&lt;em&gt;The 200-byte trap&lt;/em&gt;&lt;/a&gt; went up, a reader on CoderLegion asked the question I should have answered in the article itself: is this only an Arabic problem?&lt;/p&gt;
&lt;p&gt;It isn't. But &quot;it isn't&quot; is not much of an answer, and the one I gave in the comments was arithmetic, not measurement. So I put a typical news headline in fifteen languages through a fresh WordPress 7.1.3 site and looked at what came out the other end. Then I opened the resulting URLs the ways readers actually open them: whole, cut short, and after the slug had changed.&lt;/p&gt;
&lt;p&gt;The short answer: English gets 200 characters. Arabic, Hebrew, Greek and Russian get about 33. Hindi, Thai, Chinese and Japanese get 22. The longer answer has a few surprises in it, and one setting that makes most of it stop mattering.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-200-characters-counted-after-encoding&quot;&gt;The rule: 200 characters, counted after encoding&lt;/h3&gt;
&lt;p&gt;WordPress builds a slug from the title in &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;. Anything outside plain ASCII is stored percent-encoded, the same &lt;code&gt;%XX&lt;/code&gt; form it travels in, and the cut is applied to that encoded form, in one line of current core:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$title = utf8_uri_encode( $title, 200 );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every UTF-8 byte becomes a three-character &lt;code&gt;%XX&lt;/code&gt; token, so what a letter costs depends only on how many bytes its script needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a Latin letter, a digit or a hyphen: 1 byte, costs 1&lt;/li&gt;
&lt;li&gt;Arabic, Persian, Urdu, Hebrew, Greek, Cyrillic: 2 bytes, cost 6&lt;/li&gt;
&lt;li&gt;Hindi, Thai, Chinese, Japanese, Korean: 3 bytes, cost 9&lt;/li&gt;
&lt;li&gt;an emoji: usually 4 bytes, costing 12, and some cost far more&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Divide 200 by the cost and you have the ceiling. The cut never splits a character in half, because &lt;code&gt;utf8_uri_encode()&lt;/code&gt; stops before one that would not fit. It does split words: nothing in it knows where a word ends. Category and tag slugs go through the same function, with the same 200.&lt;/p&gt;
&lt;p&gt;The emoji line hides two surprises. ❤️ is two code points at 9 each, and the second is an invisible one that stays in the URL. A family emoji is three people held together by invisible joiners, and WordPress counts the joiners, then strips them, so the URL ends up with three separate people.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.mantek.io/insights/wordpress-url-length-by-language-diagram-1.png&quot; alt=&quot;The same 200 characters, divided by script&quot;&gt;&lt;br&gt;
&lt;em&gt;Every slug gets the same 200 characters. A Latin letter spends one of them, an Arabic, Hebrew, Greek or Russian letter six, a Hindi, Thai, Chinese, Japanese or Korean character nine, and an emoji twelve. What is left over is too small for one more.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-numbers-measured&quot;&gt;The numbers, measured&lt;/h3&gt;
&lt;p&gt;Here is what WordPress 7.1.3 kept of a news headline in each language. &quot;Cost per letter&quot; is counted after accents are removed, so é costs what e does. &quot;Characters kept&quot; includes the hyphens that replaced the spaces, and the last word counted is usually a fragment.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Language &lt;/th&gt;&lt;th&gt;Cost per letter &lt;/th&gt;&lt;th&gt;Characters kept &lt;/th&gt;&lt;th&gt;Of which letters &lt;/th&gt;&lt;th&gt;Words&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;English &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;199 &lt;/td&gt;&lt;td&gt;170 &lt;/td&gt;&lt;td&gt;30&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;French &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;190 &lt;/td&gt;&lt;td&gt;161 &lt;/td&gt;&lt;td&gt;30&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;German &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;198 &lt;/td&gt;&lt;td&gt;174 &lt;/td&gt;&lt;td&gt;25&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Vietnamese &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;157, the whole headline &lt;/td&gt;&lt;td&gt;123 &lt;/td&gt;&lt;td&gt;35&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Arabic &lt;/td&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;38 &lt;/td&gt;&lt;td&gt;32 &lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Persian &lt;/td&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;36 &lt;/td&gt;&lt;td&gt;30 &lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Urdu &lt;/td&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;40 &lt;/td&gt;&lt;td&gt;32 &lt;/td&gt;&lt;td&gt;9&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Hebrew &lt;/td&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;38 &lt;/td&gt;&lt;td&gt;32 &lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Greek &lt;/td&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;37 &lt;/td&gt;&lt;td&gt;32 &lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Russian &lt;/td&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;36 &lt;/td&gt;&lt;td&gt;32 &lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Hindi &lt;/td&gt;&lt;td&gt;9 &lt;/td&gt;&lt;td&gt;26 &lt;/td&gt;&lt;td&gt;21 &lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Korean &lt;/td&gt;&lt;td&gt;9 &lt;/td&gt;&lt;td&gt;27 &lt;/td&gt;&lt;td&gt;21 &lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Thai &lt;/td&gt;&lt;td&gt;9 &lt;/td&gt;&lt;td&gt;22 &lt;/td&gt;&lt;td&gt;22 &lt;/td&gt;&lt;td&gt;1, no spaces&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Chinese &lt;/td&gt;&lt;td&gt;9 &lt;/td&gt;&lt;td&gt;22 &lt;/td&gt;&lt;td&gt;22 &lt;/td&gt;&lt;td&gt;1, no spaces&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Japanese &lt;/td&gt;&lt;td&gt;9 &lt;/td&gt;&lt;td&gt;22 &lt;/td&gt;&lt;td&gt;22 &lt;/td&gt;&lt;td&gt;1, no spaces&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Three things in that table are worth a second look.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The two-byte scripts stopped at 32 letters, not 33, and Persian at 30.&lt;/strong&gt; Hyphens come out of the same 200, so a headline with six spaces keeps 32 letters and six hyphens, and Persian's missing two are explained further down. In practice that is five to nine words, depending on how long the words are, and the last is usually cut: the Arabic slug ends in «ال», the definite article of a word that did not fit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Twenty-two characters is fewer letters than it sounds in Hindi and Thai.&lt;/strong&gt; The count is of code points, and their vowel signs and tone marks are code points of their own. The Thai slug ends in «ใหม», which is «ใหม่» without its tone mark.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thai, Chinese and Japanese get no hyphens at all.&lt;/strong&gt; They don't put spaces between words, so the whole slug is a single run of 22 characters.&lt;/p&gt;
&lt;p&gt;One more cost doesn't show in the table. When a slug is already taken, WordPress cuts it again to fit a suffix, so a second and a third post with the same Urdu headline each lost a letter to make room for &lt;code&gt;-2&lt;/code&gt; and &lt;code&gt;-3&lt;/code&gt;. And with only five to nine words to tell headlines apart, non-Latin slugs are likelier to collide in the first place, for the same reason cut-off links go astray below.&lt;/p&gt;
&lt;h3 id=&quot;accented-latin-gets-the-full-200-and-the-site-language-decides-how&quot;&gt;Accented Latin gets the full 200, and the site language decides how&lt;/h3&gt;
&lt;p&gt;French, German and Vietnamese kept as much as English because their accents never reach the encoder. When a slug is saved, &lt;code&gt;sanitize_title()&lt;/code&gt; runs &lt;code&gt;remove_accents()&lt;/code&gt; first: é becomes e, and the whole Vietnamese headline came through as plain ASCII.&lt;/p&gt;
&lt;p&gt;How a letter is transliterated depends on the site language. On a site set to English, «Maßnahmen» became &lt;code&gt;masnahmen&lt;/code&gt;, and «Größe» would become &lt;code&gt;grose&lt;/code&gt;, which is a different word. With the site set to German, the same function produces &lt;code&gt;massnahmen&lt;/code&gt; and &lt;code&gt;groesse&lt;/code&gt;. Danish (&lt;code&gt;soester&lt;/code&gt;, &lt;code&gt;paa&lt;/code&gt;), Catalan (&lt;code&gt;collegi&lt;/code&gt;) and Serbian (&lt;code&gt;djordje&lt;/code&gt;) get the same care from their own locales. Slugs are generated when a post is saved, so set the site language before the archive grows, not after.&lt;/p&gt;
&lt;h3 id=&quot;what-spends-the-budget-without-showing-up&quot;&gt;What spends the budget without showing up&lt;/h3&gt;
&lt;p&gt;The cut happens before WordPress tidies the slug, so some characters are counted first and only then removed or replaced. Arabic and Persian headlines are full of them, and English ones are not free of them either.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Angle quotes.&lt;/strong&gt; «» cost 6 each and are stripped once counted. A headline that opened with «عاجل» and quoted one more word kept one letter of «الشركات», where the same headline without the quotes kept five, «الشرك».&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invisible marks.&lt;/strong&gt; A right-to-left mark costs 9, and so does the Persian zero-width non-joiner. Both are stripped. The Persian headline shows a second cost too: «کسبوکار» came out as «کسبوکا», glued together and then cut.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Curly punctuation, in any language.&lt;/strong&gt; A curly apostrophe, as in &lt;em&gt;what’s&lt;/em&gt;, costs 9 and is stripped, and so are curly quotes and the ellipsis. En and em dashes cost 9 and become hyphens. Headlines pasted from a word processor tend to be full of them: a long English headline with four apostrophes, a pair of curly quotes and a dash lost 62 characters of its slug to them, and stopped at 133.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-spends-the-budget-and-stays-in-the-url&quot;&gt;What spends the budget and stays in the URL&lt;/h3&gt;
&lt;p&gt;Worse are the characters that are counted and then kept. WordPress's list of characters to strip was written for Latin text, so marks from other scripts reach the URL at full price.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Arabic-Indic digits.&lt;/strong&gt; ٥٠٠٠ costs 24 where 5000 costs 4, and the cut can fall inside a number. A headline about a 5,000-dinar grant came out ending in «بقيمة-٥٠٠», worth 500: a figure that looks right and isn't. Typed with Western digits, the same headline kept the whole number.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diacritics.&lt;/strong&gt; Each tashkeel mark costs 6. A fully vocalised headline fitted three and a half words, «الحُكُومَةُ-تُعْلِنُ-خُطَّةً-جَدِيد».&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Punctuation from other scripts is kept too, at 6 or 9 a mark. This is core's own output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Arabic comma and question mark
عاجل،-الحكومة-تعلن-خطة؟-جديدة

Arabic semicolon
أولاً؛-ثانياً

Urdu full stop
حکومت-کا-اعلان۔-نیا-منصوبہ

Japanese punctuation
「速報」政府が新計画を発表！

Chinese punctuation
政府宣布，新计划。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The worst are invisible. WordPress strips the old bidirectional embedding marks once it has counted them, but it keeps the newer isolates, U+2066 to U+2069, and the Arabic letter mark. The isolates cost 9 each and the mark 6, and they stay in the URL without ever showing in the headline.&lt;/p&gt;
&lt;h3 id=&quot;what-happens-when-someone-opens-a-cut-off-link&quot;&gt;What happens when someone opens a cut-off link&lt;/h3&gt;
&lt;p&gt;Most sites use slug-only permalinks, &lt;code&gt;/%postname%/&lt;/code&gt;. I opened the Arabic test post four ways:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Request &lt;/th&gt;&lt;th&gt;Result&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;the stored slug &lt;/td&gt;&lt;td&gt;200, the article&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;cut after four words, an opening two headlines share &lt;/td&gt;&lt;td&gt;301 to one of the two, picked by the database&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;cut where only one headline matches &lt;/td&gt;&lt;td&gt;301 to the right article&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;longer than the stored slug &lt;/td&gt;&lt;td&gt;404&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The middle rows come from &lt;code&gt;redirect_guess_404_permalink()&lt;/code&gt;. When a slug isn't found, WordPress looks for one that &lt;em&gt;starts&lt;/em&gt; with what was requested, &lt;code&gt;post_name LIKE 'cut%'&lt;/code&gt;, and redirects to the first match. The query has no &lt;code&gt;ORDER BY&lt;/code&gt;, so when two headlines share their opening words, which one the reader gets is up to the database. On my test site the older article won, but don't count on it: another database, or the same one reading through a different index, can return the other. News headlines share openings all the time («القبض على المتهمين في» …), and the redirect is a 301, which tells browsers and search engines the move is permanent.&lt;/p&gt;
&lt;p&gt;If you would rather WordPress matched exactly than guessed, there is a filter for that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;add_filter( 'strict_redirect_guess_404_permalink', '__return_true' );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With it, a cut-off link gets a 404 even when the guess would have been right. Exact matches are still redirected, and that matters further down: after a switch to ID-anchored permalinks, old slug-only links are found by this same guess, and strict matching keeps them working. The filter that turns guessing off altogether, &lt;code&gt;do_redirect_guess_404_permalink&lt;/code&gt;, would send every one of them to a 404.&lt;/p&gt;
&lt;p&gt;The last row is the 200-byte trap in miniature. A link longer than the stored slug never matches, because the stored slug doesn't start with it. That is what readers meet when a core update shrinks a widened column under links that are already out in the world.&lt;/p&gt;
&lt;h3 id=&quot;put-the-id-in-the-url&quot;&gt;Put the ID in the URL&lt;/h3&gt;
&lt;p&gt;The setting that makes most of this stop mattering is under Settings → Permalinks: a custom structure of &lt;code&gt;/%post_id%/%postname%/&lt;/code&gt;. The words stay in the URL for readers, and the number decides which article it is. The same requests, plus a few harsher ones:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Request &lt;/th&gt;&lt;th&gt;Result&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;the ID and the stored slug &lt;/td&gt;&lt;td&gt;200&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;the ID and a longer slug &lt;/td&gt;&lt;td&gt;301 to the article&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;the ID and the shared four-word cut &lt;/td&gt;&lt;td&gt;301 to the right article&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;the ID and unrelated words &lt;/td&gt;&lt;td&gt;301 to the article&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;the ID alone &lt;/td&gt;&lt;td&gt;200, with a canonical tag naming the full URL&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;an old slug-only link, after switching &lt;/td&gt;&lt;td&gt;301 to the article&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;the right slug with another post's ID &lt;/td&gt;&lt;td&gt;301 to that other post&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The reason is a few lines in &lt;code&gt;redirect_canonical()&lt;/code&gt;. When the slug doesn't match, the request is a 404 for a moment, but the post ID is still in it, and WordPress sends the reader to that post's real permalink:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;if ( is_404() ) {
// Redirect ?page_id, ?p=, ?attachment_id= to their respective URLs.
$post_id = max( get_query_var( 'p' ), get_query_var( 'page_id' ), get_query_var( 'attachment_id' ) );

$redirect_post = $post_id ? get_post( $post_id ) : false;
// … and for a public post: $redirect_url = get_permalink( $redirect_post );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last row in the table is the price: the number outranks the words, so a mistyped ID lands on a different article. Links are copied far more often than they are typed, and for a large archive that is a trade I would take.&lt;/p&gt;
&lt;p&gt;Switching an existing site went smoothly in the test: old slug-only links were redirected to their articles by the same guess as above, with or without strict matching. So after switching, leave guessing on, and watch the 404 log for a while.&lt;/p&gt;
&lt;h3 id=&quot;give-the-slug-its-budget-back&quot;&gt;Give the slug its budget back&lt;/h3&gt;
&lt;p&gt;A small must-use plugin recovers the space those characters waste, and keeps Arabic-script punctuation out of the URL. It runs before WordPress counts, and only when a slug is being saved, so existing URLs keep matching exactly as before:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * Give slugs their budget back: drop or replace characters that cost slug
 * space, before WordPress counts them. Mostly Arabic, Persian and Urdu ones,
 * plus the curly quotes, dashes and ellipses that English headlines pick up.
 */
add_filter( 'sanitize_title', function ( $title, $raw_title = '', $context = 'display' ) {
if ( 'save' !== $context ) {
return $title; // Leave lookups alone, so existing URLs keep matching.
}
// Arabic diacritics, superscript alef and tatweel: 6 each, and they clutter the URL.
$title = preg_replace( '/[\x{064B}-\x{065F}\x{0670}\x{0640}]/u', '', $title );
// Quotes, the ellipsis and invisible marks, which WordPress strips anyway,
// but only after counting them.
$title = preg_replace( '/[\x{00AB}\x{00BB}\x{2039}\x{203A}\x{2018}-\x{201F}\x{2026}\x{200B}-\x{200F}\x{FEFF}]/u', '', $title );
// Direction controls: WordPress strips the embeddings only after counting
// them, and keeps the isolates and the Arabic letter mark in the URL.
$title = preg_replace( '/[\x{061C}\x{202A}-\x{202E}\x{2066}-\x{2069}]/u', '', $title );
// Punctuation that WordPress keeps in the URL (Arabic comma, semicolon and
// question mark, Urdu full stop) or turns into a hyphen only after counting
// it (no-break space, en and em dash): make it a space, which costs 1.
$title = preg_replace( '/[\x{060C}\x{061B}\x{061F}\x{06D4}\x{00A0}\x{2013}\x{2014}]/u', ' ', $title );
// Arabic-Indic and Persian digits cost 6 each; 0 to 9 cost 1.
$title = strtr( $title, array_combine(
preg_split( '//u', '٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹', -1, PREG_SPLIT_NO_EMPTY ),
str_split( '01234567890123456789' )
) );
return $title;
}, 9, 3 );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the test site, the quoted headline's slug came out identical to the unquoted one. A long headline with an Arabic comma and question mark got plain hyphens in their place, and kept two more letters. A headline carrying invisible isolates lost them and kept three more letters, and the Arabic letter marks were dropped too. The grant headline came out with its whole number, &lt;code&gt;5000&lt;/code&gt;, and the vocalised headline went from three and a half words to six, with no diacritics in the URL. English titles come out as core makes them, only no longer cut short: the long English headline went from 133 characters to 195. The plugin leaves Chinese and Japanese punctuation alone, so there it still costs 9 and stays in the URL.&lt;/p&gt;
&lt;h3 id=&quot;so-is-it-only-arabic-&quot;&gt;So, is it only Arabic?&lt;/h3&gt;
&lt;p&gt;No. Every language outside the Latin alphabet pays for the same 200 characters at a higher price: six per letter for the two-byte scripts, nine for the three-byte ones. English pays too, every time a curly apostrophe or a dash is pasted in. Arabic newsrooms are where I met it first, because that is mainly where I work, not because the rule treats Arabic differently.&lt;/p&gt;
&lt;p&gt;What to do about it, roughly in order of effort:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If your language uses accented Latin, set the site language before you publish.&lt;/li&gt;
&lt;li&gt;Edit long slugs by hand. Five to seven words is plenty.&lt;/li&gt;
&lt;li&gt;On a Russian or other Cyrillic site, consider a transliteration plugin such as Cyr-To-Lat, which writes slugs in Latin letters, so they cost what English costs. Arabic rarely goes that way, because Arabic written in Latin letters is hard to read back.&lt;/li&gt;
&lt;li&gt;On an Arabic, Persian or Urdu site, or any site whose headlines arrive with curly quotes and dashes, add the plugin above.&lt;/li&gt;
&lt;li&gt;On a large archive, anchor permalinks on the post ID.&lt;/li&gt;
&lt;li&gt;If you need longer slugs than any of this allows, widen the column, raise the budget, and stop core updates from shrinking it back. That is what &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mantekio/wp-arabic-slug-schema-guard&quot;&gt;wp-arabic-slug-schema-guard&lt;/a&gt; does.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Every number here was measured on a stock WordPress 7.1.3 site in WordPress Playground, which runs on SQLite. The slug lengths are worked out in PHP and will be the same on MySQL. Which article a shared opening lands on may not be, which is rather the point.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31694/how-many-characters-your-language-gets-in-a-wordpress-url</guid>
<pubDate>Mon, 19 Oct 2026 08:06:20 +0000</pubDate>
</item>
<item>
<title>The 200-byte trap: why WordPress core updates break Arabic URLs</title>
<link>https://coderlegion.com/28135/the-200-byte-trap-why-wordpress-core-updates-break-arabic-urls</link>
<description>&lt;p&gt;You update WordPress on a quiet afternoon, a routine release, the kind you've installed a hundred times. The dashboard says everything went fine. Then the 404s start: not a handful, but every long-headlined article in your archive, all at once, all in Arabic.&lt;/p&gt;
&lt;p&gt;Nothing in the update log mentions it. No plugin changed. And the cruel part: the data that made those URLs work is already gone, shaved off inside a database-upgrade routine that ran for a few milliseconds and reported success. This isn't a freak accident or a broken plugin. It's three separate assumptions baked into WordPress core, each hard-coding the same number, &lt;strong&gt;200&lt;/strong&gt;, and Arabic sites are almost uniquely exposed to all three. We hit this running WordPress for Arabic newsrooms, the same high-traffic publishing we've written about &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/insights/breaking-news-wordpress-aws-architecture&quot;&gt;surviving breaking-news spikes&lt;/a&gt;. We traced it to the exact lines in core and built a fix that survives every future update. Here's the whole story.&lt;/p&gt;
&lt;h3 id=&quot;why-arabic-urls-hit-a-wall-english-never-does&quot;&gt;Why Arabic URLs hit a wall English never does&lt;/h3&gt;
&lt;p&gt;WordPress stores a post's slug in the &lt;code&gt;post_name&lt;/code&gt; column of &lt;code&gt;wp_posts&lt;/code&gt;, and a category or tag slug in the &lt;code&gt;slug&lt;/code&gt; column of &lt;code&gt;wp_terms&lt;/code&gt;. Both are &lt;code&gt;VARCHAR(200)&lt;/code&gt; by default: room for 200 characters, which for an English headline is generous. &lt;em&gt;&quot;Everything you need to know about our new pricing&quot;&lt;/em&gt; is barely fifty. You'd have to write a paragraph to run out.&lt;/p&gt;
&lt;p&gt;Arabic is a different arithmetic, because of what WordPress actually stores in that column. It doesn't keep the raw Arabic text; it stores the &lt;strong&gt;percent-encoded&lt;/strong&gt; form, the same &lt;code&gt;%XX&lt;/code&gt; sequence that travels in the URL. Take a single word:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;الذكاء  →  %d8%a7%d9%84%d8%b0%d9%83%d8%a7%d8%a1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every Arabic letter is two bytes in UTF-8, and every byte becomes a three-character &lt;code&gt;%XX&lt;/code&gt; token. So &lt;strong&gt;one Arabic character costs about six characters of column space.&lt;/strong&gt; Do the division: &lt;code&gt;VARCHAR(200)&lt;/code&gt; holds roughly &lt;strong&gt;33 Arabic characters.&lt;/strong&gt; A normal news headline, &lt;em&gt;&quot;القبض على المتهمين في قضية الاحتيال الإلكتروني&quot;&lt;/em&gt;, blows past that before it's halfway done.&lt;/p&gt;
&lt;p&gt;So Arabic publishers learn early to widen the columns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ALTER TABLE wp_posts MODIFY post_name VARCHAR(1024) NOT NULL DEFAULT '';
ALTER TABLE wp_terms MODIFY slug      VARCHAR(1024) NOT NULL DEFAULT '';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;VARCHAR(1024)&lt;/code&gt; holds about 170 Arabic characters, comfortable. Permalinks resolve, the newsroom publishes, and the problem looks solved.&lt;/p&gt;
&lt;p&gt;It isn't. It's about &lt;strong&gt;one-third&lt;/strong&gt; solved, and the other two-thirds are waiting for the next core update. (This isn't only an Arabic problem, either: any non-Latin script that percent-encodes (Persian, Urdu, Hebrew, Greek) hits the same wall. Arabic publishers just hit it first; scripts that take three bytes a character, such as Hindi, Thai or Chinese, hit it harder still, at about 22 characters to Arabic's 33.)&lt;/p&gt;
&lt;h3 id=&quot;the-number-200-lives-in-three-places-not-one&quot;&gt;The number 200 lives in three places, not one&lt;/h3&gt;
&lt;p&gt;Here's the insight that turns this from a recurring mystery into something you can actually close out. WordPress hard-codes &quot;200&quot; in &lt;strong&gt;three independent places&lt;/strong&gt;, and widening the column only touches the first:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;What it caps at 200 &lt;/th&gt;&lt;th&gt;Does widening the column fix it?&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;1, Storage&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;the column that physically stores the slug &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;, but a core update silently undoes it&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;2, Generation&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;every new slug, the moment it is created &lt;/td&gt;&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;, it's PHP; needs a code override&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;3, De-duplication&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;slugs that collide and need a numeric suffix &lt;/td&gt;&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;, but this only fires on collisions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Your &lt;code&gt;ALTER&lt;/code&gt; fixes Layer 1's &lt;em&gt;storage&lt;/em&gt;. But Layer 2 means that even with a 1024-wide column, every new slug is still cut to 200 bytes &lt;em&gt;before it's ever saved&lt;/em&gt;, unless something on your site already overrode it. (If long Arabic slugs save correctly for you today, a plugin, your theme, or a past developer already patched Layer 2, which means you have an undocumented dependency you didn't know you were relying on.) And Layer 1, the storage you so carefully widened, is exactly the one a core update quietly reverts. Let's take each in turn.&lt;/p&gt;
&lt;h3 id=&quot;layer-1-how-a-core-update-erases-your-column-width&quot;&gt;Layer 1: how a core update erases your column width&lt;/h3&gt;
&lt;p&gt;WordPress keeps a canonical description of its own database schema, and on every major update it reconciles the live database against that description. The chain is short and deterministic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A new release bumps &lt;code&gt;$wp_db_version&lt;/code&gt; in &lt;code&gt;version.php&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On the next admin request, &lt;code&gt;wp_upgrade()&lt;/code&gt; notices the bump and runs the database upgrade.&lt;/li&gt;
&lt;li&gt;That calls &lt;code&gt;make_db_current_silent()&lt;/code&gt;, which calls &lt;code&gt;dbDelta( wp_get_db_schema() )&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wp_get_db_schema()&lt;/code&gt; is the canonical schema, and it says, verbatim:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;post_name varchar(200) NOT NULL default '',
...
slug varchar(200) NOT NULL default '',
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dbDelta()&lt;/code&gt; then runs a &lt;code&gt;DESCRIBE&lt;/code&gt; on every live table, compares each column to the canonical definition, and for any mismatch emits an &lt;code&gt;ALTER TABLE … CHANGE COLUMN&lt;/code&gt; to &quot;correct&quot; it. Your &lt;code&gt;post_name&lt;/code&gt; is &lt;code&gt;VARCHAR(1024)&lt;/code&gt;; the canonical says &lt;code&gt;VARCHAR(200)&lt;/code&gt;; they don't match, so dbDelta dutifully issues a &lt;code&gt;CHANGE COLUMN&lt;/code&gt; back to 200, MySQL rebuilds the table, and every byte past the 200th is discarded.&lt;/p&gt;
&lt;p&gt;Here's the detail that makes it unrecoverable. Years ago, core added &lt;em&gt;downsize protection&lt;/em&gt; to dbDelta, but &lt;strong&gt;only for &lt;code&gt;TEXT&lt;/code&gt; and &lt;code&gt;BLOB&lt;/code&gt; columns&lt;/strong&gt; (it was &lt;a rel=&quot;nofollow&quot; href=&quot;https://core.trac.wordpress.org/ticket/36748&quot;&gt;Trac #36748&lt;/a&gt;, born from exactly this kind of data loss). &lt;code&gt;VARCHAR&lt;/code&gt; got no such guard. So a &lt;code&gt;TEXT&lt;/code&gt; column is safe from accidental shrinking; your &lt;code&gt;VARCHAR(1024)&lt;/code&gt; slug column is not. dbDelta shrinks it without hesitation, and restoring the column to 1024 afterwards brings back the width but &lt;strong&gt;not the data&lt;/strong&gt;; those bytes were handed to MySQL and dropped.&lt;/p&gt;
&lt;p&gt;That's why your fix keeps coming undone: always right after an update, always silently. It isn't a bug aimed at you; it's core doing exactly what it was designed to do, to a column you were never expected to change.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;For accuracy: a separate routine, &lt;code&gt;pre_schema_upgrade()&lt;/code&gt;, also runs hand-written&lt;br&gt;
&lt;code&gt;ALTER&lt;/code&gt;s, but those are version-gated to historic migrations and won't touch a&lt;br&gt;
modern site's &lt;code&gt;post_name&lt;/code&gt;. The reversion you're seeing is dbDelta.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-fix-don-t-repair-the-damage-prevent-the-shrink&quot;&gt;The fix: don't repair the damage, prevent the shrink&lt;/h3&gt;
&lt;p&gt;The instinct is to write something that detects the reverted column and re-widens it after each update. Resist it. By the time you can detect the shrink, the data is already gone; you'd just be putting an empty wider column back over truncated slugs. The only fix that actually &lt;em&gt;saves the data&lt;/em&gt; is one that stops the shrink from ever happening.&lt;/p&gt;
&lt;p&gt;And core hands you the seam to do it. dbDelta exposes a filter, &lt;code&gt;dbdelta_create_queries&lt;/code&gt;, that receives the canonical &lt;code&gt;CREATE TABLE&lt;/code&gt; statements &lt;em&gt;before&lt;/em&gt; they're compared to the live database. Rewrite the canonical &lt;code&gt;post_name varchar(200)&lt;/code&gt; to &lt;code&gt;varchar(1024)&lt;/code&gt; in that filter, and dbDelta's idea of &quot;correct&quot; now matches what's actually on disk. It sees no mismatch. It emits no &lt;code&gt;CHANGE COLUMN&lt;/code&gt;. There is nothing to truncate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * Plugin Name: Arabic Slug Schema Guard (must-use)
 * Description: Keeps wp_posts.post_name and wp_terms.slug at VARCHAR(1024) across
 *              core DB upgrades, so long Arabic slugs are never truncated.
 *              Prevention-first: dbDelta never even tries to shrink them.
 */
defined( 'ABSPATH' ) || exit;

const SLUG_COLUMN_LEN = 1024;

// Rewrite the canonical schema dbDelta diffs against, so &quot;desired&quot; already
// equals the live 1024-wide column, so no CHANGE COLUMN is ever emitted.
add_filter( 'dbdelta_create_queries', function ( $queries ) {
    global $wpdb;

    foreach ( array( $wpdb-&amp;gt;posts =&amp;gt; 'post_name', $wpdb-&amp;gt;terms =&amp;gt; 'slug' ) as $table =&amp;gt; $column ) {
        if ( isset( $queries[ $table ] ) ) {
            $queries[ $table ] = preg_replace(
                '/(\b' . $column . '\s+varchar\()\s*200\s*(\))/i',
                '${1}' . SLUG_COLUMN_LEN . '${2}',
                $queries[ $table ]
            );
        }
    }
    return $queries;
} );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That single filter is the whole prevention story for Layer 1, and it's robust in a way an after-the-fact repair can never be: it runs &lt;em&gt;inside&lt;/em&gt; dbDelta, so it covers &lt;strong&gt;every&lt;/strong&gt; path that triggers a schema reconcile: the admin &quot;database update required&quot; screen, background auto-updates, and &lt;code&gt;wp core update-db&lt;/code&gt; on the CLI alike.&lt;/p&gt;
&lt;p&gt;Where this code lives matters as much as what it does. Put it in a &lt;strong&gt;must-use plugin&lt;/strong&gt; (a single &lt;code&gt;.php&lt;/code&gt; file in &lt;code&gt;wp-content/mu-plugins/&lt;/code&gt;), not a regular plugin. MU-plugins always load, can't be deactivated from the dashboard, and load &lt;em&gt;before&lt;/em&gt; the upgrade routine runs. And because a core update only ever replaces &lt;code&gt;wp-admin&lt;/code&gt;, &lt;code&gt;wp-includes&lt;/code&gt; and the root PHP files (it never touches &lt;code&gt;wp-content&lt;/code&gt;), your guard is guaranteed to still be in place at the exact moment the upgrade fires. A regular plugin could be deactivated the one time it mattered; don't bet your archive on a toggle.&lt;/p&gt;
&lt;h3 id=&quot;layer-2-stop-new-slugs-truncating-at-birth&quot;&gt;Layer 2: stop new slugs truncating at birth&lt;/h3&gt;
&lt;p&gt;Prevention on the column keeps your &lt;em&gt;existing&lt;/em&gt; slugs safe. But Layer 2 is still cutting every &lt;em&gt;new&lt;/em&gt; slug to 200 bytes at the moment of creation, inside &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;. The relevant line, unchanged in current core, is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$title = utf8_uri_encode( $title, 200 );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That function is attached to the &lt;code&gt;sanitize_title&lt;/code&gt; filter, so you can cleanly swap it for an identical copy that uses a larger byte budget:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// Layer 2: replace core's slug generator with a byte-for-byte copy whose only
// change is the encoding cap. The copy self-tests against core (see below), so a
// future core rewrite of this function is caught instead of drifting silently.
remove_filter( 'sanitize_title', 'sanitize_title_with_dashes' );
add_filter( 'sanitize_title', 'asg_sanitize_title_with_dashes', 10, 3 );

function asg_sanitize_title_with_dashes( $title, $raw_title = '', $context = 'display' ) {
    // … the body is line-for-line core, with one change:
    //     utf8_uri_encode( $title, 200 )  →  utf8_uri_encode( $title, 1000 )
    // 1000 leaves headroom under the 1024 column for a &quot;-2&quot; collision suffix.
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pick a sane budget: around 1000 bytes (≈ 160 Arabic characters) leaves room under the column for the numeric suffix WordPress adds to resolve duplicates. You don't want &lt;em&gt;unlimited&lt;/em&gt; slugs; you want &lt;em&gt;enough.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Layer 3 (&lt;code&gt;_truncate_post_slug()&lt;/code&gt;) caps at 200 too, but it only runs when a slug collides and needs a &lt;code&gt;-2&lt;/code&gt; suffix. For unique news headlines that almost never fires, and it isn't filterable, so most sites can leave it alone. If you genuinely need more than 200 bytes &lt;em&gt;even on collisions&lt;/em&gt;, you take over uniqueness through the &lt;code&gt;pre_wp_unique_post_slug&lt;/code&gt; filter, an advanced case worth a footnote, not a paragraph.&lt;/p&gt;
&lt;h4 id=&quot;keeping-the-fork-honest&quot;&gt;Keeping the fork honest&lt;/h4&gt;
&lt;p&gt;Copying a core function buys a maintenance debt: the day WordPress rewrites &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;, your copy diverges, &lt;em&gt;silently&lt;/em&gt;, which is the exact failure mode we're trying to kill. Pinning the copy to a core version and re-diffing by hand works right up until the release someone forgets to check, which on a long-lived site is &lt;em&gt;when&lt;/em&gt;, not &lt;em&gt;if&lt;/em&gt;. So don't rely on remembering. Make the plugin re-diff itself.&lt;/p&gt;
&lt;p&gt;The lever: &lt;code&gt;remove_filter()&lt;/code&gt; only unhooks the function; &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt; stays &lt;strong&gt;callable&lt;/strong&gt;. So you can use core's own current code as a live oracle and assert your fork still agrees with it on &lt;strong&gt;short&lt;/strong&gt; inputs, where neither byte cap fires and the only thing left that could differ is the cleanup logic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// Run once per core version. Short fixtures =&amp;gt; neither length cap engages,
// so any difference is pure logic drift, not the byte budget.
foreach ( array( 'الذكاء الاصطناعي', 'عاجل: «مهم» اليوم', 'foo/bar — baz' ) as $f ) {
    if ( sanitize_title_with_dashes( $f, $f, 'save' )
         !== asg_sanitize_title_with_dashes( $f, $f, 'save' ) ) {
        // core changed the cleanup logic: log + email ops, and (optionally) fall
        // back to core's generator so slugs cap at 200 again: degraded, but safe
        // and known, never a malformed slug from a stale copy.
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This beats a source-hash check on two counts. It's &lt;strong&gt;semantic, not textual&lt;/strong&gt;: it stays quiet when core only touches the length handling (which you override anyway) and fires only when a change actually breaks &lt;em&gt;your&lt;/em&gt; output, so the alerts that do fire get acted on. And it &lt;strong&gt;degrades safely&lt;/strong&gt;: on drift you fall back to core's exact behaviour: worst case, new slugs cap at 200 until you re-sync, never a half-encoded slug from a stale copy. Loud-and-safe beats silent-and-wrong. The full guard (with the per-core-version cache and the optional fail-safe switch) is in the complete plugin below.&lt;/p&gt;
&lt;h3 id=&quot;a-tripwire-because-silent-failure-is-the-real-enemy&quot;&gt;A tripwire, because silent failure is the real enemy&lt;/h3&gt;
&lt;p&gt;The filter prevents the known incident. But the failure mode that actually hurt you was that it happened &lt;em&gt;silently&lt;/em&gt;, so the last piece is making sure you'd never again learn about it from a drop in search traffic. The same MU-plugin can verify the column widths after every update and shout if anything is wrong:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hook &lt;code&gt;upgrader_process_complete&lt;/code&gt; to flag that a core update just ran.&lt;/li&gt;
&lt;li&gt;On the next admin request, check the real column widths in &lt;code&gt;information_schema&lt;/code&gt; and, if either has shrunk, write to the error log and email ops.&lt;/li&gt;
&lt;li&gt;Expose a WP-CLI command for cron-based monitoring:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Nightly: verify the slug columns, email ops if either has reverted.
0 3 * * *  cd /var/www/site &amp;amp;&amp;amp; wp asg verify | grep -q REVERTED \
           &amp;amp;&amp;amp; wp asg verify | mail -s &quot;WP slug schema reverted on $(hostname)&quot; *Emails are not allowed*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One discipline to internalise: this tripwire restores &lt;em&gt;confidence&lt;/em&gt;, not &lt;em&gt;data&lt;/em&gt;. If it ever reports a reverted column, treat it as an incident, not a self-healing event; the slugs longer than the new width are already gone and must come back from a backup. Prevention (the filter) and backups are your safety net; the tripwire just tells you when to reach for them.&lt;/p&gt;
&lt;h3 id=&quot;-isn-t-varchar-1024-dangerous-no-and-the-reason-is-reassuring&quot;&gt;&quot;Isn't VARCHAR(1024) dangerous?&quot;: no, and the reason is reassuring&lt;/h3&gt;
&lt;p&gt;Widening these columns &lt;em&gt;sounds&lt;/em&gt; risky (indexes, utf8mb4, InnoDB limits), and it's the part people most often get scared off by. It's almost entirely a non-issue, and one fact explains why:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The index on these columns is a fixed 191-character &lt;em&gt;prefix&lt;/em&gt;, independent of the column's declared length.&lt;/strong&gt; Core defines &lt;code&gt;KEY post_name (post_name(191))&lt;/code&gt;; it indexes the first 191 characters whether the column is 200 wide or 1024. Widening the column doesn't enlarge the index by a single byte.&lt;/p&gt;
&lt;p&gt;That dissolves the usual worries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Index / InnoDB prefix limits.&lt;/strong&gt; The old InnoDB prefix ceiling is 767 bytes; WordPress chose 191 because 191 × 4 (utf8mb4's worst case) = 764, just under it. Because the prefix stays at 191 no matter how wide the column grows, you never move &lt;em&gt;towards&lt;/em&gt; that limit. (A quiet irony: slugs are stored as percent-encoded ASCII, so they were never going to hit four bytes per character anyway.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Row size.&lt;/strong&gt; &lt;code&gt;VARCHAR&lt;/code&gt; stores the &lt;em&gt;actual&lt;/em&gt; length of each value, not its declared maximum; a 30-character slug takes ~30 bytes in a &lt;code&gt;VARCHAR(1024)&lt;/code&gt; exactly as it would in a &lt;code&gt;VARCHAR(200)&lt;/code&gt;. Rows don't get bigger because the ceiling moved up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query plans.&lt;/strong&gt; Lookups use the same 191-prefix index to narrow, then verify the full value. Identical behaviour at 200 or 1024.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plugins.&lt;/strong&gt; SEO plugins, caches and CDNs read &lt;code&gt;post_name&lt;/code&gt; and emit longer URLs without complaint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The genuine risks aren't in steady state; they're tools that &lt;strong&gt;recreate the schema&lt;/strong&gt; from an old definition: a migration plugin that runs its own &lt;code&gt;CREATE TABLE&lt;/code&gt;, or, most commonly, restoring a SQL dump taken &lt;em&gt;before&lt;/em&gt; you widened the columns. A dump of the &lt;em&gt;current&lt;/em&gt; database preserves &lt;code&gt;VARCHAR(1024)&lt;/code&gt; correctly; importing an old structure silently puts you back at 200. Write that one down in your runbook.&lt;/p&gt;
&lt;h3 id=&quot;doing-it-safely-on-a-site-with-millions-of-posts&quot;&gt;Doing it safely on a site with millions of posts&lt;/h3&gt;
&lt;p&gt;There's one operational gotcha, and it's about the migration itself, not the result. Going from &lt;code&gt;VARCHAR(200)&lt;/code&gt; to &lt;code&gt;VARCHAR(1024)&lt;/code&gt; &lt;strong&gt;cannot&lt;/strong&gt; be an instant, in-place change. InnoDB only keeps a &lt;code&gt;VARCHAR&lt;/code&gt; length change &quot;in place&quot; while both lengths stay in the same size class: values under 256 use one length byte, values 256 and over use two. Crossing 255 forces &lt;code&gt;ALGORITHM=COPY&lt;/code&gt;: a full table rebuild. On a &lt;code&gt;wp_posts&lt;/code&gt; with millions of rows, a raw &lt;code&gt;ALTER&lt;/code&gt; means a long operation and a problematic lock.&lt;/p&gt;
&lt;p&gt;So on a large live site, do the initial widening with an online schema-change tool, not a bare &lt;code&gt;ALTER&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pt-online-schema-change \
  --alter &quot;MODIFY post_name VARCHAR(1024) NOT NULL DEFAULT ''&quot; \
  --chunk-time=0.5 --max-load Threads_running=50 \
  --execute D=wordpress,t=wp_posts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;code&gt;gh-ost&lt;/code&gt; is an equally good triggerless alternative.) &lt;code&gt;wp_terms&lt;/code&gt; is tiny and takes a plain &lt;code&gt;ALTER&lt;/code&gt; without ceremony; &lt;code&gt;wp_posts&lt;/code&gt; is the one that needs the online tool. The rollout that's bitten no one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Back up&lt;/strong&gt;, and confirm the dump shows &lt;code&gt;VARCHAR(1024)&lt;/code&gt; afterwards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clone to staging&lt;/strong&gt;, deploy the MU-plugin, and run a &lt;em&gt;real&lt;/em&gt; major core update there. Confirm no &lt;code&gt;CHANGE COLUMN&lt;/code&gt; appears and the verify command reports &lt;code&gt;OK&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy the MU-plugin to production &lt;em&gt;before&lt;/em&gt; the next core update&lt;/strong&gt;: prevention has to be in place ahead of the event it prevents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Widen the production columns off-peak&lt;/strong&gt; with pt-osc / gh-ost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add a nightly verify cron and a 404 monitor&lt;/strong&gt;, so anything unexpected surfaces in hours, not in a month of lost rankings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the MU-plugin in version control&lt;/strong&gt;, and never import a pre-widening structure dump.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-deeper-fix-stop-betting-your-urls-on-a-column-width&quot;&gt;The deeper fix: stop betting your URLs on a column width&lt;/h3&gt;
&lt;p&gt;Everything above makes Arabic slugs safe. But there's a more permanent way to think about it, and for a large news archive it's worth the shift: &lt;strong&gt;don't let the URL depend on the slug surviving at all.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Anchor your permalinks on the numeric post ID, and keep the Arabic slug as a descriptive (but cosmetic) suffix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://example.com/123456/الذكاء-الاصطناعي
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resolve the post by its ID, and the slug becomes decoration. If it's ever truncated, malformed, or edited, the URL &lt;strong&gt;still resolves&lt;/strong&gt; to the right article instead of 404-ing, and WordPress can &lt;code&gt;301&lt;/code&gt; to the canonical form. You keep the Arabic keywords in the URL for search relevance &lt;em&gt;and&lt;/em&gt; you become structurally immune to the entire class of problem. (If you want to stop touching core tables altogether, you can go one step further and store the long slug in your own indexed routing table, which dbDelta has no opinion about, but for most teams, ID-anchored permalinks are the sweet spot.)&lt;/p&gt;
&lt;p&gt;That's the difference between patching a bug and designing it out.&lt;/p&gt;
&lt;h3 id=&quot;the-complete-plugin&quot;&gt;The complete plugin&lt;/h3&gt;
&lt;p&gt;Everything above, assembled into one must-use plugin. Drop it in &lt;code&gt;wp-content/mu-plugins/wp-arabic-slug-schema-guard.php&lt;/code&gt;, widen the columns once with an online schema-change tool, and all three layers, plus the tripwire, are handled in a single place. The slug generator is a copy of core's &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;; only the byte cap differs.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;📦 &lt;strong&gt;Prefer it ready-made?&lt;/strong&gt; The plugin is open-source on GitHub as &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mantekio/wp-arabic-slug-schema-guard&quot;&gt;&lt;code&gt;mantekio/wp-arabic-slug-schema-guard&lt;/code&gt;&lt;/a&gt;, or &lt;code&gt;composer require mantekio/wp-arabic-slug-schema-guard&lt;/code&gt;. Star it, file an issue, or lift it whole.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * Plugin Name: Arabic Slug Schema Guard (must-use)
 * Description: Keeps wp_posts.post_name and wp_terms.slug at VARCHAR(1024) across
 *              core DB upgrades, lets core generate long URL-encoded Arabic slugs,
 *              and verifies + alerts after every update. Prevention-first:
 *              dbDelta never truncates, because the canonical schema it diffs
 *              against already says 1024. The slug fork self-tests against core
 *              on each update and can fail safe.
 */
defined( 'ABSPATH' ) || exit;

const ASG_COLUMN_LEN = 1024;  // physical column width (bytes)
const ASG_SLUG_BYTES = 1000;  // max generated slug length, under the column width
const ASG_SCHEMA_SIG = 'post_name:1024;slug:1024';
// define( 'ASG_ALERT_EMAIL', '*Emails are not allowed*' );  // optional, set in wp-config.php
// define( 'ASG_L2_FAILSAFE', true );               // optional: on Layer-2 drift, fall back to core (slugs cap at 200) until you re-sync

/* LAYER 1: PREVENTION
 * Rewrite the canonical CREATE TABLE dbDelta diffs against, so &quot;desired&quot; already
 * equals the live 1024-wide column and no destructive CHANGE COLUMN is emitted. */
add_filter( 'dbdelta_create_queries', function ( $queries ) {
    global $wpdb;
    foreach ( array( $wpdb-&amp;gt;posts =&amp;gt; 'post_name', $wpdb-&amp;gt;terms =&amp;gt; 'slug' ) as $table =&amp;gt; $column ) {
        if ( isset( $queries[ $table ] ) ) {
            $queries[ $table ] = preg_replace(
                '/(\b' . $column . '\s+varchar\()\s*200\s*(\))/i',
                '${1}' . ASG_COLUMN_LEN . '${2}',
                $queries[ $table ]
            );
        }
    }
    return $queries;
} );

/* LAYER 2: GENERATION
 * Core caps new slugs at 200 bytes via utf8_uri_encode($title, 200). Swap in a
 * copy of sanitize_title_with_dashes() whose only change is the byte budget.
 * Installed conditionally: if the drift guard (below) flagged THIS core version
 * and ASG_L2_FAILSAFE is on, stay on core's default (caps at 200, degraded but
 * safe and known) rather than run a fork we know has drifted. */
if ( get_option( 'asg_l2_status' ) === 'drift:' . $GLOBALS['wp_version']
     &amp;amp;&amp;amp; defined( 'ASG_L2_FAILSAFE' ) &amp;amp;&amp;amp; ASG_L2_FAILSAFE ) {
    // Known-drifted on this core version → leave core's generator in place.
} else {
    remove_filter( 'sanitize_title', 'sanitize_title_with_dashes' );
    add_filter( 'sanitize_title', 'asg_sanitize_title_with_dashes', 10, 3 );
}

function asg_sanitize_title_with_dashes( $title, $raw_title = '', $context = 'display' ) {
    $title = strip_tags( $title );
    // Preserve already-encoded octets through the cleanup below.
    $title = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title );
    $title = str_replace( '%', '', $title );
    $title = preg_replace( '|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title );

    if ( seems_utf8( $title ) ) {
        if ( function_exists( 'mb_strtolower' ) ) {
            $title = mb_strtolower( $title, 'UTF-8' );
        }
        $title = utf8_uri_encode( $title, ASG_SLUG_BYTES );  // core hard-codes 200 here
    }

    $title = strtolower( $title );

    if ( 'save' === $context ) {
        // EVERYTHING here is copied verbatim from core. The ONLY sanctioned difference
        // between this fork and core is the byte budget above. Drop a line and you have
        // drifted, see the note under the drift guard below.

        // Turn non-breaking spaces, en/em dashes and slashes into hyphens.
        $title = str_replace( array( '%c2%a0', '%e2%80%93', '%e2%80%94' ), '-', $title );
        $title = str_replace( array( ' ', ' ', '–', '–', '—', '—' ), '-', $title );
        $title = str_replace( '/', '-', $title );

        // Strip these characters entirely. Note %c2%ab / %c2%bb: the guillemets Arabic
        // uses as quotation marks. And %e2%80%8e / %e2%80%8f: the LRM and RTL marks.
        $title = str_replace(
            array(
                '%c2%ad',                                            // soft hyphen
                '%c2%a1', '%c2%bf',                                  // iexcl, iquest
                '%c2%ab', '%c2%bb', '%e2%80%b9', '%e2%80%ba',        // angle quotes
                '%e2%80%98', '%e2%80%99', '%e2%80%9c', '%e2%80%9d',  // curly quotes
                '%e2%80%9a', '%e2%80%9b', '%e2%80%9e', '%e2%80%9f',
                '%e2%80%a2',                                         // bullet
                '%c2%a9', '%c2%ae', '%c2%b0', '%e2%80%a6', '%e2%84%a2',
                '%c2%b4', '%cb%8a', '%cc%81', '%cd%81',              // acute accents
                '%cc%80', '%cc%84', '%cc%8c',                        // grave, macron, caron
                '%e2%80%8b', '%e2%80%8c', '%e2%80%8d',               // zero-width chars
                '%e2%80%8e', '%e2%80%8f',                            // LRM, RTL mark
                '%e2%80%aa', '%e2%80%ab', '%e2%80%ac',
                '%e2%80%ad', '%e2%80%ae',
                '%ef%bb%bf',                                         // byte order mark
                '%ef%bf%bc',                                         // object replacement
            ),
            '',
            $title
        );

        // Convert × to 'x'.
        $title = str_replace( '%c3%97', 'x', $title );
    }

    $title = preg_replace( '/&amp;amp;.+?;/', '', $title );          // strip remaining entities
    $title = str_replace( '.', '-', $title );
    $title = preg_replace( '/[^%a-z0-9 _-]/', '', $title );  // keep only slug-safe chars
    $title = preg_replace( '/\s+/', '-', $title );
    $title = preg_replace( '|-+|', '-', $title );
    return trim( $title, '-' );
}

/* LAYER 2 DRIFT GUARD: self-test the fork against core's live implementation.
 * remove_filter() only unhooks sanitize_title_with_dashes(); the function stays
 * callable, so we use core's CURRENT code as a live oracle and assert our fork
 * still agrees on SHORT inputs (where neither byte cap engages, the only thing
 * the fork changed). Any divergence == core altered the cleanup logic and our
 * copy has drifted. Runs once per core version; shouts, and can fail safe. */
add_action( 'admin_init', 'asg_guard_layer2_drift' );

function asg_guard_layer2_drift() {
    $ok      = 'ok:' . $GLOBALS['wp_version'];
    $drifted = 'drift:' . $GLOBALS['wp_version'];
    if ( in_array( get_option( 'asg_l2_status' ), array( $ok, $drifted ), true ) ) {
        return;  // already settled for this core version
    }
    if ( ! function_exists( 'sanitize_title_with_dashes' ) ) {
        return;  // core refactored it away: no oracle to diff against; the fork
                 // still runs as a standalone copy (it needs no removed function).
    }

    // Deliberately SHORT fixtures so neither length cap fires; any difference is
    // pure cleanup-logic drift, not the byte budget.
    $fixtures = array(
        'الذكاء الاصطناعي',
        'عاجل: تطورات «مهمة» اليوم',
        'Mixed عربي + English — dash',
        'foo/bar — baz',
        'UPPER   multiple   spaces',
        '%d8%a7 pre-encoded octet',
    );

    $drift = array();
    foreach ( $fixtures as $f ) {
        $core = sanitize_title_with_dashes( $f, $f, 'save' );
        $ours = asg_sanitize_title_with_dashes( $f, $f, 'save' );
        if ( $core !== $ours ) {
            $drift[] = &quot;in=[{$f}] core=[{$core}] ours=[{$ours}]&quot;;
        }
    }

    if ( $drift ) {
        $msg = &quot;[Arabic Slug Schema Guard] Layer-2 DRIFT on WP {$GLOBALS['wp_version']}: core &quot;
             . &quot;sanitize_title_with_dashes() no longer matches our fork; re-sync the copy.\n&quot;
             . implode( &quot;\n&quot;, $drift );
        error_log( $msg );
        if ( defined( 'ASG_ALERT_EMAIL' ) &amp;amp;&amp;amp; function_exists( 'wp_mail' ) ) {
            wp_mail( ASG_ALERT_EMAIL, 'WP slug Layer-2 drift: ' . wp_parse_url( home_url(), PHP_URL_HOST ), $msg );
        }
        update_option( 'asg_l2_status', $drifted, true );  // autoload: the fail-safe reads it every request
        return;  // do NOT cache &quot;ok&quot; while drifted
    }
    update_option( 'asg_l2_status', $ok, true );
}

/* LAYER 3: DE-DUPLICATION (optional)
 * _truncate_post_slug() also caps at 200, but only when a slug COLLIDES and needs
 * a &quot;-2&quot; suffix, rare for unique headlines, and it isn't filterable. If you need
 * &amp;gt;200 bytes even on collisions, take over uniqueness here. Most sites skip this.
 *
 * add_filter( 'pre_wp_unique_post_slug', 'asg_unique_post_slug', 10, 6 );
 */

/* TRIPWIRE: verify + alert after every core update
 * This only detects and alerts: it restores the COLUMN, never bytes already
 * truncated. Treat any revert as an incident: restore from backup. */
add_action( 'upgrader_process_complete', function ( $upgrader, $extra ) {
    if ( isset( $extra['type'] ) &amp;amp;&amp;amp; 'core' === $extra['type'] ) {
        delete_option( 'asg_schema_sig' );  // force a re-check on the next admin_init
    }
}, 10, 2 );

add_action( 'admin_init', function () {
    if ( get_option( 'asg_schema_sig' ) === ASG_SCHEMA_SIG ) {
        return;  // fast path: already verified since the last update
    }
    $reverted = asg_reverted_columns();
    if ( $reverted ) {
        $msg = '[Arabic Slug Schema Guard] Column(s) reverted: ' . implode( ', ', $reverted )
             . '. Long slugs were truncated: restore from backup and check 404 logs.';
        error_log( $msg );
        if ( defined( 'ASG_ALERT_EMAIL' ) ) {
            wp_mail( ASG_ALERT_EMAIL, 'WP slug schema reverted: ' . wp_parse_url( home_url(), PHP_URL_HOST ), $msg );
        }
        return;  // do NOT cache &quot;ok&quot; while broken
    }
    update_option( 'asg_schema_sig', ASG_SCHEMA_SIG, false );
} );

function asg_reverted_columns() {
    global $wpdb;
    $reverted = array();
    foreach ( array( array( $wpdb-&amp;gt;posts, 'post_name' ), array( $wpdb-&amp;gt;terms, 'slug' ) ) as list( $table, $column ) ) {
        $len = (int) $wpdb-&amp;gt;get_var( $wpdb-&amp;gt;prepare(
            &quot;SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS
              WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = %s&quot;,
            DB_NAME, $table, $column
        ) );
        if ( $len &amp;amp;&amp;amp; $len &amp;lt; ASG_COLUMN_LEN ) {
            $reverted[] = &quot;{$table}.{$column} (now {$len})&quot;;
        }
    }
    return $reverted;
}

/* WP-CLI: `wp asg verify` for cron-based monitoring */
if ( defined( 'WP_CLI' ) &amp;amp;&amp;amp; WP_CLI ) {
    WP_CLI::add_command( 'asg verify', function () {
        foreach ( asg_reverted_columns() ?: array( 'ok' ) as $row ) {
            WP_CLI::log( 'ok' === $row ? 'Schema OK: both columns at VARCHAR(1024).' : &quot;REVERTED: {$row}&quot; );
        }
    } );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;a-postscript-the-drift-guard-caught-us&quot;&gt;A postscript: the drift guard caught us&lt;/h4&gt;
&lt;p&gt;The argument above is that a fork of a core function drifts silently, so the guard matters more than the copy. That turned out to be true in the least comfortable way.&lt;/p&gt;
&lt;p&gt;Versions 1.0.0 through 1.2.0 of the released plugin shipped a fork that was &lt;strong&gt;missing core's entire &quot;strip these characters entirely&quot; block&lt;/strong&gt;, along with the &lt;code&gt;×&lt;/code&gt; to &lt;code&gt;x&lt;/code&gt; mapping. The consequence lands exactly on the readers this post is written for: Arabic guillemets (&lt;code&gt;«&lt;/code&gt; &lt;code&gt;»&lt;/code&gt;) and the LRM/RTL direction marks survived into slugs as &lt;code&gt;%c2%ab&lt;/code&gt;, &lt;code&gt;%c2%bb&lt;/code&gt;, &lt;code&gt;%e2%80%8f&lt;/code&gt;. The character filter at the end cannot remove them, because &lt;code&gt;%c2%ab&lt;/code&gt; is built entirely from permitted characters.&lt;/p&gt;
&lt;p&gt;The drift guard printed above would have caught it on the first &lt;code&gt;admin_init&lt;/code&gt;. Its second fixture is &lt;code&gt;'عاجل: تطورات «مهمة» اليوم'&lt;/code&gt;. It never fired, because the guard is bound to &lt;code&gt;admin_init&lt;/code&gt; and nobody had opened &lt;code&gt;wp-admin&lt;/code&gt; on an install running that fork. A guard on a hook that never fires is a comment.&lt;/p&gt;
&lt;p&gt;Both are fixed in &lt;strong&gt;v1.3.0&lt;/strong&gt;: the fork is re-synced (the block above is now complete and verified against core by an offline oracle on every build), and &lt;code&gt;wp asg verify&lt;/code&gt; evaluates the drift oracle itself rather than waiting for an admin page load. If you lifted this code before July 2026, take the &lt;code&gt;str_replace&lt;/code&gt; block above.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is the kind of problem that never shows up in a tutorial. It lives where WordPress internals, MySQL's storage rules, Arabic character encoding and SEO all overlap, and it only reveals itself at the worst possible moment. It's also the territory we work in every day: keeping Arabic publishing fast, multilingual and unbreakable on WordPress, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/wordpress-aws&quot;&gt;right through to AWS at scale&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you run a WordPress publication in Arabic and want it to survive its own updates, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/contact&quot;&gt;let's talk&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/functions/dbdelta/&quot;&gt;&lt;code&gt;dbDelta()&lt;/code&gt;&lt;/a&gt; and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/hooks/dbdelta_create_queries/&quot;&gt;&lt;code&gt;dbdelta_create_queries&lt;/code&gt; hook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://core.trac.wordpress.org/ticket/36748&quot;&gt;Trac #36748&lt;/a&gt;: the TEXT/BLOB downsize protection that VARCHAR never got&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/functions/sanitize_title_with_dashes/&quot;&gt;&lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/functions/_truncate_post_slug/&quot;&gt;&lt;code&gt;_truncate_post_slug()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/hooks/upgrader_process_complete/&quot;&gt;&lt;code&gt;upgrader_process_complete&lt;/code&gt; hook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28135/the-200-byte-trap-why-wordpress-core-updates-break-arabic-urls</guid>
<pubDate>Tue, 22 Sep 2026 09:22:50 +0000</pubDate>
</item>
<item>
<title>I hid an entire webpage inside a cat face</title>
<link>https://coderlegion.com/18666/i-hid-an-entire-webpage-inside-a-cat-face</link>
<description>&lt;p&gt;The source of this page is just a cat face:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nothing-to-see-here.surge.sh/&quot;&gt;https://nothing-to-see-here.surge.sh/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;View source. You'll see (=･ω･=) and nothing else.&lt;br&gt;
But the page runs. Rainbow animation, layout, everything.&lt;/p&gt;
&lt;p&gt;The entire JavaScript is encoded as invisible Unicode &lt;br&gt;
Variation Selectors attached to the cat emoji.&lt;/p&gt;
&lt;p&gt;Unicode VS (U+FE00–FE0F and U+E0100–E01EF) map &lt;br&gt;
precisely to 256 byte values. Any byte sequence can &lt;br&gt;
ride inside normal text, invisible to readers, &lt;br&gt;
surviving copy-paste across Slack, X, LINE, iMessage.&lt;/p&gt;
&lt;p&gt;That's subtext.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://prospectorlabs.dev/subtext&quot;&gt;https://prospectorlabs.dev/subtext&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18666/i-hid-an-entire-webpage-inside-a-cat-face</guid>
<pubDate>Fri, 22 May 2026 10:33:59 +0000</pubDate>
</item>
<item>
<title>Unicode escape' codec can't decode bytes in position 2-3: truncated \ uxxxxxxxx escape</title>
<link>https://coderlegion.com/17134/unicode-escape-codec-cant-decode-bytes-in-position-2-3-truncated-uxxxxxxxx-escape</link>
<description>&lt;p&gt;In this article, we will solve a syntax based problem of &lt;strong&gt;&quot;Unicodeescape' codec can't decode bytes in position 2-3: truncated \uxxxxxxxx escape&quot;&lt;/strong&gt;. This problem is encountered when we make a mistake in using the &lt;strong&gt;&quot;unicode esacpe characters.&quot;&lt;/strong&gt;Python's interpreter is designed to understand certain characters with certain symbols and in programming each character is given a unicode. This error occurs when the interpreter fails to distinguish between a normal character and a &lt;strong&gt;unicode character&lt;/strong&gt;. We can solve this problem by simply &lt;strong&gt;manipulating the string&lt;/strong&gt; according to the interpreter's logic. We will also discuss how these unicode character works and the reason behind this syntax problem. Let's quickly go through the contents of this article. &lt;/p&gt;
&lt;div id=&quot;toc-div&quot;&gt;
&lt;h4&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Table of contents: - &lt;/strong&gt;&lt;a id=&quot;toc-toogle&quot; class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div id=&quot;toc-list&quot;&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#what&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Understanding the error and discussing how the interpreter functions.&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#why&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Solving the error with a relevant example&lt;/span&gt;&lt;/a&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#sol1&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Solution 1.&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#sol2&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Solution 2.&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#sol3&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Solution 3.&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#sol&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Why unicode characters were assigned and steps to avoid this error.&lt;/span&gt;&lt;/a&gt;

&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#con&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;The Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section&gt;&lt;a id=&quot;what&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Understanding the error and discussing how the interpreter functions.&lt;/strong&gt;&lt;/span&gt;&lt;strong&gt; &lt;a id=&quot;TypeError-str-object&quot; style=&quot;color: blue;&quot; rel=&quot;nofollow&quot; href=&quot;#what&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial, helvetica, sans-serif;&quot;&gt;Let's break this error statment down: &lt;strong&gt;&quot;Unicodeescape' codec can't decode bytes in position 2-3: truncated \uxxxxxxxx escape&quot;.&lt;/strong&gt; This means that there is a syntax error and the interpreter is not able understand the meaning of this code. The &lt;strong&gt;&quot;Unicodeescape&quot;&lt;/strong&gt; term means that the syntax error is caused because the unicode escape characters are not used properly. Now we have a lot to dissect, let's start with the interpreter's logic. if we pass a command like: -  &lt;/span&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;print('This is her mother's dress')

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

The output of this code will be: -
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;print('This is her mother's dress.')
          ^^^^^^^^^^^^^^^^^^^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the interpreter is unable to understand the range of the string. We use &quot; &lt;strong&gt;'&lt;/strong&gt; &quot; character to set the range of characters to be printed. The interpreter is confused because it is not able to distinguish between a normal character and a single quote range setter. We can solve this problem with the help of escape sequences. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;print('This is her mother\'s dress')

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;This is her mother's dress
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here , the backslash character instructs the interpreter that it is an escape sequence for the single quote character ('). As we observed that the interpreter was confused and this confusion led to a syntax error. This is the exact problem we are facing. Let's understand this with the help of an example. &lt;/p&gt;
&lt;section&gt;&lt;a id=&quot;why&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Solving the error with a relevant example&lt;/strong&gt;&lt;/span&gt;&lt;strong&gt;&lt;a id=&quot;reasons-that-will-cause&quot; style=&quot;color: blue;&quot; rel=&quot;nofollow&quot; href=&quot;#why&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;We know that the interpreter follow certain rules while printing and traversing the data. Suppose we have to load a file and traverse the data stored in it. We use the file handling concepts and perform the operation but on running the program we encounter the same error: - &lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;path = &quot;C:\Users\Dell\Downloads\lamdaphagevirus\lamda.txt&quot;
file = open(path, &quot;r&quot;)
data=file.read()
print(data)

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;path = &quot;C:\Users\Dell\Downloads\lamdaphagevirus\lamda.txt&quot;
                                                              ^
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \UXXXXXXXX escape

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What went wrong here? The error is in the path variable. we passed the location of the file and it consists of multiple backslashes. Now the interpreter is confused which character is a normal character and which one is a &lt;strong&gt;unicode escape character.&lt;/strong&gt; We know that the entire file location is a normal string without any unicode characters but the use of &quot;\&quot; characters is causing the problem. How are we going to solve this problem? Let's look at some solutions: - &lt;/p&gt;
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Tip&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;Each character is assigned with a unicode and an escape sequence which is universal for all the languages.&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;a id=&quot;sol1&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Solution 1&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;For the 1st solution, we will convert out string into a &lt;strong&gt;raw string&lt;/strong&gt; like this: -&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;path = r&quot;C:\Users\Dell\Downloads\lamdaphagevirus\lamda.txt&quot;
file = open(path, &quot;r&quot;)
data=file.read()
print(data)

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;GGGCGGCGACCTCGCGGGTTTTCGCTATTTATGAAAATTTTCCGGTTTAAGGCGTTTCCGTTCTTCTTCG
TCATAACTTAATGTTTTTATTTAAAATACCCTCTGAAAAGAAAGGAAACGACAGGTGCTGAAAGCGAGGC

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A raw string is used whenever we want to avoid the technique of escaping characters with the help of backslashes. In order to convert a string into a raw string we have to simply add a &lt;strong&gt;&quot;r&quot;&lt;/strong&gt; before the starting of the string. If this solution does not work for you then try the next method.&lt;/p&gt;
&lt;p&gt;After this manually install &quot;dlib&quot; from &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/simple/dlib/&quot;&gt;this link.&lt;/a&gt; This can help the programmer to solve this issue. &lt;/p&gt;

&lt;a id=&quot;sol2&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Solution 2&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;If the the raw string method fails then try to escape the characters with the help of a backslash.&lt;/span&gt;
&lt;/p&gt;&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;path = &quot;C:\\Users\\Dell\\Downloads\\lamdaphagevirus\\lamda.txt&quot;
file = open(path, &quot;r&quot;)
data=file.read()
print(data)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;GGGCGGCGACCTCGCGGGTTTTCGCTATTTATGAAAATTTTCCGGTTTAAGGCGTTTCCGTTCTTCTTCG
TCATAACTTAATGTTTTTATTTAAAATACCCTCTGAAAAGAAAGGAAACGACAGGTGCTGAAAGCGAGGC

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the interpreter a single backslash character means this &quot;\\&quot;. All the escape characters begin with a backslash and by using this character before every backslash in the string, we can instruct the interpreter to treat this character as a escape sequence.&lt;/p&gt;
&lt;a id=&quot;sol3&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Solution 3&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;For the third solution we have to manipulate the string and replace every backslash with a &lt;strong&gt;&quot;/&quot;&lt;/strong&gt; character.&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;path = &quot;C:/Users/Dell/Downloads/lamdaphagevirus/lamda.txt&quot;
file = open(path, &quot;r&quot;)
data=file.read()
print(data)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;GGGCGGCGACCTCGCGGGTTTTCGCTATTTATGAAAATTTTCCGGTTTAAGGCGTTTCCGTTCTTCTTCG
TCATAACTTAATGTTTTTATTTAAAATACCCTCTGAAAAGAAAGGAAACGACAGGTGCTGAAAGCGAGGC
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-purple&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;In case of file handling, majority of the errors are caused due to an invalid location entry.&lt;/span&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;p&gt;To know more about unicode errors &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3/howto/unicode.html&quot;&gt;vist this link.&lt;/a&gt;&lt;/p&gt;
&lt;section&gt;
&lt;a id=&quot;sol&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Why unicode characters were assigned and how to avoid this error.&lt;/strong&gt;&lt;/span&gt;&lt;strong&gt;&lt;a id=&quot;the-solution&quot; style=&quot;color: blue;&quot; rel=&quot;nofollow&quot; href=&quot;#sol&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Now that we have discussed the different solutions for the unicode error, we will try to understand the logic behind the designation of such codes. The main reason for assigning a universal code to every character was to establish uniformity in programming. During the early phases when technology was developing, majority of the codes were built around English language but as the technology progressed many nations started to get involved in this field. So, in order to avoid confusion and establish uniformity among the programming world, codes were assigned which were universal for all the languages around the globe.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We have to learn more about escape sequences and unicode if we want to avoid this kind of syntax error.&lt;/p&gt;


&lt;/section&gt;

&lt;section&gt;&lt;a id=&quot;con&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;The conclusion&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;This article offers the most effective solution for the error: - &lt;strong&gt;&quot;Unicodeescape' codec can't decode bytes in position 2-3: truncated \uxxxxxxxx escape&quot;.&lt;/strong&gt; We discussed about the logic of interpreter and the different ways to solve this error. We strolled through the concepts of &lt;strong&gt;unicode&lt;/strong&gt; and &lt;strong&gt;escape sequences&lt;/strong&gt;. We also discussed the idea and purpose behind the creation of unicode.&lt;/span&gt;&lt;/p&gt;
&lt;/section&gt;&lt;/section&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17134/unicode-escape-codec-cant-decode-bytes-in-position-2-3-truncated-uxxxxxxxx-escape</guid>
<pubDate>Sat, 09 May 2026 07:21:58 +0000</pubDate>
</item>
<item>
<title>Unicodedecodeerror: 'utf-8' codec can't decode bytes in position 0-1: invalid continuation byte</title>
<link>https://coderlegion.com/13535/unicodedecodeerror-utf-8-codec-cant-decode-bytes-in-position-invalid-continuation-byte</link>
<description>&lt;p&gt;&lt;strong&gt;When working with strings you may have encountered the error &quot;UnicodeDecodeError: 'utf-8' codec can't decode byte X in position X: invalid continuation byte&quot;. It occurs because when we specify an incorrect encoding when decoding bytes data. In order to fix the issue, we have to specify correct encoding. &lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;
&lt;h4&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt; Table of Contents: &lt;a id=&quot;table-of-contents&quot; rel=&quot;nofollow&quot; href=&quot;#table-of-contents&quot; style=&quot;color: blue&quot;&gt;#&lt;/a&gt;&lt;a class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;UnicodeDecodeError: 'utf-8' codec can't decode byte X in position X: invalid continuation byte &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#sol&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;The Solution&lt;/span&gt;&lt;/a&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#sol1&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;Solution one&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#sol2&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;Solution two&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#con&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;The Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt; &lt;/p&gt;&lt;h2&gt;&lt;strong&gt;UnicodeDecodeError: 'utf-8' codec can't decode byte X in position X: invalid continuation byte &lt;a rel=&quot;nofollow&quot; href=&quot;#what&quot; style=&quot;color: blue&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;Managing the encoding of character strings can sometimes cause problems in Python. For example, the error &quot;UnicodeDecodeError: 'utf-8' codec can't decode byte xxx in position x: invalid continuation byte&quot; occurs in a script in Python when trying to decode a string to UTF-8 but that it is not encoded in this way. If you are handling a file and you don't know the encoding, there are solutions to work around this error.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;To understand what the errors means, we have to analyze the error message. Strings have encoding that describes character set and collation the string bytes objects will accept. When we encode the string in UTF-8, we specify data in UTF-8 standard format. If we were to decode the string, we were to decode the string with latin-1 for instance, that will cause the error &quot;&lt;/span&gt;&lt;strong&gt;UnicodeDecodeError: 'utf-8' codec can't decode byte in position: invalid continuation byte&quot;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;div-blue&quot;&gt;Encoding is a process whereby we turn sequence of characters, which includes alphabet, numbers, punctuation and all of the other symbols, into bytes for efficiency in transmission and storing. Decoding is the opposite of encoding. It's a process of turning bytes into the sequence of characters.&lt;/div&gt;
&lt;p&gt;Here's a demonstration of the problem:&lt;/p&gt;
&lt;pre&gt;
&lt;code class=&quot;language-python&quot;&gt;str_bytes = 'ééééééé'.encode('latin-1')

# ⛔️ UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 0: invalid continuation byte
my_str = str_bytes.decode('utf-8')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code runs into an error when we try to decode the string with an encoding that wasn't used to decode the string. To fix this we have to decode the string using the same encoding we used to encode it. Here's the fix to the previous error:&lt;/p&gt;
&lt;pre&gt;
&lt;code class=&quot;language-python&quot;&gt;str_bytes = 'ééééééé'.encode('latin-1')
my_str = str_bytes.decode('latin-1')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the program runs correctly and no errors occur.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt; &lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;&lt;strong&gt;The Solution &lt;/strong&gt;&lt;/span&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#sol&quot; style=&quot;color: blue&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;In general, you ought to decode your strings in the same encoding you used to decode but at times this issue is hard to avoid because it can be difficult to keep track of many encoding. That's why I present two solution. The first one is to decode your strings correctly and the second one is to read the strings from a file and have Python automatically find the string encoding.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;&lt;strong&gt;Solution one&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;When doing a decoding of bytes using &lt;strong&gt;decode()&lt;/strong&gt; method, you have to make sure that the encoding is the one was used to encode the string into bytes. For example, in this example I have set the encoding of &lt;strong&gt;first_string &lt;/strong&gt;to UTF-8 which is an encoding that accepts all known language on earth, and then to decode it I have set the encoding to UTF-8 again.&lt;/p&gt;
&lt;pre&gt;
&lt;code class=&quot;language-python&quot;&gt;first_string = 'ééééééé'.encode('utf-8')
decoded_str = first_string.decode('utf-8')
print(decoded_str)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;&lt;strong&gt;Solution two&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;File management functions have a &quot;Binary&quot; mode which treats characters as &quot;bytes&quot;. With this mode, no decoding is performed and the characters are thus preserved, whatever their encoding. To open a file in binary mode, you must specify the &quot;rb&quot; mode.&lt;/p&gt;
&lt;pre&gt;
&lt;code class=&quot;language-python&quot;&gt;with open(filePath, 'rb') as file:
    content = file.read()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To write to a file in binary mode, you must use the &quot;wb&quot; or &quot;ab&quot; modes.&lt;/p&gt;
&lt;p&gt;If, despite the encoding problem, you want to open the file and read the content in utf-8, it is possible to add an additional parameter to the &quot;open()&quot; function telling it to ignore errors. Characters that cannot be read will be ignored and not displayed.&lt;/p&gt;
&lt;pre&gt;
&lt;code class=&quot;language-python&quot;&gt;with open(filePath, encoding=&quot;utf8&quot;, errors='ignore') as file:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &quot;byte 0xff in position 0&quot; error that appears when you try to decode a file in UTF-8 may simply indicate that the file is encoded in UTF-16. You can try changing the opening encoding of the file.&lt;/p&gt;
&lt;pre&gt;
&lt;code class=&quot;language-python&quot;&gt;with open(filePath, encoding='utf-16') as file:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This solution only works with Python 3, which includes UTF-16 encoding support in the &quot;open()&quot; function. If you are using Python 2, you will have to perform a conversion after opening the file in binary mode.&lt;/p&gt;
&lt;pre&gt;
&lt;code class=&quot;language-python&quot;&gt;with open(filePath,'rb') as file:
    content = file.read()
content = content.rstrip(&quot;\n&quot;).decode(&quot;utf-16&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;&lt;strong&gt;The Conclusion&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif&quot;&gt;Thank you for sticking with this tutorial all the way to the end. When attempting to decode a string using &lt;strong&gt;decode() &lt;/strong&gt;method and passing wrong encoding as a parameter the error message &quot;&lt;/span&gt;&lt;strong&gt;UnicodeDecodeError: 'utf-8' codec can't decode byte X in position X: invalid continuation byte&lt;/strong&gt;&quot;. To fix this issue, you have to either find out what encoding was used for the string in &lt;strong&gt;encode() &lt;/strong&gt;method and use it to decode the string using &lt;strong&gt;decode()&lt;/strong&gt; method. If you are reading encoding from a file and not a string, you can open the file in binary mode with &lt;strong&gt;open()&lt;/strong&gt; method and pass the correct encoding to it's &lt;strong&gt;encoding&lt;/strong&gt; parameter.&lt;/p&gt;
&lt;p&gt;If you found this article helpful. Please comment and share. If you find any issue you can always tell, I'll get back to you as soon as possible. Take care!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13535/unicodedecodeerror-utf-8-codec-cant-decode-bytes-in-position-invalid-continuation-byte</guid>
<pubDate>Fri, 27 Mar 2026 02:21:43 +0000</pubDate>
</item>
<item>
<title>Data Types in Java, Part 1, the primitive types in detail</title>
<link>https://coderlegion.com/5353/data-types-in-java-part-1-the-primitive-types-in-detail</link>
<description>&lt;p&gt;&lt;strong&gt;Data Types in Java&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All the data types in Java have been categorized into two, primitive data types&lt;br&gt;
and the reference data types.&lt;/p&gt;
&lt;p&gt;The primitive types, also known as built-in types, are &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;float&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;, &lt;code&gt;char&lt;/code&gt; and &lt;code&gt;boolean&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The reference data types are arrays, classes, interfaces, enums, annotations&lt;br&gt;
and records.&lt;/p&gt;
&lt;p&gt;enums and annotations were introduced into the Java programming&lt;br&gt;
language from Java 5 and records were introduced into the Java Programming&lt;br&gt;
Language from Java 16.&lt;/p&gt;
&lt;p&gt;From Java 8, there is another type called Functional Interface, which is identified by compiler. The Functional Interface is a kind of interface, for which instances may be created using lambda expressions(introduced into the Java Programming Language from java 8).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Primitive Data Types&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The following table list the primitive types in Java:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9426385492837984295&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The primitive types may be further categorized into numeric types and boolean&lt;br&gt;
type. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;boolean&lt;/code&gt; Data Type&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A variable of the boolean type can have only two possible values, true or false. In Java, boolean is a separate data type and is not like C language, where a numeric value of non-zero or zero is considered as true or false, i.e. a numeric type cannot be used as boolean. E.g. we cannot use a numeric expression in the condition of the if statement, as is allowed in C, where boolean is not a separate data type. There are many operators on other data types which result in value of type boolean, and all the Logical operators available for boolean&lt;br&gt;
type also result in value of boolean. The various operators will been discussed in a separate tutorial.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Numeric Data Types&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the Java programming language, we do not have the &lt;code&gt;signed&lt;/code&gt; or the &lt;code&gt;unsigned&lt;/code&gt; prefix for the numeric data types. Note that &lt;code&gt;signed&lt;/code&gt; and &lt;code&gt;unsigned&lt;/code&gt; are not keywords in Java.&lt;/p&gt;
&lt;p&gt;Among the numeric types, &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;short&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt; and &lt;code&gt;long&lt;/code&gt; are the signed integral types, &lt;code&gt;char&lt;/code&gt; is an unsigned integral type and the &lt;code&gt;float&lt;/code&gt; and &lt;code&gt;double&lt;/code&gt; are single-precision and double-precision signed floating-point data types.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;byte order&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For all the numeric types whose size is more than 1 byte, there are two ways of ordering the bytes. The byte order for a machine could be big-endian or little-endian. The byte ordering is normally dependent on the architecture of the machine. All of the ×86 and ×64 machines follow the little-endian byte ordering for the numeric types, and most of the other machines, with RISC-based architecture follow the big-endian byte ordering. The Java Virtual Machine&lt;br&gt;
follows the big-endian byte ordering. The difference in the two types of byte orderings is that in case of the little-endian byte ordering, the most significant byte is at a higher location (comes at the end) and the least significant byte is at a lower location (comes first), whereas in case of the big-endian byte ordering, the most significant byte is at a lower location (comes first) and the least significant byte is at a higher location (comes at the end). The following figure shows the difference in byte ordering.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10946838131042678227&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;integral data types&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What is the size of &lt;code&gt;int&lt;/code&gt; in &lt;code&gt;C&lt;/code&gt;? Is it 2 bytes or 4 bytes? It depends on the platform. Most of the &lt;code&gt;C&lt;/code&gt; compilers on the &lt;code&gt;×86&lt;/code&gt; platform consider &lt;code&gt;int&lt;/code&gt; as 2 bytes, whereas on the RISC machines, it is commonly found to be using 4 bytes, in some cases it may even use 8 bytes. Java works on single platform, the JVM; so on this platform the sizes of each of the data types are fixed. The size of &lt;code&gt;byte&lt;/code&gt; is 1 byte, &lt;code&gt;short&lt;/code&gt; is 2 bytes, &lt;code&gt;int&lt;/code&gt; is 4 bytes and &lt;code&gt;long&lt;/code&gt; is 8 bytes. The integral values are stored using 2’s complement. The range of values for the various integral data&lt;br&gt;
types is given in Table below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9326041422696363646&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;floating-point data types&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The types &lt;code&gt;float&lt;/code&gt; and &lt;code&gt;double&lt;/code&gt; represent the single-precision and double-precision floating-point values and their sizes are 4 and 8 bytes, respectively. These floating-point data type representations are according to the IEEE-754 standard for single-precision (32-bit) and double-precision (64-bit) floating-point values. This standard uses the most significant bit (MSB) as the sign bit, and the rest of the bits are divided among two fields, an exponent and a significand. In case of the single-precision value (&lt;code&gt;float&lt;/code&gt; of Java), bit-31 is the sign bit, bits 30–23 are used for the exponent and bits 22–0 are used for the significand. In case of the double-precision value (&lt;code&gt;double&lt;/code&gt; of Java), bit-63 is the sign bit, bits 62–52 are used for the exponent and bits 51–0 are used for the significand. The range of values for the two floating-point data types is given in Table below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1235609696442980213&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Infinities and NaNs for floating-point types&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let us consider the code given below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static void main(String[] args) {
    double a = 75;
    double b = 0;
    double c = a / b;
    System.out.println(c);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What would be the output of executing the above code? Error at line 4 (divide y zero)? No this does not give any error at runtime and executes perfectly. What, is the value of dividing 75 by 0? The above code prints Infinity. If we use int instead of the double data type in the above code then we will get an error at runtime, but for the floating point data types, division by zero is not an error at Runtime. The floating-point data types can represent infinities (positive and negative) as well as not-a-number (result of 0 divide by 0).&lt;/p&gt;
&lt;p&gt;According to the IEEE 754, the floating-point numbers have a representation for positive infinity and negative infinity. There are also representations for the values that are Not-a-Number (&lt;code&gt;NaN&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;When all bits for the exponent field are one, then these numbers are used to represent the infinities and the NaNs. The figures below shows the bit representations for the infinities and the &lt;code&gt;NaN&lt;/code&gt;s. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10021452714406093033&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7919810308081495269&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When all exponent bits are one and all significand bits are zero, they represent infinity. Depending on the sign bit, the infinity is either a positive infinity or a negative infinity. When all exponent bits are one and the significand is a non-zero value, then they represent &lt;code&gt;NaN&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;floating-point types and precision&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The use of floating-point data types &lt;code&gt;float&lt;/code&gt; and &lt;code&gt;double&lt;/code&gt; is discouraged, since these data types have a limitation about precision level. These types cannot precisely represent all the floating-point values. The precision level falls significantly as magnitude of value increases (moving away from zero). for e.g. if, we consider the &lt;code&gt;float&lt;/code&gt; data type, then we find that &lt;code&gt;float&lt;/code&gt; is incapable of represnting &lt;code&gt;123456789&lt;/code&gt;. The &lt;code&gt;float&lt;/code&gt; can represent the value &lt;code&gt;123456784&lt;/code&gt; and then &lt;code&gt;123456792&lt;/code&gt;. We can try the code in Listing below to test this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TestFloatingPoint {
    public static void main(String[] args) {
        for (int i = 123456784; i &amp;lt; 123456793; i++) {
            float f = i;
            System.out.printf(”int:%d, float:%10.1f\n” , i, f ) ;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Atleast, when dealing with larger values do not use the and &lt;code&gt;float&lt;/code&gt; and &lt;code&gt;double&lt;/code&gt; data types. When dealing with very high values, we have the option of using the classes like &lt;code&gt;java.math.BigInteger&lt;/code&gt; and &lt;code&gt;java.math.BigDecimal&lt;/code&gt;, we will discuss them in another tutorial.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;char&lt;/code&gt; data type&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;char&lt;/code&gt; data type in a programming language is used to represent a unit of text. How is text data represented? The text data is represented as sequence of characters. The &lt;code&gt;char&lt;/code&gt; data type is simply a numeric value of the character from a character set.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Character sets.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What is a character set? A character set is a collection of a unit of text(character) which are assigned some unique numeric value. There are various character sets available. The most commonly known character set is the &lt;code&gt;ASCII&lt;/code&gt;(American Standard Code for Information Interchange) character set which assigns only 128 characters(including the control characters) to the numeric values in the range from 0 — 127. Not all characters in a character set are printable. Character sets also include control characters. e.g. we have characters for a carriage-return, line-feed, form-feed, tab, bell etc. These are not having a print position, but give effect to position of next character or some such function.&lt;/p&gt;
&lt;p&gt;In the initial days of computing each OS would support a particular character set and the most commonly supported character sets used to be either ASCII or EBCDIC(Extended Binary Coded Decimal Interchange Code). Later ASCII and its extensions were adopted by most of the OSs. The ASCII character set included only the commonly used latin characters and control characters. Later&lt;br&gt;
ASCII and its extensions became more popular, and most new character sets were extensions of the ASCII character set. Each character set was catering to demands of a particular region/culture’s requirement of text representations. Most of these extensions of ASCII utilized the numeric values from 128 to 255 for the additional characters.&lt;/p&gt;
&lt;p&gt;Some of the common examples of character sets which are extensions of ASCII are the ISO 8859 series, e.g. 8859-1 or Latin-1 caters to the Western European, 8859-2 or Latin-2 caters to Eastern European, 8859-3 or Latin-3 caters to Southern European, 8859-4 or Latin-4 caters to Northern European, 8859-5 or Cyrillic caters to Russian, Bulgarian, 8859-6 or Arabic, caters to the Arabic&lt;br&gt;
characters, 8859-7 or Greek, caters to the Greek characters, 8859-8 or Hebrew, caters to Hebrew characters, 8859-9 or Latin-5 caters to Turkish characters, 8859-10 or Latin-6 caters to Northern European, 8859-11 or Thai, caters to the Thai characters, 8859-13 or Latin-7 caters to Baltic, 8859-14 or Latin-8 caters to Celtic, 8859-15 or Latin-9 caters to Western European and 8859-16 or Latin-10&lt;br&gt;
caters to Eastern European. There other extensions of ASCII. We have ISCII (Indian Script Code for Information Interchange) which caters to the Indian scripts. The ISO 8859-12 was reserved for catering to the Devanagari script, but this was abandoned. The ISO 8859 series of character sets are summarized in Table below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=550315900675864361&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;All the character sets mentioned above are extensioons of ASCII. i.e. They have the same characters in the range from 0 – 127,  as ASCII. A quick summary of the ASCII characters is given in Table below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2165815011618088048&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;char&lt;/code&gt; data type used by many of the programming languages would simply rely on the OSs interpretation of the numeric value of the &lt;code&gt;char&lt;/code&gt; data. i.e. if the OS used a different character set, the same numeric value would be interpreted differently. e.g. According to Latin-1 (8859-1) the value &lt;code&gt;EB&lt;/code&gt;(Hex) is used for representing the character &lt;code&gt;ë&lt;/code&gt;, whereas according to Greek (8859-7) the value &lt;code&gt;EB&lt;/code&gt;(Hex) is used for representing the character &lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2306684328638064457&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To solve this problem a universal character set was designed in the form of Unicode. The first version of Unicode was introduced in 1991. Unicode character set was designed to include all the characters available in all the languages/scripts of the world. This character set does get revised to include newer characters being added in various regions, as well as identification of some languages/scripts&lt;br&gt;
which were not included in the earlier version. This character set has been designed to use numeric values from &lt;code&gt;0&lt;/code&gt; — &lt;code&gt;0x10FFFF&lt;/code&gt;Hex. This character set is also an extension of ASCII, so the initial values from 0 to 127 are same as ASCII. Most of the Indian scripts have been provided a block of 128 characters each, starting from &lt;code&gt;0x0900&lt;/code&gt; onwards. The blocks for the Central and South East Asian Scripts in Unicode are summarized in Tables below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8838917481582975474&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=3454249047781346662&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5898163813554735257&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;What is the size of &lt;code&gt;char&lt;/code&gt; in C? The size of &lt;code&gt;char&lt;/code&gt; is usually 1 byte in &lt;code&gt;C&lt;/code&gt;. The size of &lt;code&gt;char&lt;/code&gt; in Java is 2 bytes. It is an unsigned integral, 16-bit value, used for representing UTF-16 code-units.&lt;/p&gt;
&lt;p&gt;Why is the size of a &lt;code&gt;char&lt;/code&gt;, 2 bytes in Java? In &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;char&lt;/code&gt; represents a character from the platform’s local character set, which in most cases, is some extension of ASCII. The number of characters in most of these character sets is normally upto 256, so they require only 1 byte. In the case of Java, the &lt;code&gt;char&lt;/code&gt; type is used to represent characters from the Unicode character set using the UTF-16 encoding, which requires 16 bits. The details of the UTF encodings will be given in another article.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Unicode&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let us understand Unicode. Unicode is a character set that has characters from all the languages of the world. There are various versions of the Unicode character set. At the time of writing this tutorial, the version of Unicode was 16. The Unicode standard maps characters from all the languages to a unique codepoint value. The codepoint values can be in the range of &lt;code&gt;0&lt;/code&gt; —&lt;code&gt;10FFFF&lt;/code&gt; (Hex). This codepoint range has been divided into 17 planes, each of 65536 values, i.e. 2^16 . The zeroeth plane, i.e. values from &lt;code&gt;0&lt;/code&gt; —&lt;code&gt;FFFF&lt;/code&gt;(Hex) is known as BMP (Basic Multilingual Plane), and other planes define the supplementary characters. To represent the complete range of characters using only 16-bit units, the Unicode standard defines an encoding called UTF-16. In this encoding, supplementary characters are represented as a pair of 16-bit code units, the first code unit from the high-surrogates range (&lt;code&gt;D800&lt;/code&gt; – &lt;code&gt;DBFF&lt;/code&gt;(Hex)), and the second code unit from the low-surrogates range (&lt;code&gt;DC00&lt;/code&gt; – &lt;code&gt;DFFF&lt;/code&gt;(Hex)). In Unicode standard, the range of codepoint values from &lt;code&gt;D800&lt;/code&gt; to &lt;code&gt;DFFF&lt;/code&gt; (Hex) has not been assigned to any valid character and is reserved for surrogates. For characters in the range of &lt;code&gt;0000&lt;/code&gt; —&lt;code&gt;FFFF&lt;/code&gt;(Hex), the values of codepoints and UTF-16 code units are the same. The Java programming language represents text in sequences of 16-bit code units using the UTF-16 encoding. The &lt;code&gt;char&lt;/code&gt; type in the Java programming language represents the 16-bit code unit.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Unicode Escapes in Java Source Code&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Java source code is a sequence of Unicode characters. The Java source code can contain characters from any language and not just characters from the ASCII character set. Most of the time the source code is encoded in some native character set, which is an extension of ASCII. Even in these cases the Java source code can include characters that are not part of the native character set. This is done by using the Unicode escape. In the source code we can specify any UTF-16 code unit by specifying the value as \u followed by four hexadecimal digits.&lt;/p&gt;
&lt;p&gt;Tutorial to be continued for discusing literals and reference data types in Java.&lt;/p&gt;
</description>
<category>Java Basics</category>
<guid isPermaLink="true">https://coderlegion.com/5353/data-types-in-java-part-1-the-primitive-types-in-detail</guid>
<pubDate>Mon, 15 Sep 2025 20:00:25 +0000</pubDate>
</item>
<item>
<title>About Identifiers in Java</title>
<link>https://coderlegion.com/5119/about-identifiers-in-java</link>
<description>&lt;p&gt;&lt;strong&gt;Identifiers in Java&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the Java source code, we define/declare several entities. These entities are identifiable by some names. Identifiers are used to give names to the entities. These identifiers are used for naming the class(es), interface(s), enum(s), annotation(s), record(s). They are also used for giving names to various kinds of members of the class, interface, enum, annotation and record. The members which may be given names are the fields, methods. The identifiers are also used for giving names to variables. The identifiers are also used for giving names to the type parameters of a Generic class or method. The identifiers are also used for giving names to packages and modules. In case of package and module the names can use multiple identifiers separated by a '.' character.  Identifiers are also used for giving names to labels within a method or a block. These labels are target of the break and the continue statements. &lt;/p&gt;
&lt;p&gt;What are the rules for defining an identifier in Java? In Java, an identifier may contain any number of “Java letters” or “Java digits”, and it can start only with a “Java letter”. The sequence of “Java letters” in an identifier cannot match any of the keywords of the Java language or the boolean literals &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt; or the literal &lt;code&gt;null&lt;/code&gt;. A “Java letter” is not just the letters &lt;code&gt;A — Z&lt;/code&gt; and &lt;code&gt;a — z&lt;/code&gt; from the ASCII character set, but it also includes the letters from other languages available from the Unicode character set. The “Java letter” also includes the connecting punctuation characters like the ‘&lt;code&gt;_&lt;/code&gt;’ character, currency symbols like the ‘&lt;code&gt;$&lt;/code&gt;’, ‘&lt;code&gt;₹&lt;/code&gt;’, ‘&lt;code&gt;€&lt;/code&gt;’, ‘&lt;code&gt;£&lt;/code&gt;’, ‘&lt;code&gt;¥&lt;/code&gt;’, etc. sign of a numeric letter like the roman numeral ‘&lt;code&gt;X&lt;/code&gt;’. The “Java letter or digit” also includes the digits used in the various languages available in the Unicode character set and not just the digits &lt;code&gt;0 — 9&lt;/code&gt; from the ASCII character set. It also includes the combining marks and the non-spacing marks, which may be used for combining characters, The following declaration shows a valid declaration of a Java identifier:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;char अ = 'अ';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here &lt;code&gt;अ&lt;/code&gt; has been used as an identifier; since it is a letter in Hindi, this declaration is valid. But then how do we use these characters in a Java source file, which is created using a text editor, where only the ASCII characters may be available? In a Java source file before the compiler identifies the lines and the tokens, it looks for Unicode escapes in the Java source file. The Java compiler works on Unicode characters. Our Java source file is normally encoded in ASCII or some extension of ASCII. While decoding from ASCII to Unicode, the compiler would first replace the Unicode escapes in the Java file with the actual Unicode character value. Using the Unicode escape we can write the above declaration in a Java source file encoded in ASCII as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;char \u0905 = '\u0905'; // 0905 is the hex value for hindi letter A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unicode escape is written as &lt;code&gt;\u&lt;/code&gt; followed by four hexadecimal digits, where the hexadecimal digits are the codepoint values for that character in the Unicode character set. The following code segment would not compile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;char ch = '\u000A'; // 000A is value for line feed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;since this will be seen by the Java compiler as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;char ch = '
';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead &lt;code&gt;char ch ='\n';&lt;/code&gt; should be used to have character literal for newline. Continuing with examples of valid and invalid identifiers for Java, the declaration&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String नमस्ते = ”नमस्ते ”;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is valid in Java since it is only made up of Letters, but&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String ९नमस्ते = ”नमस्ते ”;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not valid since it starts with a digit(&lt;code&gt;९&lt;/code&gt; is Devanagari digit nine). But &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String ₹९नमस्ते = ”नमस्ते ”;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;would become valid since it now does not start with a digit, but a currency symbol.&lt;/p&gt;
&lt;p&gt;Let us now define a class called &lt;code&gt;नमस्तेदुनिया&lt;/code&gt; with a &lt;code&gt;main&lt;/code&gt; method similar to the &lt;code&gt;main&lt;/code&gt; method of the class &lt;code&gt;HelloWorld&lt;/code&gt;, let use parameter name as &lt;code&gt;आर्ग&lt;/code&gt; instead of &lt;code&gt;args&lt;/code&gt;, and it should print “&lt;code&gt;नमस्तेदुनिया&lt;/code&gt;” on the standard output instead of “&lt;code&gt;Hello world&lt;/code&gt;”. This can be done as given below:&lt;/p&gt;
&lt;p&gt;Listing of class &lt;code&gt;नमस्तेदुनिया&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class नमस्तेदुनिया {
    public static void main(String[] आर्ग) {
        System.out.println(”नमस्तेदुनिया” ) ;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is not a good idea to use non-ASCII text in the Java source code directly, since the interpretation of non-ASCII text would largely depend on the encoding used by the native OS. So, if we want to include non-ASCII characters in the Java source file, we may be better off using the Unicode escapes for all such characters. This may seem to be a tedious task. This can easily be taken care of by the &lt;code&gt;native2ascii&lt;/code&gt; utility, which is part of JDK. This utility can convert any text file encoded using any of the standard encoding to ASCII by applying the Unicode escapes for all the non-ASCII characters, and can also be reversed back to native encoding using the same utility. e.g. if the code in the above Listing is saved in a file named &lt;code&gt;HelloWorldHindi.java&lt;/code&gt;. And saved using the &lt;code&gt;utf-8&lt;/code&gt; encoding, then this Java source file can be converted to ASCII only, by using the command as given below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;native2ascii -encoding utf-8 HelloWorldHindi.java HelloWorldHindi.java
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file can be converted back to &lt;code&gt;utf-8&lt;/code&gt; encoded format with the help of command as given below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;native2ascii -encoding utf-8 -reverse HelloWorldHindi.java HelloWorldHindi.java
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: Since Java 8, Java assumes that the default source files are encoded in utf-8. The native2ascii utility has been removed from JDK 9 onwards. If still want to use the utility then we can install JDK8 and use it from the bin folder of JDK8 installation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Identifier ignorable characters&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are also some non-printable characters (most of these are control-characters), which are ignored in an identifier by the Java compiler. These characters are known as Java-Identifier-Ignorable. i.e. if any such character is used in an identifier it will be ignored, so it is possible to have two different sequence of Unicode characters in an identifier, which mean the same. e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TestIdentifierIgnorable {
    public static void main(String[] args) {
        String str = ”Hello world!” ;
        System.out.println(s\u0001tr) ; // \u0001 is Java Identifier Ignorable character
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above code listing, the variable name &lt;code&gt;str&lt;/code&gt; in line 3 is same as &lt;code&gt;s\u0001tr&lt;/code&gt; used in line 4. So, the above code is legal, compiles successfully and when run, would print “Hello world!” on the standard output.&lt;/p&gt;
&lt;p&gt;The following are the numeric value in hex for the Java Identifier Ignorable characters.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0, 1, 2, 3, 4, 5, 6, 7, 8, e, f, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1a, 1b, 7f, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 8a, 8b, 8c,42, 8d, 8e, 8f, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 9a, 9b, 9c, 9d, 9e, 9f, ad

600, 601, 602, 603, 604, 605, 61c, 6dd, 70f, 8e2, 180e, 200b, 200c, 200d, 200e, 200f, 202a, 202b, 202c, 202d, 202e, 2060, 2061, 2062, 2063, 2064, 2066, 2067, 2068, 2069, 206a, 206b, 206c, 206d, 206e, 206f, feff, fff9, fffa, fffb

110bd, 110cd, 13430, 13431, 13432, 13433, 13434, 13435, 13436, 13437, 13438, 1bca0, 1bca1, 1bca2, 1bca3, 1d173, 1d174, 1d175, 1d176, 1d177, 1d178, 1d179, 1d17a, e0001, e0020, e0021, e0022, e0023, e0024, e0025, e0026, e0027, e0028, e0029, e002a, e002b, e002c, e002d, e002e, e002f, e0030, e0031, e0032, e0033, e0034, e0035, e0036, e0037, e0038, e0039, e003a, e003b, e003c, e003d, e003e, e003f, e0040, e0041, e0042, e0043, e0044, e0045, e0046, e0047, e0048, e0049, e004a, e004b, e004c, e004d, e004e, e004f, e0050, e0051, e0052, e0053, e0054, e0055, e0056, e0057, e0058, e0059, e005a, e005b, e005c, e005d, e005e, e005f, e0060, e0061, e0062, e0063, e0064, e0065, e0066, e0067, e0068, e0069, e006a, e006b, e006c, e006d, e006e, e006f, e0070, e0071, e0072, e0073, e0074, e0075, e0076, e0077, e0078, e0079, e007a, e007b, e007c, e007d, e007e, e007f.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: In order to use any of the values above Hex &lt;code&gt;FFFF(&lt;/code&gt;supplementary characters), it will have to be encoded using the &lt;code&gt;UTF-16&lt;/code&gt; encoding, which will require two &lt;code&gt;char&lt;/code&gt; values. e.g. the value Hex &lt;code&gt;110BD&lt;/code&gt; will be encoded as two 16-bit values, which will be, Hex &lt;code&gt;D804&lt;/code&gt; and &lt;code&gt;DCBD&lt;/code&gt;(These pair of values is known as surrogate pair, first value is high-surrogate and the second value is the low-surrogate). So in any identifier having a pair of character &lt;code&gt;\uD804\uDCBD&lt;/code&gt; will be ignored by the compiler.&lt;/p&gt;
&lt;p&gt;i.e. the identifier &lt;code&gt;str&lt;/code&gt; is also equivalent to &lt;code&gt;s\uD804\uDCBDtr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Misconception Note:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Misconception&lt;/strong&gt; - Letters used for forming an identifier are only from the ASCII character set i.e. A – Z and a – z.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fact&lt;/strong&gt; - Java uses Unicode character set, and so identifiers can use letters from any of the languages available in Unicode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Misconception&lt;/strong&gt; - Digits used for forming an identifier are only from the ASCII character set i.e. 0 – 9.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fact&lt;/strong&gt; - Java uses Unicode character set, and so identifiers can use digits from any of the languages available in Unicode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Misconception&lt;/strong&gt; - $ is the only currency symbol available for use in an identifier.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fact&lt;/strong&gt; In Java, identifier can use any currency symbol available in the Unicode character set.&lt;/p&gt;
</description>
<category>Java Basics</category>
<guid isPermaLink="true">https://coderlegion.com/5119/about-identifiers-in-java</guid>
<pubDate>Mon, 01 Sep 2025 12:01:47 +0000</pubDate>
</item>
</channel>
</rss>