Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

5.5 KiB

App Resources

This page is the app-author entry point for resources under apps/<App>/res/. The complete app contract remains in module-contract.md.

Directory Shape

apps/<App>/
  res/
    strings.ts        # required: zh-Hans (default)
    strings.en.ts     # required: English translations (Partial allowed)
    icons.tsx         # required if the app uses any icons
    colors.ts         # optional — only for stable app resources, not one-off Tailwind colors
    dimens.ts         # optional — only when a dimension is reused or pinned to JS pixel math

Use res/ for Android-like app resources: strings, semantic colors, reusable dimensions, and app-owned icon aliases.

In practice every shipped app has strings.ts + strings.en.ts + icons.tsx; the framework supports omitting them if an app truly has no localized text or icons (e.g. AnswerSheet), but treat the trio as the de-facto baseline.

Icons

Rules:

Rule Why
Export icon aliases with an Ic prefix, e.g. IcCard, IcBus, IcNavBack. Keeps data-driven icon names app-local and avoids leaking raw library names.
ICON_REGISTRY keys must exactly match exported Ic* names. Enables deterministic data-driven rendering.
Do not add raw Lucide names to ICON_REGISTRY. Fix the data layer instead of masking invalid data.
Import only icons the app actually uses. Keeps bundles and code review small.
Pass size inline at the call site (<IcCard size={22} />). Don't define icSize* fields in dimens.ts. Icon size is a render-site detail, not a shared dimension.
Use Tailwind text-color classes or CSS variables for icon color (className="text-app-primary", style={{ color: 'var(--app-c-foo)' }}). Hard-coded color="#1677FF" defeats theme overrides and dark-mode.

Usage:

<IcCard size={22} />
<IconRenderer name={item.icon} size={22} />

Data files should store "IcCard", not "CreditCard".

Colors

Use res/colors.ts when a color is a stable app resource:

Good fit Poor fit
Brand colors, gradients, semantic tokens not covered by Tailwind. One-off Tailwind colors such as text-gray-800 or bg-white.
Dark-mode-aware component color maps. Local one-use arbitrary values such as bg-[#FF7D00].

The manifest theme.colors owns Tier-1 shell colors such as primary, background, surface, statusBarForeground, and navigationBarForeground.

CSS variable naming

Both color sources are injected as CSS variables by os/utils/themeToCssVars.ts:

Source CSS variable prefix Example
manifest.theme.colors (Tier-1) --app-<key> primary--app-primary
res/colors.ts (Tier-2 / component colors) --app-c-<key> brand_orange--app-c-brand-orange
res/colors.states.ts (state colors) --app-cs-<key> bg_pressed--app-cs-bg-pressed
res/anim.ts (animation tokens) --app-<key> (same family as Tier-1)

The _-to-- transform is implicit; declare keys in snake_case TS, refer to them as kebab-case in CSS.

Dark mode

res/colors.ts may export colorsDark to override individual keys in dark mode:

export const colors = { brand_orange: '#FF7D00', forest_green: '#2E7D32' };
export const colorsDark: Partial<typeof colors> = {
  forest_green: '#66BB6A',
};

The Partial<typeof colors> type is the contract — only declare the keys that change. The manifest's theme.colorsDark follows the same pattern. Most shipped apps currently export an empty {} to keep the type contract while postponing dark-mode work.

Dimensions

Use res/dimens.ts only for dimensions that are reused or must stay in sync with JavaScript pixel math.

Dimensions are auto-injected as CSS variables (kebab-case + px suffix): item_height: 56 becomes --app-item-height: 56px. The injection happens when the App entry mounts and calls style={{ ...dimensToCssVars(dimens) }}.

For JS pixel calculations, prefer CSS variables or pixel literals:

<div
  className="h-(--app-row-height)"
  style={{ '--app-row-height': '48px' } as React.CSSProperties}
/>

Avoid Tailwind rem sizing such as h-12 when JS calculates scrollTop = index * 48; root font-size and zoom can otherwise create drift.

Strings

Use res/strings.ts for user-visible text. Keep structural IDs and feature config out of strings; those belong in constants.ts or data/defaults.json.

res/strings.en.ts provides English translations and is typed Partial<Record<StringKey, string>> — declare only the keys you've translated.

Apps consume strings through:

import { useAppStrings } from 'os/useAppStrings';
import { strings } from './res/strings';
import { stringsEn } from './res/strings.en';

const s = useAppStrings(strings, stringsEn);
return <button>{s.nav_back}</button>;

(Apps without a React-hooks context may use resolveAppStrings(strings, stringsEn, locale) instead — same contract, no hook.)

Do not scatter Chinese / English literals across JSX (<span>返回</span>). All user-visible text should resolve through the strings map so the locale switch covers it.

displayNameEn in manifest.ts is patched into OS i18n automatically, so new apps should not edit os/i18n/en.ts just to add their app name.