cross-store text: other platforms named in App Store text (2.3.10: Android, Google Play, ...) or Play text (Metadata: iOS, App Store, Game Center, ...) are refused by every text write (apple / play listing, play release / upload notes, listing set, whats-new, storebound edit, desktop editor), blockers in check / validate, scan other-platforms / play-other-platforms; listing.allowPlatformMentions for the owner's functional mentions; whats-new splits [apple] / [play] lines per store; storebound api --yes without a TTY is refused (exit 1, stderr) instead of a silent dry run; pitfalls 77, 78
This commit is contained in:
parent
3e4015b686
commit
edabbec874
32 changed files with 613 additions and 53 deletions
|
|
@ -4,6 +4,10 @@
|
|||
// Pure, no Node imports: the desktop window runs it on every keystroke, `storebound edit` before it writes.
|
||||
import { auditAppleKeywords, auditPlayListing } from './aso';
|
||||
import { APPLE_LIMITS, type AppleCopy, len, PLAY_LIMITS } from './copy';
|
||||
import { fieldsMentions, GUIDELINE, type PlatformAllow, type PlatformHit } from './cross-store';
|
||||
|
||||
const mentionIssue = (h: PlatformHit): FieldIssue => ({ level: 'error', field: h.field,
|
||||
msg: `line ${h.line}: "${h.term}": other platforms or stores in ${h.store === 'apple' ? 'App Store' : 'Google Play'} text get it rejected (${GUIDELINE[h.store]}); ${h.fix}` });
|
||||
|
||||
export type IssueLevel = 'error' | 'warn' | 'info';
|
||||
export interface FieldIssue { level: IssueLevel; field: string; msg: string }
|
||||
|
|
@ -12,8 +16,6 @@ export interface FieldIssue { level: IssueLevel; field: string; msg: string }
|
|||
export const COMPETITORS = ['instagram', 'tiktok', 'whatsapp', 'facebook', 'snapchat', 'youtube', 'netflix', 'spotify', 'telegram', 'messenger', 'twitter', 'pinterest', 'uber', 'airbnb',
|
||||
'amazon', 'chatgpt', 'openai', 'minecraft', 'roblox', 'fortnite', 'pubg', 'candy crush', 'clash of clans', 'pokemon', 'pokémon', 'among us', 'subway surfers', 'temple run',
|
||||
'angry birds', 'tetris', 'wordle', 'duolingo', 'zoom', 'gmail', 'google maps', 'waze', 'shazam', 'tinder', 'notion', 'evernote', 'photoshop', 'canva', 'capcut', 'iphone', 'ipad', 'apple'];
|
||||
/** Other mobile platforms or stores in App Store text (2.3.10). */
|
||||
export const OTHER_PLATFORMS = /\b(Android|Google Play|Play Store|Galaxy Store|AppGallery|Huawei|Windows Phone|BlackBerry|Amazon Appstore)\b/;
|
||||
// Developer markers count only in capitals ("TODO:", "FIXME"): "todo" is a real word and keyword (a to-do app).
|
||||
export const PLACEHOLDER_MARKERS = /\bTODO\b|\bTBD\b|\bFIXME\b|\bXXX\b/;
|
||||
export const PLACEHOLDER_TEXT = /lorem ipsum|\bplaceholder\b|your app (name|description) here|coming soon|First paragraph:|Short hook under the name|One or two sentences you can change|İsmin altındaki kısa kanca|Yeni sürüm göndermeden istediğin zaman|\bExample (Game|App|Pro|Studio)\b/i;
|
||||
|
|
@ -31,7 +33,7 @@ export const APPLE_REQUIRED = ['subtitle', 'promotionalText', 'keywords', 'descr
|
|||
* (apple.name, else app.name). Errors stop a save (the store would refuse it, or the listing file would no longer
|
||||
* parse); warnings and hints do not.
|
||||
*/
|
||||
export function appleLocaleIssues(locale: string, v: AppleCopy, appName: string): FieldIssue[] {
|
||||
export function appleLocaleIssues(locale: string, v: AppleCopy, appName: string, allow: readonly PlatformAllow[] = []): FieldIssue[] {
|
||||
const out: FieldIssue[] = [];
|
||||
for (const f of APPLE_REQUIRED) if (!(v[f] ?? '').trim()) out.push({ level: 'error', field: f, msg: `${locale}: ${f} is required` });
|
||||
for (const [f, lim] of Object.entries(APPLE_LIMITS) as [keyof typeof APPLE_LIMITS, number][]) {
|
||||
|
|
@ -63,19 +65,19 @@ export function appleLocaleIssues(locale: string, v: AppleCopy, appName: string)
|
|||
for (const f of ['name', 'subtitle', 'promotionalText', 'keywords', 'description', 'whatsNew'] as const) {
|
||||
const t = f === 'name' ? (v.name ?? '') : v[f] ?? '';
|
||||
if (!t) continue;
|
||||
const o = OTHER_PLATFORMS.exec(t);
|
||||
if (o) out.push({ level: 'warn', field: f, msg: `"${o[0]}": other platforms or stores in App Store text get it rejected (2.3.10)` });
|
||||
if (PLACEHOLDER_MARKERS.test(t) || PLACEHOLDER_TEXT.test(t)) out.push({ level: 'warn', field: f, msg: 'placeholder text (template or TODO) is still in it' });
|
||||
const b = f === 'whatsNew' ? null : BETA_WORDS.exec(t);
|
||||
if (b && !/\bdemo account\b|\bdemo (video|mode)\b/i.test(t)) out.push({ level: 'warn', field: f, msg: `"${b[0]}": demos, betas and trials belong on TestFlight (2.2)` });
|
||||
}
|
||||
// 2.3.10: other platforms or stores (Android, Google Play, ...) stop the save (pitfall 77)
|
||||
out.push(...fieldsMentions('apple', locale, { ...v, name: v.name ?? '' }, allow).map(mentionIssue));
|
||||
return out;
|
||||
}
|
||||
|
||||
export interface PlayValues { title?: string; short?: string; full?: string; whatsNew?: string }
|
||||
|
||||
/** Issues of one Google Play language (title falls back to app.name). */
|
||||
export function playLangIssues(lang: string, v: PlayValues, appName: string): FieldIssue[] {
|
||||
export function playLangIssues(lang: string, v: PlayValues, appName: string, allow: readonly PlatformAllow[] = []): FieldIssue[] {
|
||||
const out: FieldIssue[] = [];
|
||||
for (const f of PLAY_REQUIRED) if (!(v[f] ?? '').trim()) out.push({ level: 'error', field: f, msg: `${lang}: the ${f} description is required` });
|
||||
const title = (v.title ?? '').trim() || appName;
|
||||
|
|
@ -93,6 +95,8 @@ export function playLangIssues(lang: string, v: PlayValues, appName: string): Fi
|
|||
const t = v[f] ?? '';
|
||||
if (t && (PLACEHOLDER_MARKERS.test(t) || PLACEHOLDER_TEXT.test(t))) out.push({ level: 'warn', field: f, msg: 'placeholder text (template or TODO) is still in it' });
|
||||
}
|
||||
// Play Metadata policy: iOS, App Store, ... in Play text stop the save (pitfall 77)
|
||||
out.push(...fieldsMentions('play', lang, { ...v }, allow).map(mentionIssue));
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue