第七章——本土化【译】

iOS 的吸引力是全球性的——iOS 用户生活在许多国家,并使用多种语言。 您可以通过 国际化(internationalization)本土化/本地化(localization) 流程确保您的应用程序可以被全球用户使用。

国际化确保您的本地文化信息(如语言,货币,日期格式,数字格式等)不会硬编码到您的应用程序中。 本土化是根据用户的 语言(Language)区域格式(Region Format) 设置在应用程序中提供适当数据的过程。 您可以在 iOS 设置(Settings) 应用程序中找到这些设置(图7.1)。 选择 常规(General) 行,然后选择 语言和区域(Language & Region) 行。

图7.1语言和区域设置

在这里,用户可以设置他们的地区,如美国或英国。 (为什么苹果使用“地区”而不是“国家”?一些国家有不止一个地区有不同的设置,滚动 地区(Region) 中的选项来查看。)

苹果使国际化和本地化相对简单。 一个具有本地化API的优点的应用程序甚至不需要重新编译成其他语言或区域。 (顺便说一句,因为 国际化(internationalization)本地化(localization) 太长,有时会分别将它们简称为 i18nL10n

在本章中,您将首先将 WorldTrotter 应用程序国际化,然后将其本土化为西班牙语(图7.2)。

图7.2 本土化 WorldTrotter

国际化

在第一部分中,您将使用 NumberFormatterNSNumber 类将 ConversionViewController 进行国际化。

格式化器

在第4章中,您使用 NumberFormatter 的一个实例来设置 ConversionViewController 中的 Celsius 标签的文本。 NumberFormatter 具有一个 locale 属性,该属性设置为设备的当前区域设置。 每当使用 NumberFormatter 创建一个数字时,它将检查其 locale 属性并相应地设置格式。 所以 Celsius 标签的文字从一开始就已经国际化了。

Locale 知道不同地区如何显示符号,日期和小数,以及它们是否使用度量系统。 Locale 的实例表示一个地区对应的这些变量的设置。 当您访问 Locale 上的 current 属性时,会返回表示用户地区设置的 Locale 实例。 一旦你有这个 Locale 实例,你可以知道一些信息,如这个地区是否使用公制或者这个地区的货币符号是什么。

let currentLocale = Locale.current
let isMetric = currentLocale.usesMetricSystem
let currencySymbol = currentLocale.currencySymbol

即使 Celsius 标签已经国际化,但仍然存在问题。 将系统区域更改为西班牙。 选择活动方案弹出窗口,然后选择 Edit Scheme...(图7.3)。

图7.3 编辑方案

确保在左侧选择了 Run,然后选择顶部的 Options 选项卡。 在 Application Region 弹出窗口中,选择 Europe,然后选择 Spain(图7.4)。 最后 Close 关闭活动方案窗口。

图7.4 选择不同的地区

构建并运行应用程序。 在 ConversionViewController 上,点击文本字段,确保软件键盘可见。 您可能已经注意到一个区别:在西班牙,小数分隔符是一个逗号而不是一个句点(而且千位分隔符是一个句点,而不是一个逗号),所以在美国写的 123,456.789 的数字在西班牙将被写成 123.456,789

尝试键入多个十进制分隔符(逗号),并注意到应用程序愉快地接受它。哎呦! 您之前禁止多个十进制分隔符的代码检查的是一个小数点,而不是使用特定于区域设置的小数分隔符。 我们来解决这个问题。

打开 ConversionViewController.swift 并更新 textfield(_:shouldChangeCharactersIn:replacementString :) 以使用特定于区域设置的小数分隔符。

func textField(_ textField: UITextField,
      shouldChangeCharactersIn range: NSRange,
      replacementString string: String) -> Bool {

  let existingTextHasDecimalSeparator = textField.text?.range(of: ".")
  let replacementTextHasDecimalSeparator = string.range(of: ".")

  let currentLocale = Locale.current
  let decimalSeparator = currentLocale.decimalSeparator ?? "."

  let existingTextHasDecimalSeparator = textField.text?.range(of: decimalSeparator)
  let replacementTextHasDecimalSeparator = string.range(of: decimalSeparator)

  if existingTextHasDecimalSeparator != nil,replacementTextHasDecimalSeparator != nil {
    return false
  } else {
    return true
  }
}

构建并运行应用程序。 该应用程序不再允许您键入多个十进制分隔符,它以独立于用户区域选择的方式执行此操作。

但还是有一个问题。 如果您输入的小数分隔符不是数字的句点,则转换为摄氏不会发生——摄氏标签显示 “???”。 这里发生了什么? 在 fahrenheitFieldEditingChanged(_ :) 中,您正在使用 Double 类型的初始化器,它将一个字符串作为参数。 此初始化方法不知道如何处理使用十进制分隔符以外的其他字符串。

让我们使用 NumberFormatter 类修复这个代码。 在 ConversionViewController.swift 中,更新 fahrenheitFieldEditingChanged(_ :) 将文本字段的字符串转换为与区域设置无关的数字。

@IBAction func fahrenheitFieldEditingChanged(_ textField: UITextField) {

  if let text = textField.text, let value = Double(text) {
    fahrenheitValue = Measurement(value: value, unit: .fahrenheit)

  if let text = textField.text, let number = numberFormatter.number(from: text) {
    fahrenheitValue = Measurement(value: number.doubleValue, unit: .fahrenheit)
  } else {
    fahrenheitValue = nil
  }
}

使用 数字格式化器 的实例方法 number(from:) 将字符串转换为数字。 因为 数字格式化器 知道语言环境,所以它能够将字符串转换成一个数字。 如果该字符串包含有效的数字,该方法返回一个 NSNumber 的实例。 NSNumber 是一个可以表示各种数字类型的类,包括 IntFloatDouble 等。 您可以向 NSNumber 的实例请求其值表示为其中一个值。 你在这里做的是获得doubleValue 的数字。

构建并运行应用程序。 现在,您正在以 与地区设置无关 的方式转换字符串,文本字段的值正确转换为其摄氏度值(图7.5)。

图7.5 用逗号分隔符转换

基本国际化

国际化时,您会询问关于 Locale 实例的问题。 其实,Locale 只有一些 区域特定 的变量。 这就是本地化——为 不同区域和语言设置 创建特定于应用程序的替换。 本地化通常涉及为不同的区域和语言生成多个资源副本(如图像,声音和界面文件),或者创建和访问根据不同的语言而创建的 字符串表(strings tables) (本章稍后将会看到)。

在您进行本土化资源的过程之前,您必须了解一个 iOS 应用程序是如何处理本土化资源的。

当您在 Xcode 中构建目标时,将创建一个应用程序包。 您添加到 Xcode 中的目标的所有资源都将与可执行文件本身一起复制到此包中。

这个 bundle 在运行时被 Bundle 的一个实例表示为 main bundle 。 许多类与 Bundle 一起加载资源。

本土化资源将资源的另一个副本放在应用程序包中。 这些资源被组织成特定于语言的目录,称为 lproj 目录。 这些目录中的每一个都是后缀为 lproj 的本地名称。 例如,美国英语本地化是 en_US,其中 en 是英文代码,US 是美国的美国区域代码,所以美国英语资源的目录是 en_US.lproj。 (如果您不需要在资源文件中进行区域区分,则可以省略该区域。)这些语言和区域代码在所有平台上都是标准化的,而不仅仅是 iOS。

当一个软件包被请求资源文件的路径时,它首先查看该文件夹的根级别。 如果没有找到它,它会查看设备的区域设置和语言设置,找到相应的 lproj 目录,并在那里查找文件。 因此,只需通过本土化资源文件,您的应用程序将自动加载正确的文件。

本土化资源文件的一种做法是创建单独的故事板文件,并手动编辑每个文件中的每个字符串。 但是,如果您计划多个本土化,则此方法不能很好地扩展。 当您向本土化的故事板添加新的标签或按钮时会发生什么? 您必须将此视图添加到每个语言的故事板。这很不好。

为了简化本土化界面文件的过程,Xcode 有一个称为 基本国际化(base internationalization) 的功能。 基本国际化 创建了包含主界面文件的 Base.lproj 目录。 然后可以通过仅创建 Localizable.strings 文件来完成本土化各个界面文件。 仍然可以创建完整的界面文件,以防单独更改字符串无法进行本土化。 然而,借助自动布局,字符串替换就足以满足大多数本地化需求。 在下一节中,您将使用自动布局来准备您的布局进行本地化。

本地化准备

打开 Main.storyboard 并通过单击 ViewAssistant EditorShow Assistant Editor 或使用键盘快捷键 Option-Command-Return 显示助理编辑器。 从跳转栏下拉列表中,选择 Preview(图7.6)。 preview assistant 可让您轻松了解界面如何在屏幕尺寸和方向以及不同的本地化语言之间查看。

图7.6 打开预览助手

在故事板中,选择 Conversion View Controller 以查看其预览(图7.7)。

图7.7预览助手

注意预览助手的下方的控件。 左侧的 + 按钮允许您向预览画布添加其他屏幕尺寸。 这样您就可以轻松地看到界面的变化如何在屏幕尺寸和方向同时传播。 右侧的按钮允许您选择一种语言来预览此界面。

(如果您的预览是针对 iPhone 7 以外的配置,请使用 + 按钮添加此配置,然后单击默认打开的任何预览,然后按 Delete 键将其删除。)

您尚未将应用程序本地化为另一种语言,但 Xcode 提供了一种 伪语言(pseudolanguage) 供您使用。 在您收到所有字符串和 assets 的翻译之前,Pseudolanguages 可以帮助您将应用程序国际化。 内置的伪语言,双长度伪语言(Double-Length Pseudolanguage),通过重复文本元素中的任何文本字符串来模拟更详细的语言。 那么,比如说 “is really” 变成 “is really is really”。

Language 弹出窗口中,选择 Double-Length Pseudolanguage。 标签都将其文本翻倍(图7.8)。

图7.8 双字文本字符串

双长度伪语言表明了一些问题:标签在屏幕的左右两边都会关闭,您无法读取整个字符串。 解决方法是限制所有的标签,使其前后边缘保持在他们的父级视图的边缘。 然后,将标签的行数更改为 0,它表明其文本有需要的话会自动换行。 首先我们会修复一个标签,然后在其余标签上重复步骤。

在画布上,选择 degrees Fahrenheit 标签。 您将以新的方式为此标签添加约束。 选中标签右键拖动到父级视图的左侧。 当您这样做时,会出现一个上下文相关的弹出窗口,为您提供有意义的约束条件(图7.9)。 从列表中选择 Leading Space to Container Margin

图7.9 通过右键拖动创建约束

您拖动的方向会影响可能显示的约束。 水平拖动将显示水平约束,垂直拖动将显示垂直约束。 对角拖动将显示水平和垂直约束,这对于同时设置许多约束是有用的。

现在选中 degrees Fahrenheit 标签右键拖动到父级视图的右侧,并选择 Trailing Space to Container Margin

对它们来说,这些约束不是很好。 它们会保持标签前后边缘之间的现有固定距离,如预览助手所示(图7.10)。

图7.10 具有新约束的预览助手

你真正想要的是标签和边距之间的距离大于或等于 0.你可以用 不等式约束(inequality constraints) 来做到这一点。

通过单击标签左侧的 指示线 来选择主要约束。 打开其属性检查器,将 Relation 更改为 Greater Than or Equal,并将 Constant 更改为 0(图7.11)。

图7.11 不等式约束

尾部(trailing) 约束执行相同操作。 看看预览助手; 界面看起来更好,但是标签仍然被截断。

选择标签并打开其属性检查器。 将 Lines 更改为 0.现在来看看预览助手; 该标签不再被截断,而是文本流向第二行。 因为其他标签每个都与他们上面的标签相关,所以它们已经被自动下移。

对其他标签重复上述步骤。 您将需要:

  • 向每个标签添加一个前部和尾部约束。
  • 将约束的关系设置为 Greater Than or Equal,常数为 0.(编辑约束的快捷方式是双击它。)
  • 将标签的 Lines 更改为0。

完成后,双长度伪语言的预览助手将如图7.12所示。

图7.12 具有最终约束的预览助手

此时,您会用预览助手了。 您可以使用右上角的 x 关闭助理编辑器。

本地化

WorldTrotter 现在已经国际化——其界面能够适应各种语言和地区。 现在是时候本地化应用程序——也就是更新不同地区和语言下应用程序的字符串和资源。 在本节中,您将本地化 WorldTrotter 的界面:Main.storyboard 文件。 您将创建英语和西班牙语本地化,除了基础的一个之外,还将创建两个 lproj 目录。

从本地化一个故事板文件开始。 在项目导航器中选择 Main.storyboard

通过单击检查器选择器中的


选项卡或使用键盘快捷键 Option-Command-1 打开 文件检查器(file inspector)。 查找此检查器中名为 Localization 的部分。 检查 English 框,并确保下拉列表显示 Localizable Strings(图7.13)。 这将创建一个字符串表,您稍后将使用它来本地化应用程序。

图7.13 本地化为英文

接下来,在项目导航器中,选择顶部的 WorldTrotter 项目。 然后在侧面列表中的 Project 部分下选择 WorldTrotter,并确保 Info 选项卡已打开。 (如果看不到侧面列表,可以使用左上角的 Show projects and targets list 列表按钮打开它(图7.14)。)

图7.14 显示项目设置

单击 Localizations 下的 + 按钮,然后选择 Spanish (es)。 在对话框中,您可以取消选中 LaunchScreen.storyboard 文件; 确保 Main.storyboard 文件被选中。 确保参考语言为 Base,文件类型为 Localizable Strings。 单击 Finish。 这将创建一个 es.lproj 文件夹,并生成包含从基本界面文件的所有字符串的 Main.strings 文件。 Localizations 配置应如图7.15所示。

图7.15 Localizations

查看项目导航器。 单击 Main.storyboard 旁边的闭合按钮(图7.16)。XcodeMain.storyboard 文件移动到 Base.lproj 目录,并在 es.lproj 目录中创建 Main.strings 文件。

图7.16 项目导航器中的本地化storyboard文件

点击 Spanish 版本的 Main.strings。 当此文件打开时,文本不是西班牙文。 你必须自己翻译本地化的文件; Xcode 并不会这么智能

根据以下文本编辑此文件。 您的文件中的数字和顺序可能不同,但您可以利用注释中的 texttitle 字段来匹配翻译。

/* Class = "UITabBarItem"; title = "Map"; ObjectID = "6xh-o5-yRt"; */
"6xh-o5-yRt.title" = "Map" "Mapa";

/* Class = "UILabel"; text = "degrees Celsius"; ObjectID = "7la-u7-mx6"; */
"7la-u7-mx6.text" = "degrees Celsius" "grados Celsius";

/* Class = "UILabel"; text = "degrees Fahrenheit"; ObjectID = "Dic-rs-P0S"; */
"Dic-rs-P0S.text" = "degrees Fahrenheit" "grados Fahrenheit";

/* Class = "UILabel"; text = "100"; ObjectID = "Eso-Wf-EyH"; */
"Eso-Wf-EyH.text" = "100";

/* Class = "UITextField"; placeholder = "value"; ObjectID = "On4-jV-YlY"; */
"On4-jV-YlY.placeholder" = "value" "valor";

/* Class = "UILabel"; text = "is really"; ObjectID = "wtF-xR-gbZ"; */
"wtF-xR-gbZ.text" = "is really" "es realmente";

/* Class = "UITabBarItem"; title = "Convert"; ObjectID = "zLY-50-CeX"; */
"zLY-50-CeX.title" = "Convert" "Convertir";

现在你已经完成本地化这个故事板文件,让我们来测试一下。 首先,有一点 Xcode 的小问题要注意:有时 Xcode 会在构建应用程序时忽略资源文件的更改。 为确保您的应用程序从零开始构建,请先从设备或模拟器中卸载。 (按住启动器中的图标,当它开始摆动时,点击删除图标。)重新启动 Xcode。然后,从 Product 菜单中选择 Clean。 最后,在打开 Product 菜单时按住 Option 键,然后选择 Clean Build Folder .... 这将强制应用程序被完全重新编译,重新绑定并重新安装。

打开活动方案弹出窗口并选择 Edit Scheme。 确保在左侧选择 Run,然后打开 Options 选项卡。 打开 Application Language 弹出窗口并选择 Spanish。 最后,确认在 Application Region 弹出窗口中选择 Spain 。 关闭窗口。

构建并运行应用程序。 确保当你查看 ConversionViewController 时,您看到的是西班牙语界面。 因为您在标签上设置约束以适应不同长度的文本,因此他们会自己调整大小(图7.17)。

图7.17 西班牙语版的 ConversionViewController

NSLocalizedString 和字符串表

在应用程序的许多地方,您可以动态创建 String 实例,或向用户显示字符串文字。 要显示这些字符串的翻译版本,您必须创建一个字符串表。 字符串表是包含应用程序使用的所有字符串及其关联翻译的键值对列表的文件。 它是一个添加到您的应用程序的资源文件,但您不需要做很多工作来获取数据。

您可以在代码中使用一个字符串,如下所示:

let greeting = "Hello!"

要使您的代码中的字符串国际化,您可以使用函数 NSLocalizedString(_:comment :) 替换文字字符串。

let greeting = NSLocalizedString("Hello!", comment: "The greeting for the user")

此函数有两个参数:一个 key 和一个描述字符串使用的 注释(comment)。 key 是字符串表中的查找值。 在运行时,NSLocalizedString(_:comment :) 将查看与您的应用程序绑定的字符串表,该表与用户的语言设置相匹配。 然后,在该表中,该函数获取与该 key 相匹配的翻译字符串。

现在,您将使 MapViewController 在其分段控件中显示的字符串国际化。 在 MapViewController.swift 中,找到 loadView() 方法,并更新分段控件的初始化方法以使用本地化字符串。

override func loadView() {
  // Create a map view
  mapView = MKMapView()

  // Set it as *the* view of this view controller
  view = mapView

  let segmentedControl = UISegmentedControl(items: ["Standard", "Satellite", "Hybrid"])

  let standardString = NSLocalizedString("Standard", comment: "Standard map view")
  let satelliteString = NSLocalizedString("Satellite", comment: "Satellite map view")
  let hybridString = NSLocalizedString("Hybrid", comment: "Hybrid map view")
  let segmentedControl = UISegmentedControl(items: [standardString, satelliteString, hybridString])

一旦拥有 NSLocalizedString(_:comment :) 函数进行国际化的文件,就可以使用命令行命令来生成字符串表。

打开终端应用程序 这是一个 Unix 终端 它用于运行命令行工具。 您要导航到 MapViewController.swift 的位置。 如果你以前从未使用过终端应用程序,这里是一个方便的技巧。 在终端中,键入以下内容:

cd

其次是空格。 (现在不要按 Return 或 回车)

接下来,打开 Finder 并找到 MapViewController.swift 和包含它的文件夹。 将该文件夹的图标拖到终端窗口。 终端将为您填写路径。 它看起来像这样:

cd /Users/cbkeur/iOSDevelopment/WorldTrotter/WorldTrotter/

按回车。 Terminal 的当前工作目录现在是这个目录。

使用终端命令 ls 打印工作目录的内容,并确认 MapViewController.swift 在该列表中。

要生成字符串表,请在终端中输入以下内容,然后按 Return:

genstrings MapViewController.swift

生成的文件 Localizable.strings 包含 MapViewController 中的字符串。 将此新文件从 Finder 拖到项目导航器中(或使用 FileAdd Files to "WorldTrotter"... 菜单项)。 编译应用程序时,该资源将被复制到主包中。

打开 Localizable.strings。 文件应该是这样的:

/* Hybrid map view */
"Hybrid" = "Hybrid";

/* Satellite map view */
"Satellite" = "Satellite";

/* Standard map view */
"Standard" = "Standard";

请注意,您的字符串上方的注释是您提供给 NSLocalizedString 函数的第二个参数。 即使该comment参数不是必要的,但它将使您的本地化更轻松。

现在你已经创建了 Localizable.strings,你需要在 Xcode 中进行本地化。 打开其文件检查器,然后单击实用程序区域中的 Localize... 按钮。 确保从弹出窗口中选择 Base,然后单击 Localize。 选中每种语言旁边的框,添加 Spanish 和 English localization。

在项目导航器中,单击 Localizable.strings 旁边的闭合三角形。 打开西班牙语版本。 左侧的字符串是传递给 NSLocalizedString(_:comment :) 函数的 key,右侧的字符串是返回值。 将右侧的文本更改为下面显示的西班牙语翻译。 (要键入重音字符,例如 é,请按住键盘上的相应字符,然后从弹出窗口中按相应的数字。)

/* Hybrid map view */
"Hybrid" = Hybrid "Híbrido";

/* Satellite map view */
"Satellite" = Satellite "Satélite";

/* Standard map view */
"Standard" = Standard "Estándar";

再次构建并运行该应用程序。 现在,所有这些字符串(包括分段控件中的标题)将以西班牙语显示(图7.18)。 如果没有,您可能需要删除应用程序,清理项目并重建。 (或检查您的 scheme 语言设置。)

图7.18 西班牙语的 MapViewController

国际化和本地化对于您的应用来说是非常重要的,这样可以吸引更多的用户。 另外,正如你在本章的初期看到的那样,如果你的应用程序没有适当的国际化,你的应用程序可能不适用于某些用户。 您将在本书其余部分中将您的项目国际化(但不是本地化)。

在过去五章中,您已经构建了一个令人印象深刻的应用程序,允许用户在摄氏和华氏之间进行转换,并以几种不同的方式显示地图。 这个应用程序不仅在所有 iPhone 屏幕尺寸上都很好地扩展,而且还被本地化为另一种语言。

青铜挑战:另一个本地化版本

实践: 将 WorldTrotter 本地化为另一种语言。 如果您需要帮助语言,请使用翻译网站。

更多:包在国际化中的作用

利用本地化的实际工作是由 Bundle 类完成的。 bundle 表示文件系统上将编译的代码和资源组合在一起的位置。 主包(main bundle) 是应用程序包的另一个名称,它包含应用程序的所有资源和可执行文件。 您将在第 16 章中了解有关应用程序包的更多信息。

当构建应用程序时,所有 lproj 目录都将被复制到主包中。 图7.19显示了 WorldTrotter 的主包(添加了一些其他图像)。

图7.19 应用程序包

Bundle 知道如何使用实例方法 url(forResource:withExtension :) 来搜索每种资源类型的本地化目录。 当您想要通过应用程序捆绑的资源的路径时,您可以在主包上调用此方法。 这里是一个使用资源文件 Boo.png 的例子:

let path = Bundle.main.url(forResource:"Boo", withExtension: "png")

当尝试找到资源时,包首先检查资源是否存在于应用程序包的顶层。 如果是这样,它会将完整的URL返回给该文件。 如果没有,该包将获取设备的语言和区域设置,并在相应的 lproj 目录中查找以构造URL。 如果仍然找不到它,它会在 Base.lprojd 中查找。 最后,如果没有找到文件,则返回 nil

在图7.19所示的应用程序包中,如果用户的语言设置为西班牙语,则 Bundle 将在顶级查找 Boo.png, 接着是 es.lproj 中的Tom.png,最后是 Base.lproj 中的 Hat.png

当您向项目添加新的本地化时,Xcode 不会自动从顶级目录中删除资源。 这就是为什么您在本地化文件时必须删除和清理应用程序的原因 - 否则,以前的未本地化文件仍将处于应用程序包的根级别。 即使应用程序包中有 lproj 文件夹,bundle 也会首先找到顶级文件并返回其URL。

更多:导入和导出为XLIFF

本地化数据的行业标准格式是 XLIFF 数据类型,代表 XML 本地化交换文件格式(XML代表可扩展标记语言)。 当使用翻译器时,您将经常向他们发送一个包含应用程序中数据的 XLIFF 文件进行本地化,并将返回一个本地化的 XLIFF 文件供您导入。

Xcode 本身支持在 XLIFF 中导入和导出本地化数据。 导出过程将负责查找和导出项目中使用 genstrings 工具手动生成的本地化字符串。

要导出XLIFF中的本地化字符串,请在项目导航器中选择项目(WorldTrotter)。 然后选择 Editor 菜单,然后 Export For Localization.... 。在下一个屏幕上,您可以选择是否导出现有的翻译(这可能是一个好主意,因此翻译不会进行冗余工作)以及您想要的语言导出(图7.20)。

图7.20 将本地化数据导出为 XLIFF

要导入本地化,请在项目导航器中选择项目(WorldTrotter)。 然后选择 EditorImport Localizations...。 选择文件后,您将能够在导入之前确认是否更新。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 介绍 捆绑是macOS和iOS中用于封装代码和资源的基础技术。软件包通过为所需资源提供已知位置来简化开发人员体验,...
    nicedayCoco阅读 1,222评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,806评论 25 707
  • 文/绝择衣 图源:小妖精美化APP 我始终相信玩弄感情的人,哪怕只有一次也会被感情反过来玩弄很多次。所以,...
    绝择衣阅读 257评论 0 1