Skip to content

Localise the app (Simplified Chinese), and fix what was stopping a cold-cache IPA build - #49

Closed
Vincent-520 wants to merge 7 commits into
Leviidev:mainfrom
Vincent-520:i18n-zh-hans
Closed

Vincent-520 wants to merge 7 commits into
Leviidev:mainfrom
Vincent-520:i18n-zh-hans

Conversation

@Vincent-520

Copy link
Copy Markdown

Five commits: the feature the branch is named for, and the four fixes this branch
needed before it would build at all. The build fixes are independent of the i18n
and can be taken on their own if you would rather split them.

1. Localise the app, and add Simplified Chinese (992b44b)

Every user-facing string now goes through a String Catalog.

  • Localizable.xcstrings — 511 keys, en as the source language so an
    untranslated key falls back to itself, all 511 translated to zh-Hans.
  • InfoPlist.xcstrings — the local-network prompt. Its English is spelled out
    in the catalogue as well, because its key is an Info.plist key rather than a
    sentence: without that entry the compiled en.lproj maps the key to itself
    and the prompt reads NSLocalNetworkUsageDescription.
  • CFBundleLocalizations in Info.plist, and a Settings › Language screen that
    writes AppleLanguages so a language can be pinned. The screen says when the
    choice needs a relaunch to be seen, and says nothing when it does not.
  • Localization.swift adds localizedKey(_:) and localizedString(_:); the
    shared components in Theme.swift now take LocalizedStringKey.

Most of the diff is the 265 places SwiftUI does not localise on its own —
anything the compiler sees as a String: a ternary, a concatenation, a value
out of a computed property, a parameter typed String. A sentence with a value
in the middle is written as one literal with an interpolation (Version %@),
never as "Version " + x, which would put the untranslated half inside the key.

Deliberately left alone: log lines, shell commands, paths, and the names of
things Android reports (app labels, ABIs, engine names). Those are evidence, not
sentences.

2. Four fixes that were stopping the IPA build

bb8d4ea added embedded MoltenVK and build-ipa.yml was not updated with it, so
every cold-cache run has failed since. Upstream's last green run predates that
commit, and its cache carries a build/ios-arm64/lib from before the dependency
existed — which is why this only shows on a fresh cache.

  • 5071977 Build MoltenVK, in the layer the cache covers, with its script in
    that cache's key. The key goes to qemu-v2 because a v1 lib/ holds no
    MoltenVK and must not be restored.
  • 8c0692d Install qemu-img — project.yml's pre-build step creates the
    firmware and userdata seeds with it and a fresh runner has none — and build
    libhusk_rppairing
    , which the app links (-lhusk_rppairing) and which
    nothing staged.
  • 194c212 check_dualmap called sys_icache_invalidate directly instead of
    the portable flush_icache() the file already defines a few hundred lines
    above. The block is under #if defined(__aarch64__), which is true for the
    aarch64-linux-gnu build in tests/translation-layer/run.sh, so the test job
    died on an implicit declaration.
  • acc2a23 With that compiling, the aarch64 test link stopped on
    tl_find_stikdebug_prewarmed, which check_dualmap calls and which lives in
    husk-tl-load.c. That file is the Apple-side loader — it includes
    mach/mach_time.h unguarded — so the host tests deliberately do not link it.
    Stubbed to return NULL, which is what the real lookup answers on a machine with
    no StikDebug, so the dualmap check still reports skip for the honest reason;
    the harness asserts exactly that. The generated stub file is renamed
    noscan.c → nostubs.c now that it holds two unrelated stubs.

Verification

CI is green on this branch, both jobs — run
37600731806,
including a from-scratch QEMU + MoltenVK + libhusk_rppairing layer.

The IPA that run produced carries zh-Hans.lproj/Localizable.strings with all
511 entries, zh-Hans.lproj/InfoPlist.strings, and
CFBundleLocalizations = [en, zh-Hans], with every other bundle check
scripts/package_ipa.sh makes passing.

Vincent-520 and others added 7 commits October 6, 2026 23:52
Every user-facing string now goes through a String Catalog, and Chinese is the
first translation beside the English source.

Localizable.xcstrings holds all 511 keys, with en as the source language so an
untranslated key falls back to itself; InfoPlist.xcstrings holds the one line
Info.plist shows, which needs its English spelled out because the key is an
Info.plist key rather than a sentence. Info.plist gains CFBundleLocalizations,
and Settings gains a Language screen that writes AppleLanguages and says when a
relaunch is needed to see the choice.

The work is mostly the places SwiftUI does not localise on its own. `Text("a
literal")` is already a key, but anything the compiler sees as a String is not:
a ternary, a concatenation, a value out of a computed property, a parameter
typed String. Localization.swift adds the two helpers those need, and the shared
components in Theme.swift now take LocalizedStringKey. A sentence with a value in
the middle is one literal with an interpolation -- `Version %@`, not `"Version "
+ x`, which would put the untranslated half inside the key.

Log lines, shell commands, paths and the names of things Android reports (app
labels, ABIs, engine names) are deliberately left alone: they are evidence, not
sentences.
bb8d4ea added embedded MoltenVK for Unreal Engine games' Vulkan renderer and
updated project.yml, but not this workflow. build/ios-arm64/lib/MoltenVK.xcframework
is therefore missing from a cold build, and the app target stops before it
compiles: "There is no XCFramework found at .../MoltenVK.xcframework".

Upstream is red for the same reason and has been since that commit; its last
green run predates it, and its cache carries a lib/ built when the dependency
did not exist yet.

So: build it, before the cache that covers build/ios-arm64/lib is saved, and put
its script in that cache's key so a version bump rebuilds the layer. The qemu key
goes to v2 because a v1 cache holds a lib/ with no MoltenVK in it and must not be
restored.
Two more things the app target needs that this workflow never staged, both of
which only showed once MoltenVK stopped stopping the build early:

  * project.yml's pre-build step creates the firmware and userdata seeds with
    qemu-img, which a fresh runner does not have. It is a Homebrew install
    alongside meson and ninja.
  * the app links -lhusk_rppairing for Built-in StikJIT's on-device pairing, and
    nothing built it. It stages into build/ios-arm64/lib, the path the qemu
    cache covers, so it is built before that cache is saved and both its script
    and Cargo.lock are part of that cache's key.
The file already has a flush_icache() that picks sys_icache_invalidate on Apple
and __builtin___clear_cache elsewhere, but check_dualmap called the Apple one
directly. It sits under `#if defined(__aarch64__)`, which is true for the
aarch64-linux-gnu build tests/translation-layer/run.sh does, so the Linux job
died on an implicit declaration of sys_icache_invalidate.
With the icache call fixed, the aarch64 link got further and stopped on
tl_find_stikdebug_prewarmed, which check_dualmap calls and which lives in
husk-tl-load.c. That file is the Apple-side loader -- it includes
mach/mach_time.h unguarded -- so the host tests deliberately do not link it, and
the aarch64 link line needs a stub the way the scanner already has one.

Returning NULL is what the real lookup answers on a machine with no StikDebug,
so the dualmap check still reports "skip" for the honest reason; the harness
asserts exactly that. The generated stub file is renamed noscan.c -> nostubs.c
now that it holds two unrelated stubs.
@Leviidev

Leviidev commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Heavy conflicts, I prefer to add this feature myself in 1.0.0

@Leviidev Leviidev closed this Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants