前言

游戏出海是近年来中国游戏厂商的重要增长方向。但成功的出海不仅需要优质的内容,更需要完善的本地化技术支持。本文将深入探讨游戏本地化系统的设计与实现。

多语言系统

1. i18n 框架设计

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// 本地化配置
interface LocaleConfig {
    code: string;        // 语言代码
    name: string;        // 语言名称
    region: string;      // 地区代码
    direction: 'ltr' | 'rtl';  // 文本方向
    fallback: string;    // 回退语言
    formats: LocaleFormats;
}

interface LocaleFormats {
    date: Intl.DateTimeFormatOptions;
    time: Intl.DateTimeFormatOptions;
    number: Intl.NumberFormatOptions;
    currency: Intl.NumberFormatOptions;
}

// 翻译管理器
class I18nManager {
    private currentLocale: string;
    private locales = new Map<string, LocaleConfig>();
    private translations = new Map<string, Map<string, string>>();
    private fallbackChain: string[] = [];

    constructor(defaultLocale: string = 'zh-CN') {
        this.currentLocale = defaultLocale;
        this.loadLocaleConfig(defaultLocale);
    }

    // 加载语言配置
    async loadLocaleConfig(locale: string): Promise<void> {
        const config = await this.fetchConfig(locale);
        this.locales.set(locale, config);
    }

    // 加载翻译资源
    async loadTranslations(locale: string): Promise<void> {
        const resources = await this.fetchResources(locale);
        const translationMap = new Map<string, string>();

        for (const [key, value] of Object.entries(resources)) {
            translationMap.set(key, value);
        }

        this.translations.set(locale, translationMap);
    }

    // 翻译文本
    translate(key: string, params?: Record<string, any>): string {
        let text = this.getTranslation(key);

        // 参数插值
        if (params) {
            text = this.interpolate(text, params);
        }

        // 复数处理
        text = this.handlePlural(text, params);

        return text;
    }

    // 获取翻译
    private getTranslation(key: string): string {
        // 当前语言
        const translations = this.translations.get(this.currentLocale);
        if (translations?.has(key)) {
            return translations.get(key)!;
        }

        // 回退链
        for (const locale of this.fallbackChain) {
            const fallbackTranslations = this.translations.get(locale);
            if (fallbackTranslations?.has(key)) {
                return fallbackTranslations.get(key)!;
            }
        }

        // 返回 key 作为最后的回退
        console.warn(`Translation not found: ${key}`);
        return key;
    }

    // 参数插值
    private interpolate(text: string, params: Record<string, any>): string {
        return text.replace(/\{\{(\w+)\}\}/g, (match, key) => {
            return params[key] !== undefined ? params[key] : match;
        });
    }

    // 复数处理
    private handlePlural(text: string, params?: Record<string, any>): string {
        if (!params?.count) return text;

        const locale = this.locales.get(this.currentLocale);
        const pluralForm = new Intl.PluralRules(locale?.code).select(params.count);

        const pluralKey = `${text}.${pluralForm}`;
        if (this.hasTranslation(pluralKey)) {
            return this.getTranslation(pluralKey);
        }

        return text;
    }

    // 切换语言
    async switchLocale(locale: string): Promise<void> {
        // 加载新语言资源
        if (!this.translations.has(locale)) {
            await this.loadTranslations(locale);
        }

        this.currentLocale = locale;

        // 构建回退链
        const config = this.locales.get(locale);
        this.fallbackChain = [locale];
        if (config?.fallback) {
            this.fallbackChain.push(config.fallback);
        }

        // 触发语言变更事件
        this.onLocaleChange(locale);
    }

    // 格式化日期
    formatDate(date: Date, format?: 'short' | 'long' | 'full'): string {
        const config = this.locales.get(this.currentLocale);
        const options = format
            ? { ...config?.formats.date, dateStyle: format }
            : config?.formats.date;

        return new Intl.DateTimeFormat(this.currentLocale, options).format(date);
    }

    // 格式化数字
    formatNumber(num: number): string {
        const config = this.locales.get(this.currentLocale);
        return new Intl.NumberFormat(this.currentLocale, config?.formats.number).format(num);
    }

    // 格式化货币
    formatCurrency(amount: number, currency?: string): string {
        const config = this.locales.get(this.currentLocale);
        return new Intl.NumberFormat(this.currentLocale, {
            ...config?.formats.currency,
            style: 'currency',
            currency: currency || 'USD'
        }).format(amount);
    }
}

// 使用示例
const i18n = new I18nManager();

// 简单翻译
const title = i18n.translate('game.title');
// => "王者荣耀" (zh-CN)
// => "Honor of Kings" (en-US)

// 带参数翻译
const message = i18n.translate('player.joined', { name: 'John', count: 1 });
// => "玩家 John 加入了游戏" (zh-CN)
// => "Player John joined the game" (en-US)

// 复数翻译
const coinsText = i18n.translate('wallet.coins', { count: 5 });
// => "5 金币" (zh-CN)
// => "5 coins" (en-US)

// 日期格式化
const dateStr = i18n.formatDate(new Date(), 'long');
// => "2026年1月16日" (zh-CN)
// => "January 16, 2026" (en-US)

2. 翻译资源管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 翻译资源结构
interface TranslationResources {
    [namespace: string]: {
        [key: string]: string | TranslationObject;
    };
}

interface TranslationObject {
    value: string;
    context?: string;
    plural?: {
        one?: string;
        other?: string;
        [form: string]: string | undefined;
    };
    metadata?: {
        length?: number;
        description?: string;
        character_limit?: number;
    };
}

// 资源加载器
class TranslationLoader {
    private cache = new Map<string, TranslationResources>();
    private remoteUrl: string;

    constructor(remoteUrl: string) {
        this.remoteUrl = remoteUrl;
    }

    // 加载翻译资源
    async load(locale: string, namespace: string): Promise<TranslationResources> {
        const cacheKey = `${locale}:${namespace}`;

        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey)!;
        }

        // 从远程加载
        const url = `${this.remoteUrl}/locales/${locale}/${namespace}.json`;
        const response = await fetch(url);
        const resources = await response.json();

        this.cache.set(cacheKey, resources);
        return resources;
    }

    // 热更新翻译
    async hotUpdate(locale: string, version: string): Promise<void> {
        // 检查版本
        const manifest = await this.fetchManifest();
        const latestVersion = manifest.locales[locale];

        if (latestVersion === version) {
            console.log('Translations already up to date');
            return;
        }

        // 下载新翻译
        const namespaces = manifest.namespaces;
        for (const ns of namespaces) {
            await this.load(locale, ns);
        }

        // 保存版本
        await this.saveVersion(locale, latestVersion);
    }
}

文化适配

1. 文化差异处理

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
// 文化规则引擎
class CultureRulesEngine {
    private rules = new Map<string, CultureRule>();

    constructor() {
        this.loadRules();
    }

    // 加载文化规则
    private loadRules(): void {
        // 颜色禁忌
        this.addRule('color.taboo', {
            'CN': ['white'],  // 中国:白色(丧事)
            'JP': ['black'],  // 日本:黑色
            'SA': ['yellow'], // 沙特:黄色
        });

        // 数字禁忌
        this.addRule('number.taboo', {
            'CN': [4],      // 四
            'JP': [4, 9],   // 四、九
            'US': [13],
            'KR': [4],
        });

        // 图像规范
        this.addRule('image.restrictions', {
            'IN': { // 印度
                avoid: ['cows', 'religious_symbols'],
                require: ['modest_clothing']
            },
            'AE': { // 阿联酋
                avoid: ['alcohol', 'gambling', 'pork'],
                require: ['modest_clothing', 'halal_certification']
            }
        });

        // 节假日
        this.addRule('holiday', {
            'CN': ['spring_festival', 'mid_autumn', 'national_day'],
            'US': ['christmas', 'thanksgiving', 'independence_day'],
            'JP': ['golden_week', 'obon'],
            'KR': ['seollal', 'chuseok'],
        });
    }

    // 检查是否适合目标文化
    checkCulturalFit(content: any, targetRegion: string): CulturalFitReport {
        const issues: CulturalIssue[] = [];

        // 检查颜色
        if (content.colors) {
            const colorTaboos = this.getRule('color.taboo', targetRegion) || [];
            for (const color of content.colors) {
                if (colorTaboos.includes(color)) {
                    issues.push({
                        type: 'color',
                        severity: 'warning',
                        message: `Color ${color} may be inappropriate in ${targetRegion}`,
                        suggestion: `Consider alternative colors`
                    });
                }
            }
        }

        // 检查数字
        if (content.numbers) {
            const numberTaboos = this.getRule('number.taboo', targetRegion) || [];
            for (const num of content.numbers) {
                if (numberTaboos.includes(num)) {
                    issues.push({
                        type: 'number',
                        severity: 'info',
                        message: `Number ${num} has negative connotations in ${targetRegion}`,
                        suggestion: `Consider using ${num + 1} or ${num - 1}`
                    });
                }
            }
        }

        // 检查图像
        if (content.images) {
            const restrictions = this.getRule('image.restrictions', targetRegion);
            if (restrictions) {
                for (const image of content.images) {
                    for (const avoid of restrictions.avoid) {
                        if (image.tags?.includes(avoid)) {
                            issues.push({
                                type: 'image',
                                severity: 'error',
                                message: `Image contains culturally sensitive content: ${avoid}`,
                                suggestion: `Remove or replace this image`
                            });
                        }
                    }
                }
            }
        }

        return {
            compatible: issues.filter(i => i.severity === 'error').length === 0,
            issues,
            score: this.calculateScore(issues)
        };
    }
}

