iOS语言国际化/本地化-实践总结

2018年09月07日

Build Apps for the World

1 添加要支持的国际语言

  1. 添加语言后面的括号内容是该语言的国际标准缩写名


  2. 如选择添加日语后,会弹出如下对话框,选择Finish即可


  3. 如下选择添加日语,法语,简体中文,繁体中文后,可以发现相应的变化


2 本地化应用名

本地化应用名:App的名称,根据设备语言的设置,显示成对应名称(如微信App,在简体中文语言下显示成微信,在英文语言下显示weChat)

2.1 选中info.plist文件,右键选择新建文件

2.2 选择创建文件类型为Strings File

2.3 指定名称为InfoPlist.strings(名称必须是InfoPlist)

2.4 创建成功后

2.5 选中InfoPlist.strings,在Xcode的File inspection(Xcode右侧文件检查器)中点击Localize,目的是选择我们需要本地化的语言,如下图:

注意:在点击Localize之前,一定要保证第1步已经添加了需要国际语言

2.6 弹出确认对话框,默认选择English,你可展开优选选择你需要本地化的语言,或者直接按默认,后续可以继续添加你需要的国际语言

2.7 选择本地化的语言后,可以看到InfoPlist.strings文件在文件检查器栏的Locaization栏发生变化

2.8 将其他国际语言都勾上后,可以看到InfoPlist.strings文件也出现了多个子项的变化

2.9 从项目文件夹方向看看发生了什么

多语言本地化的核心思想
就是为每种语言单独定义一份资源,iOS就是通过xxx.lproj目录来定义每个语言的资源,这里的资源可以是图片,文本,Storyboard,Xib等

每种语言都有自己的 语言代码.lproj 文件夹,加载资源时只需要加载相应语言文件夹下的资源,这步可以系统为我们完成,也可以手动去做

2.10 先设置demo的display name为:本地化测试Demo

  • info.plist 文件中会出现display name的key value展示


  • 我们知道display name 的名字就是App安装后再设备上显示的名称


  • 通过显示info.plist中的raw key value来获取修改display name对应的key:CFBundleDisplayName



看看官方解析咯
CFBundleDisplayName
CFBundleName
info.plist 的所有key查看 -Core Foundation Keys

CFBundleDisplayName

所以CFBundleDisplayName是可以用在infoPlist.strings上的。

2.11 分别在不同的语言所对应InfoPlist.strings上设置本地化的App名称

// InfoPlist.strings(English) 文件
CFBundleDisplayName = "EnglishDemo";


// InfoPlist.strings(Chinese(Simplified)) 文件
CFBundleDisplayName = "简体Demo";


// InfoPlist.strings(Chinese(Tradictional)) 文件
CFBundleDisplayName = "繁体Demo";


// InfoPlist.strings(Japanese) 文件
CFBundleDisplayName = "日语Demo";


// InfoPlist.strings(French) 文件
CFBundleDisplayName = "法语Demo";

备注:CFBundleDisplayName可以使用双引号,也可以不使用双引号!

2.12 获取当前模拟器当前设置的语言

// ViewController.m
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSArray *languages = [[NSUserDefaults standardUserDefaults] valueForKey:@"AppleLanguages"];
    NSLog(@"AppleLanguages 语言有 %@", languages);
    NSString *currentLanguage = languages.firstObject;
    NSLog(@"模拟器当前语言:%@",currentLanguage);

}

2.13 设置模拟器的语言环境

在iOS设备上设置语言

2.14 运行程序后各个语言本地化的效果展示

  1. 繁体环境下(模拟器当前语言:zh-Hant-HK)
// 运行结果
2018-09-04 16:49:40.056223+0800 LanguageLocalizationDemo[92506:3273624] AppleLanguages 语言有 (
    "zh-Hant-HK",
    "zh-Hans-US",
    en
)
2018-09-04 16:49:40.056505+0800 LanguageLocalizationDemo[92506:3273624] 模拟器当前语言:zh-Hant-HK
  1. 英语环境下(模拟器当前语言:en)
// 运行结果
2018-09-04 17:00:37.811874+0800 LanguageLocalizationDemo[92874:3283615] AppleLanguages 语言有 (
    en
)
2018-09-04 17:00:37.812133+0800 LanguageLocalizationDemo[92874:3283615] 模拟器当前语言:en
  1. 日语环境下(模拟器当前语言:ja-US)
