feat: Add locales with OGG sounds (#1057)

* fix jiuchuan build problem

* feat: Add locales with OGG sounds

* fix building echoear

* Support ogg files frame duration <= 60
This commit is contained in:
Xiaoxia
2025-08-12 18:41:00 +08:00
committed by GitHub
parent 08b8b04c6c
commit 9ae34d3b45
460 changed files with 1372 additions and 80 deletions

View File

@@ -4,6 +4,7 @@ import json
import os
HEADER_TEMPLATE = """// Auto-generated language config
// Language: {lang_code} with en-US fallback
#pragma once
#include <string_view>
@@ -16,19 +17,58 @@ namespace Lang {{
// 语言元数据
constexpr const char* CODE = "{lang_code}";
// 字符串资源
// 字符串资源 (en-US as fallback for missing keys)
namespace Strings {{
{strings}
}}
// 音效资源
// 音效资源 (en-US as fallback for missing audio files)
namespace Sounds {{
{sounds}
}}
}}
"""
def generate_header(input_path, output_path):
def load_base_language(assets_dir):
"""加载 en-US 基准语言数据"""
base_lang_path = os.path.join(assets_dir, 'locales', 'en-US', 'language.json')
if os.path.exists(base_lang_path):
try:
with open(base_lang_path, 'r', encoding='utf-8') as f:
base_data = json.load(f)
print(f"Loaded base language en-US with {len(base_data.get('strings', {}))} strings")
return base_data
except json.JSONDecodeError as e:
print(f"Warning: Failed to parse en-US language file: {e}")
else:
print("Warning: en-US base language file not found, fallback mechanism disabled")
return {'strings': {}}
def get_sound_files(directory):
"""获取目录中的音效文件列表"""
if not os.path.exists(directory):
return []
return [f for f in os.listdir(directory) if f.endswith('.ogg')]
def generate_header(lang_code, output_path):
# 从输出路径推导项目结构
# output_path 通常是 main/assets/lang_config.h
main_dir = os.path.dirname(output_path) # main/assets
if os.path.basename(main_dir) == 'assets':
main_dir = os.path.dirname(main_dir) # main
project_dir = os.path.dirname(main_dir) # 项目根目录
assets_dir = os.path.join(main_dir, 'assets')
# 构建语言JSON文件路径
input_path = os.path.join(assets_dir, 'locales', lang_code, 'language.json')
print(f"Processing language: {lang_code}")
print(f"Input file path: {input_path}")
print(f"Output file path: {output_path}")
if not os.path.exists(input_path):
raise FileNotFoundError(f"Language file not found: {input_path}")
with open(input_path, 'r', encoding='utf-8') as f:
data = json.load(f)
@@ -36,37 +76,88 @@ def generate_header(input_path, output_path):
if 'language' not in data or 'strings' not in data:
raise ValueError("Invalid JSON structure")
lang_code = data['language']['type']
# 加载 en-US 基准语言数据
base_data = load_base_language(assets_dir)
# 合并字符串:以 en-US 为基准,用户语言覆盖
base_strings = base_data.get('strings', {})
user_strings = data['strings']
merged_strings = base_strings.copy()
merged_strings.update(user_strings)
# 统计信息
base_count = len(base_strings)
user_count = len(user_strings)
total_count = len(merged_strings)
fallback_count = total_count - user_count
print(f"Language {lang_code} string statistics:")
print(f" - Base language (en-US): {base_count} strings")
print(f" - User language: {user_count} strings")
print(f" - Total: {total_count} strings")
if fallback_count > 0:
print(f" - Fallback to en-US: {fallback_count} strings")
# 生成字符串常量
strings = []
sounds = []
for key, value in data['strings'].items():
for key, value in merged_strings.items():
value = value.replace('"', '\\"')
strings.append(f' constexpr const char* {key.upper()} = "{value}";')
# 生成音效常量
for file in os.listdir(os.path.dirname(input_path)):
if file.endswith('.p3'):
base_name = os.path.splitext(file)[0]
sounds.append(f'''
extern const char p3_{base_name}_start[] asm("_binary_{base_name}_p3_start");
extern const char p3_{base_name}_end[] asm("_binary_{base_name}_p3_end");
static const std::string_view P3_{base_name.upper()} {{
static_cast<const char*>(p3_{base_name}_start),
static_cast<size_t>(p3_{base_name}_end - p3_{base_name}_start)
# 收集音效文件:以 en-US 为基准,用户语言覆盖
current_lang_dir = os.path.join(assets_dir, 'locales', lang_code)
base_lang_dir = os.path.join(assets_dir, 'locales', 'en-US')
common_dir = os.path.join(assets_dir, 'common')
# 获取所有可能的音效文件
base_sounds = get_sound_files(base_lang_dir)
current_sounds = get_sound_files(current_lang_dir)
common_sounds = get_sound_files(common_dir)
# 合并音效文件列表:用户语言覆盖基准语言
all_sound_files = set(base_sounds)
all_sound_files.update(current_sounds)
# 音效统计信息
base_sound_count = len(base_sounds)
user_sound_count = len(current_sounds)
common_sound_count = len(common_sounds)
sound_fallback_count = len(set(base_sounds) - set(current_sounds))
print(f"Language {lang_code} sound statistics:")
print(f" - Base language (en-US): {base_sound_count} sounds")
print(f" - User language: {user_sound_count} sounds")
print(f" - Common sounds: {common_sound_count} sounds")
if sound_fallback_count > 0:
print(f" - Sound fallback to en-US: {sound_fallback_count} sounds")
# 生成语言特定音效常量
for file in sorted(all_sound_files):
base_name = os.path.splitext(file)[0]
# 优先使用当前语言的音效,如果不存在则回退到 en-US
if file in current_sounds:
sound_lang = lang_code.replace('-', '_').lower()
else:
sound_lang = 'en_us'
sounds.append(f'''
extern const char ogg_{base_name}_start[] asm("_binary_{base_name}_ogg_start");
extern const char ogg_{base_name}_end[] asm("_binary_{base_name}_ogg_end");
static const std::string_view OGG_{base_name.upper()} {{
static_cast<const char*>(ogg_{base_name}_start),
static_cast<size_t>(ogg_{base_name}_end - ogg_{base_name}_start)
}};''')
# 生成公共音效
for file in os.listdir(os.path.join(os.path.dirname(output_path), 'common')):
if file.endswith('.p3'):
base_name = os.path.splitext(file)[0]
sounds.append(f'''
extern const char p3_{base_name}_start[] asm("_binary_{base_name}_p3_start");
extern const char p3_{base_name}_end[] asm("_binary_{base_name}_p3_end");
static const std::string_view P3_{base_name.upper()} {{
static_cast<const char*>(p3_{base_name}_start),
static_cast<size_t>(p3_{base_name}_end - p3_{base_name}_start)
# 生成公共音效常量
for file in sorted(common_sounds):
base_name = os.path.splitext(file)[0]
sounds.append(f'''
extern const char ogg_{base_name}_start[] asm("_binary_{base_name}_ogg_start");
extern const char ogg_{base_name}_end[] asm("_binary_{base_name}_ogg_end");
static const std::string_view OGG_{base_name.upper()} {{
static_cast<const char*>(ogg_{base_name}_start),
static_cast<size_t>(ogg_{base_name}_end - ogg_{base_name}_start)
}};''')
# 填充模板
@@ -83,9 +174,14 @@ def generate_header(input_path, output_path):
f.write(content)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--input", required=True, help="输入JSON文件路径")
parser.add_argument("--output", required=True, help="输出头文件路径")
parser = argparse.ArgumentParser(description="Generate language configuration header file with en-US fallback")
parser.add_argument("--language", required=True, help="Language code (e.g: zh-CN, en-US, ja-JP)")
parser.add_argument("--output", required=True, help="Output header file path")
args = parser.parse_args()
generate_header(args.input, args.output)
try:
generate_header(args.language, args.output)
print(f"Successfully generated language config file: {args.output}")
except Exception as e:
print(f"Error: {e}")
exit(1)