2. UI 适配

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
// UI 适配器
class UIAdapter {
    private currentDirection: 'ltr' | 'rtl' = 'ltr';

    // 适配文本方向
    adaptForDirection(direction: 'ltr' | 'rtl'): void {
        this.currentDirection = direction;

        // 更新根元素
        document.documentElement.dir = direction;

        // 镜像布局
        const mirroredElements = document.querySelectorAll('[data-rtl-mirror]');
        mirroredElements.forEach(el => {
            if (direction === 'rtl') {
                el.classList.add('rtl-mirrored');
            } else {
                el.classList.remove('rtl-mirrored');
            }
        });
    }

    // 适配字体
    adaptFonts(locale: string): void {
        const fontFamilies = {
            'zh-CN': '"PingFang SC", "Microsoft YaHei", sans-serif',
            'ja-JP': '"Hiragino Kaku Gothic Pro", "Yu Gothic UI", sans-serif',
            'ko-KR': '"Malgun Gothic", "Apple SD Gothic Neo", sans-serif',
            'ar': '"Noto Sans Arabic", "Arial", sans-serif',
            'th': '"Noto Sans Thai", "Tahoma", sans-serif',
        };

        const font = fontFamilies[locale] || 'system-ui, sans-serif';
        document.body.style.fontFamily = font;
    }

    // 适配布局
    adaptLayout(locale: string): void {
        // 检查是否为 RTL 语言
        const isRTL = locale.startsWith('ar') ||
                     locale.startsWith('he') ||
                     locale.startsWith('fa');

        if (isRTL) {
            this.adaptForDirection('rtl');
        } else {
            this.adaptForDirection('ltr');
        }

        // 调整文本容器
        const textContainers = document.querySelectorAll('.text-container');
        textContainers.forEach(container => {
            const element = container as HTMLElement;

            // 阿拉伯语需要更大的行高
            if (locale.startsWith('ar')) {
                element.style.lineHeight = '1.8';
            }

            // 中文可以更紧凑
            if (locale.startsWith('zh')) {
                element.style.lineHeight = '1.5';
            }

            // 日韩文字间距
            if (locale.startsWith('ja') || locale.startsWith('ko')) {
                element.style.letterSpacing = '0.05em';
            }
        });
    }

    // 动态调整 UI 元素大小
    adaptTextLength(element: HTMLElement, locale: string): void {
        const text = element.textContent || '';
        const lengthFactor = this.getTextLengthFactor(locale);

        // 获取原始宽度
        const originalWidth = element.offsetWidth;

        // 计算新宽度
        const newWidth = Math.ceil(originalWidth * lengthFactor);

        // 应用宽度限制
        if (newWidth > originalWidth) {
            element.style.minWidth = `${newWidth}px`;
        }

        // 调整字体大小
        const fontSize = parseInt(window.getComputedStyle(element).fontSize);
        if (fontSize < 12) {
            element.style.fontSize = '12px';
        }
    }

    // 获取文本长度系数
    private getTextLengthFactor(locale: string): number {
        const factors: Record<string, number> = {
            'en': 1.5,   // 英文通常比中文长
            'de': 1.6,   // 德文更长
            'ar': 1.4,   // 阿拉伯语
            'ja': 1.2,   // 日文
            'ko': 1.2,   // 韩文
            'zh': 1.0,   // 中文基准
        };

        const langCode = locale.split('-')[0];
        return factors[langCode] || 1.3;
    }
}

时区与日历

1. 时区处理

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
// 时区管理器
class TimeZoneManager {
    private userTimeZone: string;

    constructor() {
        // 检测用户时区
        this.userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
    }

