备案 控制台
开发者社区 开发与运维 文章 正文

Web应用程序中Resource Bundle技术概述

简介:

背景概述

世界经济日益全球化的同时,一个应用程序需要在全球范围内使用势在必然。传统的程序设计方法将可翻译信息如菜单按钮的标签,提示信息,帮助文档等文字信息硬编码在程序代码中,不能很好的适应全球化发展,程序的扩展性差,维护成本高。一个能支持全球化的应用程序,必须实现单一可执行的程序,动态地使用资源(Single Source Single Executable),图1 是两种模型的对比图。

图 1. 传统的程序模型和支持全球化程序模型

Web应用程序中Resource Bundle技术概述

Web应用程序中Resource Bundle技术概述

对于一个能支持全球化的应用程序来说:

一方面需要考虑的是用户的语言环境(我们称作 Language Locale):这里主要就是指翻译,在不同的国家用他们自己的语言正确运作软件,让客户感觉这个产品是为他们而设计的;

另一方面就是用户的文化环境(我们称作 Culture Locale):主要处理的是多元文化的支持,包括货币、日历、时间、日期、排序、界面方向性(Bi-directional) 等符合各个国家自己习惯的显示方式。

图2 概述了如何使得一个应用程序(C/S 或者 B/S)支持全球化和本地化。

图 2. 全球化应用程序

Web应用程序中Resource Bundle技术概述 Web应用程序中Resource Bundle技术概述

本文结合项目实践,总结了 web 应用程序中 Java,JSP,Dojo 和 HTML 四种不同语言是如何管理 Resource Bundle 的,实现单一可执行程序动态读取资源文件,从而支持全球化和本地化。主要从三个方面来阐述:资源文件存储和命名规则;用户语言区域信息的读取;如何取得对应语言的资源文件中的键值。

Java 程序中的 Resource Bundle 管理

ResourceBundle 是一个机制,主要用来根据用户的语言环境展示不同的界面文字给用户,让用户感觉这个应用程序为我而定制。

然而 Java 中的 ResourceBundle 是一个类,包含在标准的 Java 发行版中。图3总结了 Java 程序中 ResourceBundle 的管理机制。

图 3. Java 程序 Resource Bundle 管理流程

Web应用程序中Resource Bundle技术概述

Web应用程序中Resource Bundle技术概述

Java 程序中资源文件的存储和命名

在一个多模块的 Java 应用程序中,一般每个模块都有自己独立的资源文件(也叫 Resource Bundles),Resource Bundle 一般存储在对应模块的 src/resources/bundles/java/ 目录下面。通常的命名规则是:模块名_语言_国家 .properties({moduleName}_{language}_{country}.properties)。对应资源文件中的每一个 key,一般都是小写字母开头,用下划线表示这个 key 在程序中的层级结构,并且按照字母顺序排序,便于管理和查找,如清单1所示的例子。

清单 1. Java properties 文件示例

 
  1. 英文Properties 文件: 
  2.     helloKey=Hello! 
  3.     goodMorningKey=Good Morning! 
  4.     goodEveningKey=Good Evening! 
  5.      
  6. 日语Properties 文件: 
  7.     helloKey=\u3053\u3093\u306b\u3061\u306f! 
  8.     goodMorningKey=\u304a\u306f\u3088\u3046! 
  9.     goodEveningKey=\u3053\u3093\u3070\u3093\u306f! 

Java 程序对资源文件的回滚机制:当对应翻译的资源文件不存在时,将使用"默认"的资源文件(通常为英文),图 4 是 Java 资源文件的组织方式。

图 4. Java 资源文件组织方式

Web应用程序中Resource Bundle技术概述

Java 中用户语言环境(Locale)和资源文件的读取

根据用户使用的区域信息来决定从哪个 Resource Bundle 里面读取对应的 key 值。Java 语言通过 java.util.Locale 类来表示区域,一个 Locale 实例就代表了一个特定的区域。在实际的项目中,通常将读取资源文件包装成一个类,方便后续代码的重用和管理,如清单 2 的示例所示。