// 运行结果
2018-09-05 10:33:16.423697+0800 LanguageLocalizationDemo[46412:14218145] AppleLanguages 语言有 (
    "ja-US",
    en
)
2018-09-05 10:33:16.423941+0800 LanguageLocalizationDemo[46412:14218145] 模拟器当前语言:ja-US
  1. 简体中文环境下(模拟器当前语言:zh-Hans-US)
// 运行结果
2018-09-05 10:36:18.376366+0800 LanguageLocalizationDemo[46510:14225868] AppleLanguages 语言有 (
    "zh-Hans-US",
    "zh-Hant-US",
    "ja-US",
    en
)
2018-09-05 10:36:18.376628+0800 LanguageLocalizationDemo[46510:14225868] 模拟器当前语言:zh-Hans-US
  1. 法语环境下(模拟器当前语言:fr-US)
// 运行结果
2018-09-05 10:47:12.397641+0800 LanguageLocalizationDemo[46634:14239902] AppleLanguages 语言有 (
    "fr-US",
    "zh-Hans-US",
    "zh-Hant-US",
    "ja-US",
    en
)
2018-09-05 10:47:12.398086+0800 LanguageLocalizationDemo[46634:14239902] 模拟器当前语言:fr-US

当然,模拟器设置对应的语言之后,模拟器重启完成时对应 App 的名称就会随系统语言而变更,不需要重新 run 这个项目,而上述结果每次都进行 run 操作的目的是观察系统语言的变化及确定所设置的语言是否正确

2.15 影响通过AppleLanguageskey 从NSUserDefaults中获取的支持语言的原因

  1. 根据设备曾经添加过的语言返回结果
NSArray *languages = [[NSUserDefaults standardUserDefaults] valueForKey:@"AppleLanguages"];

上面获得的NSArray *languages内容是根据当前设备已经添加过哪些语言决定的,模拟器默认只有英文,所以你会看到2.14中打印的不同结果

  1. 受到 Xcode 的语言设定影响
    假设设备已经添加了简体中文、繁体中文、英文、法语、日语。
    2.1 设备添加了对应语言


2.2 Xcode 中的原因按默认设置为跟随系统


那么此时运行下面代码获得的数组是:

NSArray *languages = [[NSUserDefaults standardUserDefaults] valueForKey:@"AppleLanguages"];
2018-09-05 10:47:12.397641+0800 LanguageLocalizationDemo[46634:14239902] AppleLanguages 语言有 (
    "fr-US",
    "zh-Hans-US",
    "zh-Hant-US",
    "ja-US",
    en
)
2018-09-05 10:47:12.398086+0800 LanguageLocalizationDemo[46634:14239902] 模拟器当前语言:fr-US

打印结果是设备正常已添加的语言。

2.3 Xcode 中的语言设置为指定某一语言(此处设为English)时,结果是:

2018-09-05 10:58:06.445970+0800 LanguageLocalizationDemo[46726:14250118] AppleLanguages 语言有 (
    en
)
2018-09-05 10:58:06.446199+0800 LanguageLocalizationDemo[46726:14250118] 模拟器当前语言:en

AppleLanguages数组中只有一个en 元素
但模拟器真实语言环境没有变化,还是法语,且 App 名称同样还是根据设备环境显示成法语的名称

这里作出的对比,是希望以后再开发调试时候,留意这些影响设备语言获取的因子

同时需要注意的是,App 的名称不会随 Xcode 的 application langue变化而变化,它仅仅跟随设备实际设置的语言而变

3 本地化 代码中的字符串

本地化 代码中的字符串是指程序内的字符串在不同的语言环境下显示不同的内容,比如首页一词,在简体中文下显示就是首页,而在英文下则会显示Home

本地化 代码中的字符串流程与本地化 App 名称基本一致,同样下面也会给出各个步骤的操作贴图

3.1 选中要存放新建文件位置的文件夹后,通过 command + n 快速创建文件

3.2 选择创建文件类型为Strings File

3.3 指定 Strings File 文件名称为 Localizable

使用这个名字原因它是系统默认加载本地化文件名称,后面会提到通过自定义其他名称来模块化处理本地化及解耦