    // 转换时区
    convertTime(
        time: Date | string,
        fromZone: string,
        toZone: string
    ): Date {
        const date = typeof time === 'string' ? new Date(time) : time;

        // 使用 Intl API 转换
        const formatter = new Intl.DateTimeFormat('en-US', {
            timeZone: toZone,
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit',
            hour12: false
        });

        const parts = formatter.formatToParts(date);
        const mapping: Record<string, string> = {};
        parts.forEach(p => mapping[p.type] = p.value);

        return new Date(
            `${mapping.year}-${mapping.month}-${mapping.day}T${mapping.hour}:${mapping.minute}:${mapping.second}`
        );
    }

    // 获取服务器时间(UTC)
    getServerTime(): Date {
        return new Date();
    }

    // 获取本地时间
    getLocalTime(utcTime: Date): Date {
        return this.convertTime(utcTime, 'UTC', this.userTimeZone);
    }

    // 格式化时间(显示时区)
    formatWithTimeZone(
        date: Date,
        timeZone?: string,
        format?: Intl.DateTimeFormatOptions
    ): string {
        const zone = timeZone || this.userTimeZone;

        return new Intl.DateTimeFormat(this.userTimeZone, {
            timeZone: zone,
            ...format,
            timeZoneName: 'short'
        }).format(date);
    }

    // 计算时差
    getTimeZoneOffset(zone1: string, zone2: string): number {
        const now = new Date();
        const time1 = this.formatWithTimeZone(now, zone1);
        const time2 = this.formatWithTimeZone(now, zone2);

        return Math.abs(
            new Date(time1).getTime() - new Date(time2).getTime()
        ) / (1000 * 60 * 60);
    }
}

// 游戏时间同步
class GameTimeSync {
    private serverOffset: number = 0;
    private timeZoneManager: TimeZoneManager;

    async syncWithServer(): Promise<void> {
        // 发送请求获取服务器时间
        const response = await fetch('/api/time');
        const { serverTime } = await response.json();

        // 计算时差
        this.serverOffset = new Date(serverTime).getTime() - Date.now();
    }

    // 获取同步后的服务器时间
    getServerTime(): Date {
        return new Date(Date.now() + this.serverOffset);
    }

    // 获取游戏内时间(可能经过加速)
    getGameTime(speedMultiplier: number = 1): Date {
        const realTime = Date.now();
        const gameElapsed = realTime * speedMultiplier;
        return new Date(this.serverOffset + gameElapsed);
    }
}

2. 多历法支持

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 历法管理器
class CalendarManager {
    private currentCalendar: string = 'gregory';

    // 支持的历法类型
    private calendars = {
        'gregory': 'gregory',     // 公历
        'chinese': 'chinese',     // 农历
        'islamic': 'islamic',     // 伊斯兰历
        'hebrew': 'hebrew',       // 希伯来历
        'japanese': 'japanese',   // 和历
        'buddhist': 'buddhist',   // 佛历
    };

    // 格式化日期(指定历法)
    formatDate(
        date: Date,
        calendar: string,
        locale: string
    ): string {
        return new Intl.DateTimeFormat(locale, {
            calendar: this.calendars[calendar] || 'gregory',
            year: 'numeric',
            month: 'long',
            day: 'numeric'
        }).format(date);
    }

    // 获取农历信息
    getChineseDate(date: Date): ChineseDate {
        // 实现农历转换算法
        const lunar = this.solarToLunar(date);

        return {
            year: lunar.year,
            month: lunar.month,
            day: lunar.day,
            leapMonth: lunar.isLeap,
            zodiac: this.getChineseZodiac(lunar.year),
            element: this.getChineseElement(lunar.year),
            constellation: this.getChineseConstellation(lunar.month, lunar.day)
        };
    }

    // 获取伊斯兰历日期
    getIslamicDate(date: Date, locale: string): string {
        return new Intl.DateTimeFormat(locale, {
            calendar: 'islamic',
            year: 'numeric',
            month: 'long',
            day: 'numeric'
        }).format(date);
    }
}

内容审核与合规

1. 敏感词过滤

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 敏感词过滤器
class ContentFilter {
    private filters = new Map<string, Set<string>>();
    private patterns = new Map<string, RegExp[]>();

    // 加载过滤规则
    async loadFilters(locale: string): Promise<void> {
        const response = await fetch(`/api/filters/${locale}`);
        const rules = await response.json();

        // 构建敏感词集合
        const sensitiveWords = new Set<string>(rules.words || []);
        this.filters.set(locale, sensitiveWords);

        // 构建正则表达式
        const regexes = (rules.patterns || []).map(
            pattern => new RegExp(pattern, 'gi')
        );
        this.patterns.set(locale, regexes);
    }

