upload --pre-check/--evidence, shots copy translate, game-center --locale-map, play games API-disabled hint
This commit is contained in:
parent
29a9683ab7
commit
8ffa27d4f8
24 changed files with 598 additions and 27 deletions
|
|
@ -65,6 +65,8 @@ export interface GamesConfig {
|
|||
gameCenter?: boolean;
|
||||
/** Play Games Services: the games project id (the digits of games_APP_ID / com.google.android.gms.games.APP_ID). */
|
||||
playGames?: { appId?: string };
|
||||
/** Game Center only: config language id (or its apple locale) -> the locale the live Game Center uses, e.g. { en: 'en-GB' } when the app's English is en-GB there but the language says en-US. `storebound apple game-center --locale-map en=en-GB` overrides. */
|
||||
localeMap?: Record<string, string>;
|
||||
leaderboards?: GameLeaderboard[];
|
||||
achievements?: GameAchievement[];
|
||||
}
|
||||
|
|
@ -78,6 +80,10 @@ export function validateGames(g: GamesConfig | undefined, languages: Language[]
|
|||
if (g.gameCenter !== undefined && typeof g.gameCenter !== 'boolean') e.push('games.gameCenter: true | false');
|
||||
if (g.playGames !== undefined && (typeof g.playGames !== 'object' || (g.playGames.appId !== undefined && !/^\d{6,20}$/.test(String(g.playGames.appId)))))
|
||||
e.push('games.playGames.appId: the digits of the Play Games Services project id (games_APP_ID)');
|
||||
if (g.localeMap !== undefined) {
|
||||
if (!g.localeMap || typeof g.localeMap !== 'object' || Array.isArray(g.localeMap)) e.push('games.localeMap: an object { en: "en-GB" } (config language id or locale -> Game Center locale)');
|
||||
else for (const [k, v] of Object.entries(g.localeMap)) if (typeof v !== 'string' || !/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(v)) e.push(`games.localeMap.${k}: "${String(v)}" is not a locale like en-GB`);
|
||||
}
|
||||
const langs = new Set(languages.map(l => l.id));
|
||||
const seen = new Set<string>();
|
||||
const checkId = (kind: string, id: unknown) => {
|
||||
|
|
@ -133,7 +139,41 @@ export function changedFields(have: Record<string, unknown> | undefined, want: R
|
|||
return Object.keys(want).filter(k => JSON.stringify(have?.[k] ?? null) !== JSON.stringify(want[k] ?? null));
|
||||
}
|
||||
|
||||
const appleLocale = (languages: Language[], id: string): string | undefined => languages.find(l => l.id === id)?.apple;
|
||||
const appleLocale = (languages: Language[], id: string, map: Record<string, string> = {}): string | undefined => {
|
||||
const apple = languages.find(l => l.id === id)?.apple;
|
||||
return map[id] ?? (apple ? map[apple] : undefined) ?? apple;
|
||||
};
|
||||
|
||||
/** The effective Game Center locale map: games.localeMap, then --locale-map en=en-GB,tr=tr (flag entries win). */
|
||||
export function gcLocaleMap(g: GamesConfig | undefined, flag: string[] = []): Record<string, string> {
|
||||
const out: Record<string, string> = { ...(g?.localeMap ?? {}) };
|
||||
for (const item of flag.flatMap(x => x.split(',')).map(x => x.trim()).filter(Boolean)) {
|
||||
const m = /^([A-Za-z0-9_-]+)=([A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*)$/.exec(item);
|
||||
if (!m) throw new Error(`--locale-map "${item}": use <config language id or locale>=<Game Center locale>, e.g. en=en-GB (several: en=en-GB,pt=pt-PT)`);
|
||||
out[m[1]!] = m[2]!;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** A suggestion per config language whose Game Center locale is not on the live Game Center while exactly one other
|
||||
* variant of the same language is (en-US configured, only en-GB live): the map entry that stops the spurious creates. */
|
||||
export function suggestLocaleMap(languages: Language[], liveLocales: string[], map: Record<string, string> = {}): { id: string; from: string; to: string }[] {
|
||||
const live = [...new Set(liveLocales)];
|
||||
if (!live.length) return [];
|
||||
const out: { id: string; from: string; to: string }[] = [];
|
||||
for (const l of languages) {
|
||||
const from = appleLocale(languages, l.id, map);
|
||||
if (!from || live.includes(from) || map[l.id] || map[l.apple ?? '']) continue;
|
||||
const base = from.split('-')[0]!.toLowerCase();
|
||||
const same = live.filter(x => x.split('-')[0]!.toLowerCase() === base);
|
||||
if (same.length === 1) out.push({ id: l.id, from, to: same[0]! });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The live locales of a Game Center read (every leaderboard and achievement localization). */
|
||||
export const liveGcLocales = (have: { leaderboards: GcExisting[]; achievements: GcExisting[] }): string[] =>
|
||||
[...have.leaderboards, ...have.achievements].flatMap(x => x.localizations.map(l => String(l.attributes.locale ?? '')).filter(Boolean));
|
||||
|
||||
export function gcLeaderboardAttrs(l: GameLeaderboard): Record<string, unknown> {
|
||||
return {
|
||||
|
|
@ -151,7 +191,7 @@ export interface GcExisting { id: string; attributes: Record<string, unknown>; l
|
|||
|
||||
/** Game Center plan: matched by vendorIdentifier; create what is missing, PATCH what differs, keep the rest. Archived
|
||||
* items on the store are left alone (an archived vendor id cannot be reused; the plan names it). */
|
||||
export function planGameCenter(g: GamesConfig, languages: Language[], have: { leaderboards: GcExisting[]; achievements: GcExisting[] }): ItemPlan[] {
|
||||
export function planGameCenter(g: GamesConfig, languages: Language[], have: { leaderboards: GcExisting[]; achievements: GcExisting[] }, localeMap: Record<string, string> = {}): ItemPlan[] {
|
||||
const out: ItemPlan[] = [];
|
||||
const one = (kind: ItemPlan['kind'], id: string, attrs: Record<string, unknown>, locs: Record<string, Record<string, unknown>>, list: GcExisting[]) => {
|
||||
const cur = list.find(x => x.attributes.vendorIdentifier === id);
|
||||
|
|
@ -166,7 +206,7 @@ export function planGameCenter(g: GamesConfig, languages: Language[], have: { le
|
|||
for (const l of (g.leaderboards ?? []).filter(x => onStore(x, 'apple'))) {
|
||||
const locs: Record<string, Record<string, unknown>> = {};
|
||||
for (const [k, v] of Object.entries(l.locales)) {
|
||||
const loc = appleLocale(languages, k);
|
||||
const loc = appleLocale(languages, k, localeMap);
|
||||
if (loc) locs[loc] = { name: v.name, ...(v.suffix ? { formatterSuffix: v.suffix } : {}) };
|
||||
}
|
||||
one('leaderboard', l.id, gcLeaderboardAttrs(l), locs, have.leaderboards);
|
||||
|
|
@ -174,7 +214,7 @@ export function planGameCenter(g: GamesConfig, languages: Language[], have: { le
|
|||
for (const a of (g.achievements ?? []).filter(x => onStore(x, 'apple'))) {
|
||||
const locs: Record<string, Record<string, unknown>> = {};
|
||||
for (const [k, v] of Object.entries(a.locales)) {
|
||||
const loc = appleLocale(languages, k);
|
||||
const loc = appleLocale(languages, k, localeMap);
|
||||
if (loc) locs[loc] = { name: v.name, beforeEarnedDescription: v.description, afterEarnedDescription: v.earned ?? v.description };
|
||||
}
|
||||
one('achievement', a.id, gcAchievementAttrs(a), locs, have.achievements);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue