1. A number for a character is different from the bytes used to store it
Suppose you export a Korean document and reopen it, only to find strange characters in the title. The author's language has not changed: the rules used to interpret the stored numbers may have become mismatched. Unicode is a standard that assigns numbers called code points to characters and symbols; UTF-8 is an encoding that represents the corresponding values as sequences of bytes. For example, the Korean syllable ‘가’ has the code point U+AC00. The notation after U+ is hexadecimal, not a position in a file or a memory address. That number does not specify a font shape or a pixel position on the screen. The same code point can be drawn differently by different fonts and text-layout environments. Unicode support, saving as UTF-8, and installing a Korean font are therefore related but separate things to check. The Unicode UTF FAQ provides a starting point for distinguishing character information from its storage format.
In UTF-8, ‘가’ becomes the three hexadecimal bytes EA B0 80. By contrast, the English letter A is U+0041 and becomes the single UTF-8 byte 41. A byte is a unit of eight bits, and characters within one document can require different numbers of bytes. A .txt or .csv filename extension does not identify the actual encoding either. UTF-8 represents valid Unicode scalar values using one to four bytes, so multiplying the number of visible characters by a fixed number cannot give an exact file size. Do not extend this example for a Korean syllable to every Korean-related symbol or every language. Encoding maps character information to bytes; it is neither compression nor encryption. Additional file-format information, line endings, and transfer compression can change the overall size even when the sentence is unchanged. Distinguishing the byte-to-character conversions defined by the WHATWG Encoding Standard from total file size and confidentiality helps narrow down the problem.
2. Bytes, code units, and the characters a reader sees
In JavaScript, a string's length counts UTF-16 code units. It does not count the bytes in a UTF-8 file or the characters perceived by a reader. For example, the grinning face ‘😀’ is one code point, U+1F600, but uses two UTF-16 code units, so '😀'.length is 2. Its UTF-8 representation is the four bytes F0 9F 98 80. The decimal value 128512 returned by codePointAt(0) is the same number as U+1F600, not a character count or a file size. For ‘가’, the respective values are one code point, one UTF-16 code unit, and three UTF-8 bytes. The numbers differ because the units differ. MDN's explanation of string length specifies what length measures. This number alone cannot establish how many bytes a JavaScript engine actually uses to store a string in memory.
Counting individual code points still does not always count the characters a user sees. A base character can have combining marks, and several code points can form one emoji. Unicode's text-segmentation rules explain the boundaries of extended grapheme clusters used to handle such groups. Expanding a string by code point with Array.from(text) does not automatically count these multi-code-point groups. In a browser, after checking support in the intended environment, you can consider using Intl.Segmenter with granularity set to 'grapheme'. A grapheme cluster nevertheless is not guaranteed to correspond one-to-one with a shape drawn by a font, and editing expectations can differ by language. A profile-description field needs a character-count rule users can understand, while storage or communication limits need a separate byte count. Displaying ‘20 characters’ while simply truncating UTF-16 code units can split an emoji or a combining sequence in the middle.
3. Separate storage, delivery, and display when Korean text is garbled
Garbled text can result when the encoding used to write bytes differs from the decoding used to read them. Reading a UTF-8 file as another encoding may display unrelated characters even when the file itself is unchanged. For HTML, check the actual storage format, the charset in the HTTP response's Content-Type, and the meta charset declaration near the beginning of the document together. The HTML attribute lang='ko' identifies the content's language; it has a different role from charset, which identifies how character bytes are decoded. W3C's guide to encoding declarations explains that changing only a declaration to UTF-8 is insufficient: save the actual file as UTF-8 and align the server and document declarations. An encoding signature can also affect detection precedence, so do not assume one meta element always overrides every other source of information. A difference between opening a file locally and receiving it from a server is an important clue about delivery settings.
Square boxes caused by a font lacking the required characters can have a different cause from incorrect decoding. If the character data is correct but a display shape is unavailable, changing the storage encoding will not supply that shape. Conversely, changing fonts cannot restore the original characters after the data has already been interpreted as different characters. Preserve a copy of the affected file, then compare the encoding selected by the editor, the server's declaration, the characters interpreted by the browser, and the fonts used. Do not identify the cause from appearance alone: also examine which code points are obtained when the affected text is copied. Record whether only one browser behaves differently and whether another editor misreads the same file. This helps distinguish display problems from changes to the data. Repeatedly saving until something looks correct can destroy the original bytes. If declarations are consistent but only one post is affected, investigate where its content changed before entering the database.
4. Check the same values with TextEncoder and TextDecoder
Begin a small experiment with a short string you created yourself. TextEncoder converts a JavaScript string into a UTF-8 byte array. Reading the result of new TextEncoder().encode('가') in hexadecimal gives EA B0 80, and the array's length is 3. Developer tools may display the same bytes as the decimal numbers 234, 176, and 128. That is a change of number notation, not different data. Compare it with the ordinary string length of 1 to see the difference in units. Decoding this array with new TextDecoder('utf-8').decode(bytes) returns ‘가’, where bytes refers to the array just created. The encoder produces UTF-8; it is not a general converter that writes an arbitrarily selected older encoding. When investigating a real file, start with the preserved original bytes. Re-encoding a sentence that was already displayed incorrectly as UTF-8 cannot establish what bytes the initial file contained.
Passing 'utf-8' and { fatal: true } to the TextDecoder constructor makes invalid input produce an error rather than quietly replacing it. For example, the single byte FF is not valid UTF-8, so decoding it in this mode fails. In the default replacement mode, invalid portions can produce the replacement character U+FFFD. However, the absence of an error does not prove that you have identified the original encoding or verified the correctness of the content. Byte sequences can overlap between encodings, and text already changed into other characters may have been saved again as valid UTF-8. This check establishes whether the selected decoder accepts the input. It does not automatically verify the document's origin, agreement with the original, or whether the file was modified along the way.
5. Normalization separately aligns representations of the same character
Even two correctly decoded strings can contain different sequences of code points. ‘가’ can be represented by U+AC00 alone or by the combining leading consonant U+1100 followed by the vowel U+1161. The latter pair is different from simply putting the everyday compatibility jamo ‘ㄱ’ and ‘ㅏ’ next to each other, so examine the numbers too. The two canonical representations are normally drawn as the same syllable but can compare unequal as raw JavaScript strings. Applying NFC to both sides, according to Unicode's normalization rules, lets this canonically equivalent example be compared in the same representation. NFD uses canonical decomposition. In this example, the NFC form takes three UTF-8 bytes, while the NFD pair of jamo takes six. Identical-looking text can therefore have different storage sizes. If a search term and a stored name look the same but do not match, investigate representation differences separately from garbling caused by incorrect decoding.
The choice of normalization form also depends on the purpose. NFC handles canonical equivalence, while NFKC includes compatibility decomposition and can change a wider range of deliberately distinct inputs, such as full-width digits or circled numbers. Such conversion can be useful for constructing search-comparison values, but distinctions can matter in mathematical notation or original source materials. Preserve the original and decide which form, if any, to apply to a separate comparison copy. Normalization does not replace case comparison, language-specific sorting, or finding words with the same pronunciation. Bulk conversion of existing filenames or account identifiers can merge distinct items into the same comparison value, so first examine the service's naming rules and collision handling. Treating every visually similar character as the same stored value is not the general goal of normalization.
6. Preserve originals and record the boundaries between conversions
Before overwriting an initial file while narrowing down damage, preserve its original bytes and how it was obtained. Try the expected decoding on a copy, and check that correctly displayed content remains intact when saved again. The optional UTF-8 signature discussed in W3C's BOM guide consists of the initial bytes EF BB BF; those bytes are not mandatory. UTF-8 does not require a choice of byte order, so do not interpret this signature as performing the same byte-order-selection role as in UTF-16. Adding or removing a BOM is also different from converting the body bytes to another encoding. If unreadable values have already been replaced by replacement characters or question marks and saved, several original values may have collapsed into one, and automatic recovery cannot be guaranteed. Consider whether reversal is possible only when the original encoding and conversion history are known. Normalization is not a tool for restoring lost bytes.
Do not finish testing multilingual features with one line of English. Include Korean syllables, Latin letters with combining marks, supplementary-plane emoji, and right-to-left text such as Arabic or Hebrew in the same short test collection. Compare input, storage, retrieval, and export. At each stage, examine appearance, code points, UTF-8 byte counts, and the product's chosen character-count rule. Reading direction is not a property fixed by changing UTF-8 to another encoding; review the interface's language and direction settings separately. When modifying input limits or search comparisons, preserve existing originals and explain the changed rule. Check that the browser's displayed count and the server's acceptance criteria use the same units; if distinct limits are needed, display them separately. The numbers and code examples in this article demonstrate conversions of short example strings, not measurements of an outage at a particular service. Checking character numbers, stored bytes, string units, and display and comparison rules in turn makes changes at each boundary reproducible.