3.4 Localize Localizable.strings文件

3.5 勾选其他语言

到目前为止,上述步骤与本地化 App 名称是一样的,不同点是 strings 文件的名称

3.6 在对应语言文件中按 key-value 的形式写入需要本地化的字符串

// Localizable.strings(English) 文件
"home" = "home";

// Localizable.strings(Chinese(Simplified)) 文件
"home" = "简体主页";

// Localizable.strings(Japanese) 文件
"home" = "日语主页";

// Localizable.strings(Chinese(Traditional)) 文件
"home" = "繁体主页";

// Localizable.strings(French) 文件
"home" = "法语主页";

3.7 通过NSLocalizedString(key, comment)这个系统提供的宏,使用本地化文件中的内容

  1. 环境语言的切换可以通过设备的语言设置Xcode中Scheme设置,这里推荐通过 Xcode 进行快速设置,这样省去等待模拟器的重启时间
  2. 下面是设置运行效果
  • 简体中文


  • 英文


  • 法语


  • 繁体中文


  1. NSLocalizedString宏定义解析

localizedStringForKey:value:table: - NSBundle

3.8 NSLocalizedString 使用小技巧

  1. 使用NSLocalizedString按照给定的 key 查找对应 strings 文件时,如果找不到该 key 对应value 时,默认返回的值就是 key。
  • Localizable.strings(English)文件什么都没写
  • NSLocalizedString使用的 key 为 lala
  • 当查找不到时,就会将 key 返回,如下图:


  1. 假设你使用的是英文的名称作为 key,那么一般情况下,Localizable.strings(English)文件中的键值对应该是这样的"home" = "home";,即键值一样,那就可以利用上面的第一点,查找不到是返回 key 的特性,省去在Localizable.strings(English)文件中补上键值的情况(当然,这是不建议的😆)

4 模块化管理本地化

4.1 模块化管理的原因

  1. 从第3大点上可以知道,通过构建一个系统默认的名称的Localizable.strings文件,可以将项目中所有需要本地化语言的代码字符串统一放在Localizable.strings文件中。
  2. 但是都放在一个地方虽然可以统一管理,但随着项目的模块的增多等情况,该文件内容必定会存在内容臃肿情况,虽然可通过注释或者#pragma mark - <#desc#>来分层管理,但面向多人编程时,同时修改一个文件导致的问题更复杂

4.2 自定义.strings文件,实现模块化管理

  1. 通过NSLocalizedStringFromTable宏,手动指定查找 strings 文件。