清单 2. Java 中 ResourceBundleService 使用

 
  1. public class LocalizedPropertyResources extends MessageResources {  
  2. private static final String MODULE_NAME = "resourceBundle.module.name";  
  3. public String getProperty(String key, Object... parameters) {  
  4. Locale locale = getUserLocale();  
  5. String value = getProperty(key, locale, parameters);  
  6. Return value;  
  7. }  
  8. public String getProperty(String key, Locale locale, Object... parameters) {  
  9. ResourceBundle resourceBundle = ResourceBundle.getBundle(MODULE_NAME, locale);  
  10. String value resourceBundle.getString(key, parameters);  
  11. return value;  
  12. }  

JSP 中的 Resource Bundle 管理

资源文件管理:JSP 是基于 Java 技术,所以 Java 中的 ResourceBundle 机制也可以应用于 JSP,同样以 .properties 形式存储资源文件,资源文件的命名规则、资源文件的存储结构,key 的命名规则等都可以遵循 Java 程序中的 Resource Bundle 管理机制。

用户语言环境的设定:HTTP 协议通过 Accept-Language 请求头将本地化信息从浏览器传递至服务器, JSTL fmt 库中的定制标记又会利用这些方法来自动地确定用户的语言环境,从而相应地调整它们的输出。同时用户也可以通过 来设置用户语言环境。

对于 JSP 中资源文件的读取有以下三种情况:

1、使用 fmt 消息标签:JSP 标准标签库(JSP Standard Tag Library,JSTL)中的 fmt标签支持通过资源文件(ResourceBundle)对文本内容进行本地化,该功能可以对一个特定的语言请求作出相应的响应,它使用了 J2SE 的 ResourceBundle 来保持各种翻译过的语言编码。另外用来设置地区,比如,这等于设定了语言和国家代码。默认 JPS 将读取 Accept-Language 信息。还可以指定 ResourceBundle,比如: 。一旦设定了 locale(地区)或 ResourceBundle,就可以使用 来把原文进行相应的转化,同时还可以使用< fmt:requestEncoding/>来设定请求的字符编码。如清单3 示例所示:

清单 3. JSTL fmt 标签示例

 
  1. //myTest.jsp 
  2. <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> 
  3.  
  4. <html> 
  5.   <head> 
  6.     <title>JSTL fmt: Localized tag testing</title> 
  7.   </head> 
  8.  
  9.  <body> 
  10.   <fmt:requestEncoding value="UTF-8" /> 
  11.   <fmt:setLocale value="zh_TW"/> 
  12.   <fmt:setTimeZone value= "GMT+8" scope="request"/> 
  13.   <fmt:bundle basename="src.resources.bundles.java.menu "
  14.       <fmt:message key="OK"/><br/> 
  15.       <fmt:message key="CANCEL"/><br/> 
  16.   </fmt:bundle> 
  17. //也可以通过下面的方式获取资源文件 
  18.  
  19.   <fmt:setBundle basename="src.resources.bundles.java.menu"    var="resources"/> 
  20.     … 
  21.     <fmt:message key="OK"/><br/> 
  22.     <fmt:message key="CANCEL"/><br/> 
  23.      … 
  24.   </body> 
  25. </html> 

2、被 Spring 管理的 JSP,通常 JSP 中有这样的 taglig"",这种情况下可以使用 Spring message 标签。

 
  1. <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>  
  2. <spring:message code="common_button_ok"/> 

3、对于由 Struts 操作类提供的JSP,可以使用 Struts bean 消息标签:

 
  1. <%@ taglib uri="/tags/struts-bean" prefix="bean" %>  
  2. <bean:message key=" common_button_ok "/> 

Dojo 中的 Resource Bundle 管理

Dojo 是一个 JavaScript 库,提供了一个有用的工具来创建丰富的 Web 客户端界面。同时 Dojo 也提供了 API 支持全球化功能,如翻译资源包,函数的格式化和解析日期、数字和货币,Dojo 对全球化的支持主要基于 CLDR (Common Locale Data Repository) 和 ICU 的使用。 这一小节总结了 Dojo 中资源文件的管理,用户区域信息(locale)和资源文件的读取,如图5所示。

图 5. Dojo 中资源文件管理

Web应用程序中Resource Bundle技术概述

Web应用程序中Resource Bundle技术概述

Dojo 中资源文件的存储和命名

UTF-8 是 Dojo 程序编码的先决条件,Dojo 脚本一般将使用 HTML,JSP,Servlet,js等编写,所以这些文件必须以 UTF-8 编码。Dojo 用 JSON 格式来组织资源文件,首先在 /nls 目录下有一个主资源文件(一般是英文),主资源文件中定义了支持的区域,区域的值设置为 true 或者 false,如清单4所示。

清单 4. Dojo 主资源文件的定义

 
  1. define({ 
  2.       root: { 
  3.         OK: "Ok"
  4.         CANCEL: "Cancel"
  5.         OK_CANCEL: "${OK}, ${CANCEL}" 
  6.       }, 
  7.       "ko"true
  8.       "ja"true
  9.       "ru"false
  10.       "zh-cn"true 
  11.     }); 

翻译的资源文件应放在/ nls / 目录中,目录的命名需要符合下面的规范:

  • 目录的名称必须要全部小写
  • 用横线(而不是下划线)分割语言和国家,比如 zh-cn,zh-tw 等,如清单5所示。

清单 5. 翻译资源文件的目录结构

 
  1. /src/web/js/nls/menu.js       ... 主资源文件,用于默认的消息显示 
  2.     /src/web/js/nls/ko/menu.js    ... 韩语的翻译文件 
  3.                     /ja/menu.js    ... 日语的翻译文件 
  4.                     /ru/menu.js    ...俄语的翻译文件 
  5.                      /zh-cn/menu.js  ...简中的翻译文件 
  6.                     ... 

Dojo 资源文件的回滚机制为:在 / nls 目录的根目录下如果检测到区域设置,但 nls 目录中不存在特定于区域设置的资源,则将使用主包;当主资源文件中某个语言设置为 false 时,即使该语言的资源文件存在,也将使用主包。

Dojo 中用户语言环境(Locale)和资源文件的读取

Dojo 和 Java 以相同的方式支持用户区域信息,但 Dojo 和 Java 实现之间存在微小差异。

  • "Dojo 和 Java 对区域设置命名约定有一些区别,Dojo 使用" - "(连字符)作为连接语言代码,国家代码和变体的分隔符,而 Java 使用"_"(下划线)。例如,Java 中的 "zh_CN" 与 Dojo 中的 "zh-cn" 相似。
  • 像 Java 中的默认用户语言区域一样,Dojo 有一个全局变量 dojo.locale 用来存储默认 locale 值,但是我们不能直接修改 dojo.locale,可以通过 dojoConfig.locale 来对 dojo.locale 做初始化工作。
  • 如果 dojoConfig.locale 未定义,Dojo 使用浏览器的显示语言作为用户区域设置。另外 dojoConfig.locale 的设定需要在加载 dojo.js 之前,这样设置的 dojoConfig.locale 才会起效果。如清单 6 是一个示例将用户的 locale 信息保存在 sessionScope,然后传递给 dojoConfig。

清单 6. 将用户 locale 保存在 sessionScope 传递给 dojoConfig

 
  1. var dojoConfig = { 
  2.             async: 'sync'
  3.             baseUrl: ''
  4.             locale: '${sessionScope.userLanguageLocale}'
  5.              
  6.              bindEncoding: "utf-8"
  7.                 … 
  8.         }; 

userLanguageLocale:是应用程序中用户所设定的 locale,每个应用程序可能不太一样。

Dojo 中有两种方式读取资源文件:

方式 1:又叫 AMD 方式,通过 dojo/i18n! 这个插件加载资源文件,首先检测 dojoConifg.locale 是否设置,如果设置了就读取 dojoConfig.locale,否则就会使用浏览器的显示语言读取对应的翻译资源文件。清单 7 是 dojo/i18n! 的使用示例。

清单 7. Dojo/i18n!使用示例

 
  1. ...  
  2. <script src="dojo-release-1.9.1/dojo/dojo.js" data-dojo-config="async: true, parseOnLoad: true></script>  
  3. <script>  
  4. require(["dojo/string""dojo/i18n!js/nls/menu"],  
  5. function(string, resources){  
  6. var strOk = resources.OK;  
  7. alert(strOk);  
  8. });  
  9. </script>  
  10. ... 

方式 2: 使用 dojo.requireLocalization 加载资源文件,dojo.i18n.getLocalization 读取资源文件,这个是旧的资源加载方式,新的 Dojo 版本都将采用 AMD 的方式,不过这种方式用户可以指定 locale 信息,缺省 locale 的时候,采用和 AMD 同样的 locale 解析方式。清单8 是一个示例。

清单 8. Dojo. requireLocalization 使用示例

 
  1. <script src="dojo-release-1.9.1/dojo/dojo.js" data-dojo-config="async: true, parseOnLoad: true></script>  
  2. <script>  
  3. dojo.require("dojo.i18n");  
  4. dojo.requireLocalization("js""menu");  
  5. var resources = dojo.i18n.getLocalization("js""menu", locale);  
  6. //或者省去 locale 参数,var resources = dojo.i18n.getLocalization("js""menu")  
  7. alert(resources.OK);  
  8. </script>  
  9. ... 

HTML 中的 Resource Bundle 管理

HTML 中的硬编码(Hard Code)通常分为两种情况:一种情况是 HTML 页面不涉及任何编程逻辑,这时候可以翻译整个 HTML 文件(这种方式在开发过程中不需要特殊处理),或者将 HTML 变更为 JSP,这样就可以利用 JSP 中 fmt 标签将 Hard Code 提取出来,如清单 9 示例所示。

清单 9. HTML 更改为 JSP

 
  1. //Index.html  
  2. <html>  
  3. <head>  
  4. <script language="JavaScript">  
  5. window.location = "/login.do";  
  6. </script>  
  7. <title> Welcome Page </title>  
  8. </head>  
  9. <body>  
  10. …  
  11. </body>  
  12. </html>  
  13. //将 Index.html 更改为 Index.jsp,这样就可以利用 JSP 中 fmt 标签  
  14. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>  
  15. <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>  
  16. <html>  
  17. <head>  
  18. <script language="JavaScript">  
  19. …  
  20. </script>  
  21. <title> <fmt:message key="common_welcome" /></ title>  
  22. </head>  
  23. <body>  
  24. …  
  25. </body>  
  26. </html>  
  27. //test.properties 
  28.  Common_welcome=Welcome Page  
  29. … 

//将 Index.html 更改为 Index.jsp,这样就可以利用 JSP 中 fmt 标签

另外一种情况是 HTML 作为 Dojo widget 模板文件,这种情况下可以使用 dojo API 的 i18n 库来处理硬编码消息,其处理方式和 Dojo 很类似,模板 HTML 文件中使用的 Key 需要在 Widget 的 js 文件中定义和获取。下面将通过示例介绍 HTML 作为 Dojo 模板文件时,如何抽取当中的 hard code 信息。通常分三个步骤完成模板 HTML 文件中的字符串抽取。

第一步: 将 HTML 中的字符串放到一个资源文件如 message.js 中,资源文件的组织方式和 Dojo 是一样的;

第二步: 在 Dojo widget 的 js 文件中使用 Dojo Resource Bundle 获取资源文件,定义变量获取对应的Key值;

第三步: 在HTML模板文件中使用 Dojo .js 文件中定义的变量。如清单 10 所示。

清单 10. HTML 作为 Dojo Widget 模板

 
  1. Test.html  
  2. <div dojoAttachPoint="testNode" style="display:inline">  
  3. <table style="position: relative; margin: 20px 0 0 120px; padding: 0;">  
  4. <tr>  
  5. <td style="text-align: right;">Username:</td> //原来的 hard code  
  6. <td style="text-align: right;">${usernameLabel}</td> //Resource out 之后的变量引用  
  7. ….  
  8. </tr>  
  9. </div>  
  10. Test.js  
  11. dojo.requireLocalization("js""message");  
  12. dojo.declare("Test") {  
  13. templatePath: dojo.moduleUrl("common"" /html/Test.html"),  
  14. usernameLabel: "",  
  15. postMixInProperties: function(){  
  16. this.inherited(arguments);  
  17. var resources = dojo.i18n.getLocalization("js""message");  
  18. this.usernameLabel = resources.labels.username; //获取资源文件  
  19. }  

清单 11. HTML 中特殊硬编码处理

 
  1. // Hard Code  
  2. <html>  
  3. <body>  
  4. <select id="Product">  
  5. <option>Is Not</option>  
  6. <option>Is</option>  
  7. </select>  
  8. </body>  
  9. </html>  
  10. //处理后的 HTML  
  11. <html>  
  12. <body>  
  13. <select id="Product">  
  14. <option value="Is Not"> ${ Label.IS_NOT}</option
  15. <option value="Is">${Label.IS}</option>  
  16. </select>  
  17. </body>  
  18. </html> 

总结

本文总结了如何存储 web 应用程序中使用不同框架时的资源文件及其命名规则,如何取得资源文件中的键值。希望这篇文章能为正在开发国际化 Web 应用程序的读者提供一定的参考价值。


本文作者:佚名

来源:51CTO

行者武松
目录
相关文章
sunrr
|
5天前
|
前端开发 JavaScript 开发者
Web技术标准是如何定义网页的?
【6月更文挑战第2天】Web技术标准是如何定义网页的?
sunrr
22 2
sunrr
|
5天前
|
JavaScript 前端开发 开发者
Web技术标准
【6月更文挑战第2天】Web技术标准
sunrr
14 4
小万哥丶
|
7天前
|
XML 网络协议 Java
XML Web 服务技术解析:WSDL 与 SOAP 原理、应用案例一览
XML Web服务是基于WSDL、SOAP、RDF和RSS等标准的网络应用程序组件技术。WSDL描述服务接口和消息格式,SOAP用于结构化信息交换,RDF描述网络资源,RSS则用于发布网站更新。Web服务特点是自包含、自描述,基于开放协议,可重用且能连接现有软件。WSDL文档包含`types`、`message`、`portType`和`binding`元素,定义服务操作和协议。SOAP协议规定消息格式,通过HTTP等传输。
小万哥丶
462 1
西兰花加满
|
7天前
|
前端开发 JavaScript Java
Java语言在Web前端开发中的技术应用
Java语言在Web前端开发中的技术应用
西兰花加满
30 0
前端菜鸟的自我修养
|
8天前
|
JavaScript 前端开发
基于 Node.js 环境,使用内置 http 模块,创建 Web 服务程序
基于 Node.js 环境,使用内置 http 模块,创建 Web 服务程序
前端菜鸟的自我修养
16 2
vohelon
|
8天前
|
存储 人工智能 应用服务中间件
Web应用是一种通过互联网浏览器和网络技术在互联网上执行任务的计算机程序
【5月更文挑战第30天】Web应用是一种通过互联网浏览器和网络技术在互联网上执行任务的计算机程序
vohelon
23 2
全全.
|
10天前
|
SQL 缓存 监控
基于Java的Web应用性能优化技术
基于Java的Web应用性能优化技术
全全.
26 3
sunrr
|
13天前
|
开发框架 前端开发 .NET
web技术
【5月更文挑战第25天】web技术
sunrr
24 2
史迪奇2号
|
Java
[Java Web] 3、WEB开发之HTML基础程序试手
史迪奇2号
929 0
游客5fdji2pvmf8888
|
2天前
|
关系型数据库 MySQL PHP
PHP的生命周期:从诞生到现代Web开发
本文将探索PHP的发展历程,从其最初的设计目标到成为现代Web开发的核心语言。我们将深入了解PHP如何适应不断变化的技术环境,并保持其在开发者社区中的受欢迎程度。
游客5fdji2pvmf8888
23 7

热门文章

最新文章

  • 1
    双十一护航团队说:我们是如何防住5亿次Web攻击的?
  • 2
    好程序员web前端培训分享React学习笔记(一)
  • 3
    创建自定义配置节点(web.config和app.config都适用)
  • 4
    利用HttpModuler实现WEB程序同一时间只让一个用户实例登陆
  • 5
    AJAX联手SOA 打造新一代Web服务应用程序
  • 6
    Windows 2000配置Web服务器
  • 7
    Xenu-web开发死链接检测工具应用
  • 8
    【ASP.NET Web API教程】2.3.5 用Knockout.js创建动态UI
  • 9
    Web SSM 入坑
  • 10
    LoadRunner启动自带例子Web Tours报错
  • 1
    实验目的1.编译安装httpd2.优化路径3.并将鲜花网站上传到web服务器为网页目录4.在客户机访问网站http://www.bdqn.com
    177
  • 2
    构建高效Python Web应用:异步编程与Tornado框架解析
    59
  • 3
    请描述在 Python WEB 开发中常用的测试方法。
    28
  • 4
    在 Python 中,如何处理 Web 请求和响应?
    47
  • 5
    Django是一个用Python编写的开源Web应用框架
    17
  • 6
    构建高性能Web应用:前端性能优化的关键策略与实践
    151
  • 7
    视觉智能平台常见问题之web端编辑器实现如何解决
    47
  • 8
    构建高性能Web应用的关键技术
    61
  • 9
    构建响应式Web界面:Flexbox的力量
    33
  • 10
    探索现代Web应用中的微前端架构
    40
  • 相关课程

    更多
  • Python Web开发基础
  • Java Web开发系列课程 - Spring框架入门
  • 企业Web常用架构LAMP-LNMP实战
  • 高校精品课-西安交通大学-Web 编程技术
  • Java Web项目实战 - 图书商城
  • Java面试疑难点解析 - Java Web开发
  • 相关电子书

    更多
  • Web应用系统性能优化
  • 高性能Web架构之缓存体系
  • PWA:移动Web的现在与未来
  • 相关实验场景

    更多
  • 如何快速创建插件agent
  • 每个IT人都想学的“Web应用上云经典架构”实战
  • 使用SLB+2ECS+NAS,部署电商web网站的高可用架构
  • 1分钟SAE部署Web在线游戏
  • Html5和Webpack3:Webpack5的常见用法
  • Html5和Webpack1:Webpack5打包工具介绍
  • 下一篇
    2024年阿里云免费云服务器及学生云服务器申请教程参考

    两个鬼故事北京烤肉自助餐在漫威当超级英雄的那些年小孩缺水起什么名字起名一字的含义禁区沉沦法尊布店起什么名字好能看所有vip影视的软件app点滴梦想安建理财产品有哪些环保主题起名自缢给小猫咪起名字男同志王伦宝Chinese3燕起名什么意思的马姓宝宝起名汉字的演变过程卫视在线直播女孩宝宝起什么名字好果然的近义词张沄熙饺子店起什么名字好听撩她上瘾BY阿司匹林翁氏起名字小食品批发起名樊姓起名诗意丑陋的反义词慕字起名发型店起名少年生前被连续抽血16次?多部门介入两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”淀粉肠小王子日销售额涨超10倍高中生被打伤下体休学 邯郸通报单亲妈妈陷入热恋 14岁儿子报警何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言张家界的山上“长”满了韩国人?男孩8年未见母亲被告知被遗忘中国拥有亿元资产的家庭达13.3万户19岁小伙救下5人后溺亡 多方发声315晚会后胖东来又人满为患了张立群任西安交通大学校长“重生之我在北大当嫡校长”男子被猫抓伤后确诊“猫抓病”测试车高速逃费 小米:已补缴周杰伦一审败诉网易网友洛杉矶偶遇贾玲今日春分倪萍分享减重40斤方法七年后宇文玥被薅头发捞上岸许家印被限制高消费萧美琴窜访捷克 外交部回应联合利华开始重组专访95后高颜值猪保姆胖东来员工每周单休无小长假男子被流浪猫绊倒 投喂者赔24万小米汽车超级工厂正式揭幕黑马情侣提车了西双版纳热带植物园回应蜉蝣大爆发当地回应沈阳致3死车祸车主疑毒驾恒大被罚41.75亿到底怎么缴妈妈回应孩子在校撞护栏坠楼外国人感慨凌晨的中国很安全杨倩无缘巴黎奥运校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变王树国卸任西安交大校长 师生送别手机成瘾是影响睡眠质量重要因素国产伟哥去年销售近13亿阿根廷将发行1万与2万面值的纸币兔狲“狲大娘”因病死亡遭遇山火的松茸之乡“开封王婆”爆火:促成四五十对奥巴马现身唐宁街 黑色着装引猜测考生莫言也上北大硕士复试名单了德国打算提及普京时仅用姓名天水麻辣烫把捣辣椒大爷累坏了

    两个鬼故事 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化