Your Chinese looks perfect here.
On a player's PC it's a row of boxes.

The translation is not the problem. tr("MENU_START") returns 开始, the table is loaded, the locale is right. What is missing is a glyph — and Godot quietly went and found one on your computer, in a font file that is not inside your export. Paste your translation CSV below — or, if you have no translation at all, a scene or a script straight out of your project — and this lists every character that falls outside the set the engine actually guarantees. A project written entirely in English is already in this the day someone types Continue → into a Button. It runs the LocGuard scanner in your browser; nothing is uploaded.

    What the engine actually guarantees

    Godot's built-in UI font is Open Sans SemiBold, and it is a subset. Asking the engine itself — ThemeDB.fallback_font.has_char(cp) for every codepoint in the BMP, the emoji planes and CJK Extension B — gives an exact number: 1010 codepoints, in 92 ranges (G1, Y1). That enumeration is the table this page scans against; it is generated by the verification script, not typed by hand.

    scriptin the built-in font?claim
    printable ASCII 0x20–0x7Eyes, all of itG2
    Latin-1 accents — é ã ç (fr/pt/es)yesG3
    Latin Extended-A — ł š ğ (pl/cs/tr)yesG4
    Vietnamese — ạyesG5
    Cyrillic — ЖyesG6
    Greek — αyesG7
    Hebrew — אyesG8
    Arabic — بnoG9
    Chinese — 你 我noG10
    Japanese kana — あ アnoG11
    Korean — 가noG12
    Thai — กnoG13
    Devanagari — कnoG14
    emoji — 😀noG15
    arrows — ← ↑ → ↓noG16
    — … • “ ” ’ €yesG17
    ✓ ★ ♥ ♪noG18

    Two of those rows are the reason this page exists. This is not a "non-Latin languages" problem. A project with no translation at all, whose entire UI is English, ships a hex box the moment a designer writes Continue → Next or OK ✓ (G16, G18). And the em dash, the ellipsis, the curly quotes and the euro sign are in the font (G17) — so "avoid non-ASCII" is not the rule, and guessing does not work. That is why the set is measured.

    Then why does it look fine on your machine?

    Because Godot borrows. Shaping 你好 with the built-in font still produces two real glyphs (S1) — and they carry a font_rid that is not the built-in font's (S2). OS.get_system_font_path_for_text() names where they came from (S3):

    OS.get_system_font_path_for_text("Sans", "你好")
    ["/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
     "/usr/share/fonts/opentype/unifont/unifont.otf"]

    An absolute OS path, not a res:// path. Same for an emoji in a UI string (Y2). None of it travels inside your .pck. Your development machine — a full desktop with a font package installed — is the single worst place to test whether your player can read your game.

    What the player without that font sees

    FontFile.allow_system_fallback = false reproduces it, and it is the closest thing to a player's machine you can run in CI:

    The marker is not glyph index 0

    If you go looking for this in code, do not test for a zero glyph index — you will find nothing. Godot marks an undrawable codepoint by putting the codepoint itself in index and leaving font_rid invalid (S4b):

    你好 | index=20320 (U+4F60)  font_rid=RID(0)  valid=false

    That pair is the hex box on screen, and it is the only reliable way to detect this from inside the running game.

    Why nothing warns you

    The fix, and the shape of it

    var body := FontFile.new()
    body.load_dynamic_font("res://fonts/NotoSansSC-Regular.ttf")
    
    var ui := ThemeDB.fallback_font.duplicate()
    ui.allow_system_fallback = false   # fail loudly here, not on a player's PC
    ui.fallbacks = [body]

    Measured: with system fallback off, a Font in fallbacks takes the arrow's undrawable glyph to zero (S10). Measured just as carefully: the same fallback does not fix Chinese, because DejaVu Sans has no CJK either (S11). A fallback only covers what it actually owns — so "add a fallback font" is not advice until you know which codepoints you are shipping. Which is what the tool at the top answers.

    This is deliberately not a linter rule

    Every rule in LocGuard answers "is this translation table complete and consistent", and is fixed by editing the table. This one answers "can the engine draw it", and the fix is a font file. Different question, different artifact — and a check that would be wrong to fail a translator's build over. LocGuard Pro is the Godot 4 editor dock and CLI that catch the table-side failures — missing keys, empty translations, placeholder drift, unbalanced BBCode — before your players do: blobsmith.itch.io/locguard. The free CLI is locguard-lite.

    Reproducing all 35 claims

    git clone https://github.com/leobaray/locguard
    docs/verify_font_glyphs.sh /path/to/Godot_v4.7-stable_linux.x86_64 dump.json

    Every claim id on this page is printed by that script. The optional second argument re-dumps the coverage ranges as JSON — that file is the source of the table this page scans against, so a Godot upgrade is a re-run and a diff, not a rewrite. Last run: 35 passed / 0 failed against 4.7.stable.official.5b4e0cb0f. The full write-up is docs/font-glyphs-missing-in-translations.md, and the scanner this page runs is docs/glyph-scan-core.js — byte for byte the file the command line uses.

    Sweeping a whole project, not one paste

    node docs/scan_project_glyphs.js /path/to/your/godot/project
    node docs/scan_project_glyphs.js scenes/hud.tscn --json   # exit 1 on findings
    node docs/scan_project_glyphs.js /path/to/project --all   # sweep .csv tables too

    Same table, same matching, every file at once — zero dependencies, reads only, writes nothing. It is deliberately narrow: a res:// path, a node path, a comment and a print() are skipped even when they hold the same character, because a report with noise in it is a report nobody acts on.

    What it finds in Godot’s own demo projects

    The claim above is easier to believe against code nobody here wrote, so that is where it was measured: godotengine/godot-demo-projects pinned at 34fc995 — 1028 scene and script files, 1806 strings a player can see. Five files come back, and honesty about what they are is the point of showing them — none of the five is the accidental Continue → this page opens with. Two are worth reading closely:

    loading/runtime_save_load/runtime_save_load.tscn  (scene, 10 strings)
        153:  U+2194  "↔"       Arrows ×1  in text
             "abcdefghijklmnopqrstuvwxyz\nABCDEFGHIJKLM\nNOPQRSTUVWXYZ\n12..."
      → NOT in the built-in font, even though the text around it is ASCII
    
    2d/physics_platformer/tileset_edit.tscn  (scene, 1 string)
        152:  U+0010  "\u0010"  Control character ×14  in text
             "This scene serves as a tool for editing the tileset.\n\u0010Nod..."
      → not a glyph at all - a stray control byte in the string; delete it

    Neither is what you would guess. The first is a hidden font-sampler Label — alphabet, digits, ()[]{}<>, éàç, ×÷±≠ø and then ↔ — so the character is deliberate; what is accidental is that it sits on the last line of a multi-line property, five lines below the property name, where the first version of this scanner never looked. That one file is why it reads whole values instead of lines. The second is not a font problem at all: fourteen U+0010 control bytes inside a Label, which is a typo you delete, not a font you bundle — so the scanner says that instead of filing it under “bundle a font”.

    The other three are the expected kind, and still worth naming: gui/bidi_and_font_features/bidi.tscn and gui/ui_mirroring/ui_mirroring.tscn carry Thai and Arabic, and 3d/labels_and_texts/3d_labels_and_texts.tscn uses U+E800–E80B — Private Use Area, i.e. an icon font, which is fine if that font is in the export and set on that control, and nothing at all otherwise. Total: 81 character occurrences across 5 files. So: in 1806 strings written by other people, this scanner found one real typo, one icon font, two correctly-translated scenes and one font sampler — and zero false alarms. The Continue → case is not rare because it is unlikely; it is absent here because these are engine demos, not shipped UI.

    The number that matters more is the other one, 1023 files reported as silent. Every uncovered codepoint in all of them was checked again at the byte level, ignoring what the extractor chose to read, and exactly one survives: misc/os_test/os_test.gd passing 你好 to OS.get_system_font_path_for_text() — an argument to the font-probing API, not a string on screen. Correct silence. Both halves of that run are asserted by test/project-glyph.test.js, and re-running it is three commands, printed at the top of that file.

    Measured the same way

    This scanner runs in your terminal too — it and eight others are one MIT zip with no dependencies: nine scanners for a Godot 4 project.