NSLocalizedStringFromTable(<#key#>, <#tbl#>, <#comment#>)
  1. 自定义名称·strings文件(Module1.strings)

  2. 使用NSLocalizedStringFromTable


5 本地化图片

5.1 方式1:类似本地化代码字符串方式,通过NSLocalizedString获取语言的图片名称,从Assets.xcassets中获取相应图片

  • 使用代码示例


  • Assets.xcassets图片命名

  • 对应 Localizable.strings 内容

// Localizable.strings(English) 文件
"image-languge" = "english";

// Localizable.strings(Chinese(Simplified)) 文件
"image-languge" = "simplify";

// Localizable.strings(Japanese) 文件
"image-languge" = "japan";

// Localizable.strings(Chinese(Traditional)) 文件
"image-languge" = "french";

// Localizable.strings(French) 文件
"home" = "法语主页";
  • 运行效果


    法语

    英语

    其他请参考 demo

5.2 方式2:指定 bundle 的图片,让其具有本地化属性(即把图片资源放到对应的语言lproj文件夹中)

  1. 拖拽一张图片至项目 bundle 中


  2. 本地化图片


  3. 将其他语言选上


  4. 在对应图片的本地化文件夹内都可以看到有一张 icon 图


  5. 将对应语言的.lproj文件夹内的 icon 图替换成同名的其他 icon 图



    其他语言一样操作

  6. 完成第5步后回到项目,点击对应的语言.strings文件看到对应的 icon


  7. 通过 icon 名称langueIcon作为 key,使用NSLocalizedString获取应语言字符串

  • 运行效果如5.1


本地化图片需要多张图片类似的图片,如果一次适配的语言较多情况下,那么包体积变大是不可避免的事情,这一点可考虑一下动态获取图片的方法。这样按需获取在多语言情况下比较可行

6 本地化 Xib

6.1 本地化 Xib

  1. 创建 xib 的 vc


  2. Localize xib 文件,注意:直接选择 Base,不要选其他

  • 选其他的话,会没有生成对应的 key-value


  1. 勾选其他的语言


相比于其他的.strings文件,xib 或 sb 本地化之后,对应语言的.strings 文件是可以变换成特定 xib 样式的


你可以选择 让其他语言以 strings 文件形式来 基于Base 的 xib;或者每个语言都成为独立的 xib 文件
但是不建议使用独立 xib 的形式,当然如果不同语言不同布局的话,也是可以使这样形式的

  1. 选择 Base 的 String 自动生成strings 文件内容浏览
    系统会根据当前 xib 中的子控件(限文本控件,如:Label、Button等),的 Object ID 生成内容键值

  2. 根据语言修改对应 value 值即可,系统会自动获取xib 中控件 ID 然后匹配系统语言进行内容赋值


  3. 修改 Xcode 的语言设置,查看运行结果

  • 英文


  • 法语


6.2 在Interface Builder中预览本地化

在Interface Builder中,您可以在不运行应用程序的情况下预览用户界面的本地化。

  1. 在项目导航器中,选择要预览的文件.storyboard或.xib文件

  2. 选择视图>辅助编辑器>显示助手编辑器

  3. 在助手编辑器跳转栏中,打开“助手”弹出菜单,滚动并选择“预览”项,然后从子菜单中选择.storyboard或.xib文件(如果应用程序用户界面的预览未显示在助理编辑器中,请在图标或大纲视图中选择要预览的窗口或视图)

  4. 在助理编辑器中,从右下角的语言弹出菜单中选择要预览的本地化。(本地化的预览显示在助理编辑器中。如果选择实际语言,则不需要本地化或需要本地化但当前不是本地化的字符串将以大写形式显示)

6.3 添加新控件至 XIB

  1. 不会联动变化
    我们发现,添加新控件后,相应的 strings 文件没有自动发生变化⁉️

注意:Xib相应语言的strings一旦生成后,Base Xib有任何编辑都不会影响到strings,即删除或添加了一个Label,strings也不会同步做相应的改动

  1. 手动添加咯(当然是可以的)
  • 获取到对应 Label 的 ObjectID,然后参考之前生成 strings 之前已经添加到 xib 的 label 处理格式,分别修改对应语言的 strings 文件,如下:
  • 运行效果

6.3 自动化更新 strings 文件

  1. 方式1:使用ibtool生成新的.strings文件(后续会分析这个的使用步骤)
  • Xcode 为我们提供了 ibtool 工具来生成 Xib 的 strings 文件,命令如下
    ibtool XibController.xib --generate-strings-file ./XibController.strings
  • 但是这个ibtool翻译的键值对中的值是按照xib上控件上填写的文本来显示的,一般不是我们想要的,如果要实现更新,我们需要将XibController.strings和之前对应语言文件夹fr.lproj等的XibController.strings比较,将XibController.string中多出来的key-value取出来,追加到Main.string的尾部(实在麻烦)
  1. 方式2:使用脚本来实现自动追加新 key-value,删除不再存在的 key-value
    脚本内还是通过使用 ibtool
  • 逻辑是:假设原来我们就有翻译文件A,添加控件后,我们再执行一次国际化指令,生成文件B,我们拿A和B对比,把B中多出来的键值对插入A文件的尾部,将A中有而B中没有的键值对删掉(即控件被删除),这样我们就算是更新了storyboard的翻译文件了

  • 参照上述的逻辑,我们可以借助脚本文件来实现,Xcode的Run Script也提供了脚本支持,可以让我们在Build后执行脚本

2.1 新建 py 脚本


AutoGenStrings.py参考 demo 工程

2.2 选择:Target -> Build Phases -> New Run Script Phase,在shell里面写入下面指令

#!/bin/bash
python ${SRCROOT}/${TARGET_NAME}/RunScript/AutoGenStrings.py ${SRCROOT}/${TARGET_NAME}

2.3 你也可以选择Run script only when installing,这样在编译是不会云脚本,只有install的时候才会运行脚本

2.4 新添加控件之后,编译一下,对应的 strings 文件会增加新控件的 key-value 对,而删除的控件的的 key-value 对会被注释


之后有空会补上AutoGenStrings.py的解读,参考的脚本原先只有 storyboard 的处理,这里扩展至 xib

7 本地化Storyboard

  • 与 Xib 操作流程一致,请参考 Xib 的本地化流程

8 说说NSLocalizedString(key, comment)中 comment 参数的使用

第一个参数是:key的名字
第二个参数是:对这个“键值对”的注释,在用genstrings工具生成Localizable.strings文件时会自动加上去

一般情况下,语言翻译的操作并不是我们程序员源来做的(当然,nb的猿都会nb地接手这些工作),那么怎样才能够将语言本地化strings文件分发出去同时又能够很好的按流程工作呢?

8.1 直接在代码中使用NSLocalizedString进行臆想本地化实现

  • 此时的情况是本地没有任何 strings 文件,利用NSLocalizedString的查找逻辑,key 不存在时直接返回 key 值
  • 所以开发过程时,界面显示的就是 key 值,而推荐使用英文表示 key 的优点就是:当超出所支持语言时,默认使用英语。那使用英语翻译的 key 正好满足条件

8.2 通过 genstrings 将使用了NSLocalizedStringviewController.m文件,生成对应语言环境的 strings 文件

  • 当所有的.m文件都使用NSLocalizedString修改好之后,就可以动用genstrings工具了
  1. 启动终端,进入工程所在目录。
  2. 新建需要支持的语言.lproj 文件夹,位置默认放在项目根目录下
    目录名会作用到Localizable.strings文件对应的语言,所以目录名称不能写错了。
mkdir zh-Hans.lproj
mkdir en.lproj
  • 推荐通过 Xcode 帮我们生成,参考文章 “第1点:添加要支持的国际语言”,这样你还可以省去语言简写目录名的烦恼,直接从 Xcode 中选择你想要支持的国际语言

  • 选择语言


  • 生成的.lproj文件夹

  • 发现没有 en.lproj 文件夹,没关系,点选 main.storyboard,在文件的 localize 位置勾上 English 选项,同理 launch.storyboard 也同样操作

  • 出现了 en.lproj

  1. 生成Localizable.strings文件
genstrings -o zh-Hans.lproj *.m
genstrings -o en.lproj *.m
genstrings -o ja.lproj *.m

-o <文件夹>,指定生成的Localizable.strings文件放置的目录。
*.m,扫描所有的.m文件。这里支持的文件还包括.h, .java等。

  • 执行完genstrings -o zh-Hans.lproj *.m命令之后,对应的zh-Hans.lproj文件夹多了Localizable.strings,其他同理
  1. 生成Localizable.strings文件都拖拽到工程中,Xcode会自动合并成一个,并且对应生成的内容是按照前面NSLocalizedString(@"home", @"这个是用来在生成 strings 文件时,在对应的 key-value 行上的注释,用来提示翻译员或者猿们的相关提示")生成的。

  2. 之后我们将这些文件分发到翻译员(或者你自己手里)

注意:genstrings指令只能是该目录下的文件遍历,但不能实现递归遍历,要实现递归变量,可以使用下述命令
find ./ -name *.m | xargs genstrings -o en.lproj
这是shell组合指令find+xargsfind ./ -name *.m 会递归所有.m文件并存在一个数组中,这个数组经由pipe传递给了下一个指令,而xargs会将收到的数组一一分割,并交给genstrings执行。

9 应用内设置语言(实践后补上)

10 指定语言 bundle 获取本地化字符串(实践后补上)

  • 宏使用例子补充
#define NSLocalizedString(key, comment) \
        [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]
#define NSLocalizedStringFromTable(key, tbl, comment) \
        [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:(tbl)]
#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
        [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \
        [bundle localizedStringForKey:(key) value:(val) table:(tbl)]

11 启动图本地化(实践后补上)

12 优化(实践后补上)

REF

Demo

LanguageLocalizationDemo - 第1-7点使用
LocalizationGenStringsDemo - 第8点使用


文/Jacob_LJ(简书作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载需联系作者获得授权,并注明出处,所有打赏均归本人所有!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容