    // 过滤文本
    filter(text: string, locale: string): FilterResult {
        const result: FilterResult = {
            original: text,
            filtered: text,
            violations: [],
            clean: true
        };

        // 检查敏感词
        const words = this.filters.get(locale);
        if (words) {
            for (const word of words) {
                if (text.toLowerCase().includes(word.toLowerCase())) {
                    result.violations.push({
                        type: 'sensitive_word',
                        content: word,
                        position: text.toLowerCase().indexOf(word.toLowerCase())
                    });
                    result.clean = false;
                }
            }
        }

        // 检查正则模式
        const patterns = this.patterns.get(locale);
        if (patterns) {
            for (const pattern of patterns) {
                const matches = text.match(pattern);
                if (matches) {
                    for (const match of matches) {
                        result.violations.push({
                            type: 'pattern_match',
                            content: match,
                            position: text.indexOf(match)
                        });
                        result.clean = false;
                    }
                }
            }
        }

        // 生成过滤后的文本
        if (!result.clean) {
            result.filtered = this.maskViolations(text, result.violations);
        }

        return result;
    }

    // 屏蔽违规内容
    private maskViolations(text: string, violations: ContentViolation[]): string {
        let result = text;

        // 按位置排序(从后往前,避免索引变化)
        const sorted = violations.sort((a, b) => b.position - a.position);

        for (const violation of sorted) {
            const before = result.substring(0, violation.position);
            const after = result.substring(violation.position + violation.content.length);
            const mask = '*'.repeat(violation.content.length);
            result = before + mask + after;
        }

        return result;
    }
}

2. 区域合规检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// 合规规则引擎
class ComplianceEngine {
    private rules = new Map<string, ComplianceRule[]>();

    // 加载合规规则
    async loadRules(region: string): Promise<void> {
        const response = await fetch(`/api/compliance/${region}`);
        const rules = await response.json();

        this.rules.set(region, rules);
    }

    // 检查内容合规性
    checkCompliance(content: GameContent, region: string): ComplianceReport {
        const rules = this.rules.get(region) || [];
        const violations: ComplianceViolation[] = [];

        for (const rule of rules) {
            const result = this.checkRule(content, rule);
            if (!result.compliant) {
                violations.push({
                    rule: rule.name,
                    severity: rule.severity,
                    message: result.message,
                    requirement: rule.requirement
                });
            }
        }

        return {
            region,
            compliant: violations.filter(v => v.severity === 'critical').length === 0,
            violations,
            recommendations: this.getRecommendations(violations)
        };
    }

    // 检查单条规则
    private checkRule(content: GameContent, rule: ComplianceRule): RuleCheckResult {
        switch (rule.type) {
            case 'age_rating':
                return this.checkAgeRating(content, rule);

            case 'violence':
                return this.checkViolenceLevel(content, rule);

            case 'gambling':
                return this.checkGamblingContent(content, rule);

            case 'loot_box':
                return this.checkLootBox(content, rule);

            case 'privacy':
                return this.checkPrivacyCompliance(content, rule);

            default:
                return { compliant: true };
        }
    }

    // 年龄评级检查
    private checkAgeRating(content: GameContent, rule: ComplianceRule): RuleCheckResult {
        const maxRating = rule.maxRating || '18';
        const contentRating = content.ageRating || '12';

        const ratingOrder = ['3', '7', '12', '16', '18'];
        const contentIndex = ratingOrder.indexOf(contentRating);
        const maxIndex = ratingOrder.indexOf(maxRating);

        if (contentIndex > maxIndex) {
            return {
                compliant: false,
                message: `Content rating ${contentRating} exceeds maximum ${maxRating}`
            };
        }

        return { compliant: true };
    }

    // 暴力内容检查
    private checkViolenceLevel(content: GameContent, rule: ComplianceRule): RuleCheckResult {
        const allowedLevel = rule.maxLevel || 'moderate';
        const contentLevel = content.violenceLevel || 'none';

        const levels = ['none', 'mild', 'moderate', 'strong'];
        const contentIndex = levels.indexOf(contentLevel);
        const maxIndex = levels.indexOf(allowedLevel);

        if (contentIndex > maxIndex) {
            return {
                compliant: false,
                message: `Violence level ${contentLevel} exceeds allowed ${allowedLevel}`
            };
        }

        return { compliant: true };
    }
}

总结

游戏本地化技术方案的核心要点:

  1. 多语言系统:完善的 i18n 框架、翻译资源管理
  2. 文化适配:文化差异处理、UI 适配
  3. 时区处理:时区转换、多历法支持
  4. 内容审核:敏感词过滤、合规检查

成功的游戏出海需要技术团队对目标市场有深入理解,并提供完善的本地化支持。


相关工具: