revert update zh hans (#5754)

revert update zh hans (#5754)

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -782,7 +782,7 @@
     goal_survive_time: "生存 __seconds__ 秒。"
     goal_defeat: "击败所有怪物。"
     goal_defeat_amount: "击败 __amount__ 个怪物。"
-    goal_move: "移动到所有红X标记。"
+    goal_move: "移动的所有的红X标记。"
     goal_collect: "收集所有物品。"
     goal_collect_amount: "收集 __amount__ 个物品。"

@@ -1693,4 +1693,1330 @@
     help: "帮助"
     language: "编程语言"
     edit_class_settings: "修改班级设置"
-
+    access_restricted: "需要升级账号"
+    teacher_account_required: "只有老师账号才能访问。"
+    create_teacher_account: "创建老师账号"
+    what_is_a_teacher_account: "什么是老师账号？"
+    teacher_account_explanation: "老师账号可以让你设立教室，当学生们学完课程后可以查看他们的进度，管理许可证以及查看相应的帮助文档以辅助构建课堂。"
+    current_classes: "当前班级"
+    archived_classes: "已归档的班级"
+    archived_classes_blurb: "您可以把班级归档，便于后续查阅。课程可以解档课程，并在当前课程列表中再次查看。"
+    view_class: "查看班级"
+    archive_class: "归档班级"
+    unarchive_class: "解档班级"
+    unarchive_this_class: "解档本班级"
+    no_students_yet: "该班级尚未有学生加入。"
+    no_students_yet_view_class: "查看班级并添加学生。"
+    try_refreshing: "(您可能需要刷新页面)"
+    create_new_class: "创建新班级"
+    class_overview: "班级概览" # View Class page
+    avg_playtime: "单关平均通关时间"
+    total_playtime: "总时长"
+    avg_completed: "平均完成关卡数"
+    total_completed: "完成总关卡数"
+    created: "创建时间"
+    concepts_covered: "覆盖语法概念"
+    earliest_incomplete: "最近未完成关卡"
+    latest_complete: "最近通关关卡"
+    enroll_student: "注册学生"
+    apply_license: "使用许可"
+    revoke_license: "终止许可"
+    revoke_licenses: "终止所有许可"
+    course_progress: "课程进度"
+    not_applicable: "N/A"
+    edit: "修改"
+    edit_2: "修改"
+    remove: "移除"
+    latest_completed: "最近通关" # {change}
+    sort_by: "排列依据"
+    progress: "进度"
+#    concepts_used: "Concepts used by Student:"
+#    concept_checked: "Concept checked:"
+    completed: "已完成"
+    practice: "练习"
+    started: "已开始"
+    no_progress: "无进度"
+    not_required: "不需要"
+    view_student_code: "点击查看学生的代码"
+    select_course: "选择查看"
+    progress_color_key: "进度颜色指示："
+    level_in_progress: "正在通关"
+    level_not_started: "通关未开始"
+    project_or_arena: "开发项目或竞技场"
+    students_not_assigned: "未开始{{courseName}}的学生"
+    course_overview: "班级概览"
+    copy_class_code: "复制班级代码"
+    class_code_blurb: "未加入的学生可以在自己的功能页面输入该班级代码，或者访问网址加入。" # {change}
+    copy_class_url: "复制班级链接地址"
+    class_join_url_blurb: "新学生可以在登录之后，访问该网址加入班级。" # {change}
+    add_students_manually: "手工添加学生" # {change}
+    bulk_assign: "批量添加" # {change}
+    assigned_msg_1: "{{numberAssigned}} 学生被分配 {{courseName}}。"
+    assigned_msg_2: "{{numberEnrolled}} 被应用许可证。"
+    assigned_msg_3: "你现在还有 {{remainingSpots}} 个可用的许可。"
+    assign_course: "分配课程"
+#    removed_course_msg: "{{numberRemoved}} students were removed from {{courseName}}."
+    remove_course: "移除课程"
+    not_assigned_modal_title: "课程未分配"
+    not_assigned_modal_starter_body_1: "本课程需要学生许可证。您没有足够的学生许可证来分配本课程给所有选课的学生。"
+#    not_assigned_modal_starter_body_2: "Purchase Starter Licenses to grant access to this course."
+    not_assigned_modal_full_body_1: "本课程需要许可证才能使用。您没有足够的许可证, 因此无法将此课程分配给已选定的 __selected__ 名学生。"
+    not_assigned_modal_full_body_2: "您当前仅有__numFullLicensesAvailable__个许可证可供使用 (__numStudentsWithoutFullLicenses__名学生目前没有许可证) 。"
+    not_assigned_modal_full_body_3: "请适量减少选定的学生数量, 或联系china@codecombat.com寻求帮助。"
+    assigned: "已添加"
+    enroll_selected_students: "注册选中的学生"
+    no_students_selected: "没有学生被选中。"
+    show_students_from: "展示这些学生" # Enroll students modal
+    apply_licenses_to_the_following_students: "将许可应用于以下学生"
+    students_have_licenses: "以下学生已应用许可:"
+    all_students: "所有学生"
+    apply_licenses: "应用许可"
+    not_enough_enrollments: "没有足够许可注册。"
+    enrollments_blurb: "每个学生需要一个许可，以便在第一课程后访问后续内容。"
+    how_to_apply_licenses: "如何应用许可"
+    export_student_progress: "导出学生进度 (CSV)"
+    send_email_to: "将重置密码邮件发送到以下邮箱："
+    email_sent: "邮件已发送"
+    send_recovery_email: "发送重置密码邮件"
+    enter_new_password_below: "在下面输入您的新密码："
+    change_password: "更改密码"
+    changed: "已更改"
+    available_credits: "可用许可"
+    pending_credits: "评估许可"
+    empty_credits: "许可已用完"
+    license_remaining: "剩余许可证"
+    licenses_remaining: "剩余许可证"
+    one_license_used: "1个许可证已被使用" # {change}
+    num_licenses_used: "__numLicensesUsed__个许可证已被使用" # {change}
+    starter_licenses: "开始使用许可证"
+    start_date: "起始日期："
+    end_date: "结束日期："
+    get_enrollments_blurb: " 我们会帮您解决您的班级、学校或地区的需求。"
+    how_to_apply_licenses_blurb_1: "当老师第一次将课程分配给学生时，我们将自动应用许可证。使用教室中的批量分配下拉菜单为所选学生分配课程:"
+    how_to_apply_licenses_blurb_2: "我可以只申请许可而不分配课程吗?"
+    how_to_apply_licenses_blurb_3: "是的 — 转到课堂中的\"许可状态\"选项卡，然后单击 \"应用许可\"到没有被分配许可的学生。"
+    request_sent: "申请已发送！"
+    assessments: "评估"
+    license_status: "许可状态"
+    status_expired: "在 {{date}} 过期"
+    status_not_enrolled: "未分配名额"
+    status_enrolled: "在 {{date}} 过期"
+    select_all: "全选"
+    project: "项目"
+    project_gallery: "项目图库"
+    view_project: "查看项目"
+    unpublished: "（未发布的）"
+    view_arena_ladder: "查看竞技场天梯"
+    resource_hub: "资源中心"
+    pacing_guides: "课堂进度建议指南"
+    pacing_guides_desc: "了解如何整合CodeCombat的所有资源来制定您的教学计划！"
+    pacing_guides_elem: "小学进度指南"
+    pacing_guides_middle: "中学进度指南"
+    pacing_guides_high: "高中进度指南"
+    getting_started: "入门"
+    educator_faq: "常见问题"
+    educator_faq_desc: "在您的学校或教室使用 CodeCombat 的常见问题。"
+    teacher_getting_started: "老师入门指南"
+    teacher_getting_started_desc: "第一次使用 CodeCombat ？下载此老师入门指南开始设置您的账号，创建第一个课堂并邀请学生参加第一门课程。"
+    student_getting_started: "学生快速入门指南"
+    student_getting_started_desc: "您可以在讲解 CodeCombat 之前先将此入门指南发布给学生，以便他们熟悉代码编辑器。本指南可用于 Python 和 JavaScript 语言教室。"
+    ap_cs_principles: "AP 计算机科学原理"
+    ap_cs_principles_desc: "AP计算机科学原理为学生提供了计算机科学的应用，影响和可能性的广泛介绍。本课程在教授编程基础知识的同时，还强调了计算思维和解决问题的能力。"
+    cs1: "计算机科学的介绍"
+    cs2: "计算机科学 2"
+    cs3: "计算机科学 3"
+    cs4: "计算机科学 4"
+    cs5: "计算机科学 5"
+    cs1_syntax_python: "课程 1 Python 语法指南"
+    cs1_syntax_python_desc: "参考常见的 Python 语法，学生将学习计算科学的介绍。"
+    cs1_syntax_javascript: "课程 1 JavaScript 语法指南"
+    cs1_syntax_javascript_desc: "参考常见的 JavaScript 语法，学生将学习计算科学的介绍。"
+    coming_soon: "更多指南即将推出！"
+    engineering_cycle_worksheet: "程序设计流程表"
+    engineering_cycle_worksheet_desc: "基于此表来管理学生的程序设计流程、评估、计划、进度和调试。请参考完整的示例表作为指南。"
+    engineering_cycle_worksheet_link: "查看示例"
+    progress_journal: "学习进度"
+    progress_journal_desc: "鼓励学生通过进度日志跟踪他们的进步。"
+    cs1_curriculum: "计算机科学的介绍 - 课程指南"
+    cs1_curriculum_desc: "课程 1 的范围、顺序、课程计划和活动等。"
+    arenas_curriculum: "竞技场 － 老师指导"
+    arenas_curriculum_desc: "如何在班级中使用多人竞技场《Wakka Maul》《Cross Bones》和《Power Peak》"
+    assessments_curriculum: "评估级别 - 教师指南"
+    assessments_curriculum_desc: "了解如何使用挑战级别和组合挑战级别来评估学生的学习成果。"
+    cs2_curriculum: "计算机科学 2 - 课程指南"
+    cs2_curriculum_desc: "课程 2 的范围、顺序、课程计划和活动等。"
+    cs3_curriculum: "计算机科学 3 - 课程指南"
+    cs3_curriculum_desc: "课程 3 的范围、顺序、课程计划和活动等。"
+    cs4_curriculum: "计算机科学 4 - 课程指南"
+    cs4_curriculum_desc: "课程 4 的范围、顺序、课程计划和活动等。"
+    cs5_curriculum_js: "计算机科学 5 - 课程指南 (JavaScript)"
+    cs5_curriculum_desc_js: "使用JavaScript的课程5的范围，顺序，课程计划和活动等。"
+    cs5_curriculum_py: "计算机科学 5 - 课程指南 (Python)"
+    cs5_curriculum_desc_py: "使用Python的课程5的范围，顺序，课程计划和活动等。"
+    cs1_pairprogramming: "组队编程活动"
+    cs1_pairprogramming_desc: "让两个学生一起组队编程练习，这将有助于他们互相更好的学习与沟通。"
+    gd1: "游戏开发1"
+    gd1_guide: "游戏开发1 – 课程指南"
+    gd1_guide_desc: "使用本指南来指导学生在5天内开发他们的第一个可共享游戏项目。"
+    gd1_rubric: "游戏开发1 – 课程评估"
+    gd1_rubric_desc: "使用本标准来评估学生们在游戏开发1创建的项目。"
+    gd2: "游戏开发2"
+    gd2_curriculum: "游戏开发2 – 课程指南"
+    gd2_curriculum_desc: "游戏开发2的课程计划。"
+    gd3: "游戏开发3"
+    gd3_curriculum: "游戏开发3 – 课程指南"
+    gd3_curriculum_desc: "游戏开发3的课程计划。"
+    wd1: "网页开发1"
+    wd1_curriculum: "网页开发1 - 课程指南"
+    wd1_curriculum_desc: "网页开发1的范围、顺序，课程计划和活动等。"
+    wd1_headlines: "标题标识活动练习"
+    wd1_headlines_example: "查看示例"
+    wd1_headlines_desc: "为什么段落和标题标签很重要？运用此练习说明精选的标题如何使网页更易于阅读。当然还有更多其他的解决方案!"
+    wd1_html_syntax: "HTML语法指南"
+    wd1_html_syntax_desc: "这是学生在网页开发1中学习HTML样式的单页参考。"
+    wd1_css_syntax: "CSS语法指南"
+    wd1_css_syntax_desc: "这是学生在网页开发1中学习CSSL风格的单页参考。"
+    wd2: "网页开发2"
+    wd2_jquery_syntax: "jQuery函数语法指南"
+    wd2_jquery_syntax_desc: "这是学生在网页开发2中学习jQuery函数的单页参考。"
+    wd2_quizlet_worksheet: "测试规划表"
+    wd2_quizlet_worksheet_instructions: "查看说明和示例"
+    wd2_quizlet_worksheet_desc: "在学生创建他们的个性测验项目，结束网页开发2之前，他们应该使用此工作表来规划测试问题、结果和响应。教师可以分发说明和示例供学生参考。"
+    student_overview: "概述"
+    student_details: "学生详情"
+    student_name: "学生姓名"
+    no_name: "未提供姓名。"
+    no_username: "未提供用户名。"
+    no_email: "学生没有设置电子邮件地址。"
+    student_profile: "学生档案"
+    playtime_detail: "游戏时间详情"
+    student_completed: "学生已完成"
+    student_in_progress: "学生进行中"
+    class_average: "班级平均"
+    not_assigned: "尚未分配以下课程"
+    playtime_axis: "游戏时间(秒)"
+    levels_axis: "关卡来自于"
+    student_state: "看看"
+    student_state_2: "的情况?"
+    student_good: "表现得很好，课程"
+    student_good_detail: "这个学生的进度达到了班级的平均进度。"
+    student_warn: "可能需要一些帮助"
+    student_warn_detail: "这个学生可能需要一些帮助，在本课程中介绍的新概念。"
+    student_great: "做得太好了，关卡"
+    student_great_detail: "这个学生可能是一个很好的候选人，帮助其他学生通过这门课程。"
+    full_license: "全课许可"
+    starter_license: "入门许可"
+    trial: "试用"
+    hoc_welcome: "计算机教育周快乐"
+#    hoc_title: "Hour of Code Games - Free Activities to Learn Real Coding Languages"
+#    hoc_meta_description: "Make your own game or code your way out of a dungeon! CodeCombat has four different Hour of Code activities and over 60 levels to learn code, play, and create."
+#    hoc_intro: "There are three ways for your class to participate in Hour of Code with CodeCombat"
+#    hoc_self_led: "Self-Led Gameplay"
+#    hoc_self_led_desc: "Students can play through two Hour of Code CodeCombat tutorials on their own"
+#    hoc_game_dev: "Game Development"
+#    hoc_and: "and"
+#    hoc_programming: "JavaScript/Python Programming"
+#    hoc_teacher_led: "Teacher-Led Lessons"
+    hoc_teacher_led_desc1: "下载我们"
+#    hoc_teacher_led_link: "Introduction to Computer Science lesson plans"
+#    hoc_teacher_led_desc2: "to introduce your students to programming concepts using offline activities"
+#    hoc_group: "Group Gameplay"
+#    hoc_group_desc_1: "Teachers can use the lessons in conjunction with our Introduction to Computer Science course to track student progress. See our"
+#    hoc_group_link: "Getting Started Guide"
+    hoc_group_desc_2: "获取更多信息"
+#    hoc_additional_desc1: "For additional CodeCombat resources and activities, see our"
+    hoc_additional_desc2: "问题"
+    hoc_additional_contact: "保持联系"
+#    revoke_confirm: "Are you sure you want to revoke a Full License from {{student_name}}? The license will become available to assign to another student."
+    revoke_all_confirm: "你确定要撤销所有这个班级的所有学生的许可证吗？"
+    revoking: "撤销中"
+    unused_licenses: "您有未使用的学生许可,当学生准备学习更多课程时您可以分配给他们!"
+    remember_new_courses: "记得分配新课程!"
+    more_info: "更多信息"
+    how_to_assign_courses: "如何分配课程"
+    select_students: "选择学生"
+    select_instructions: "点击您要分配课程的每个学生旁边的复选框."
+    choose_course: "选择课程"
+    choose_instructions: "从您要分配的下拉菜单中选择课程, 然后点击 “分配给所选学生.”"
+    push_projects: "我们建议学生在完成计算机科学导论后分配网页开发1或游戏开发1! 有关这些课程的更多信息,请参阅我们的 {{resource_hub}} ."
+    teacher_quest: "老师任务获得成功"
+    quests_complete: "任务完成"
+    teacher_quest_create_classroom: "添加班级"
+    teacher_quest_add_students: "添加学生"
+    teacher_quest_teach_methods: "帮助你的学生学习如何 `调用方法`"
+    teacher_quest_teach_methods_step1: "班级中至少有 75% 的学生通过该关卡 __Dungeons of Kithgard__"
+    teacher_quest_teach_methods_step2: "打印资源中心的 [学生快速入门指南](http://files.codecombat.com/docs/resources/StudentQuickStartGuide.pdf) "
+    teacher_quest_teach_strings: "学习 `字符串`"
+    teacher_quest_teach_strings_step1: "班级中至少有 75% 的学生通过该关卡 __True Names__"
+    teacher_quest_teach_strings_step2: "使用 [课程指导](/teachers/courses) 页面上的老师关卡选择器预览 __True Names__"
+    teacher_quest_teach_loops: "让你的学生在循环中学习 `while循环`"
+    teacher_quest_teach_loops_step1: "班级中至少有 75% 的学生通过该关卡 __Fire Dancing__."
+    teacher_quest_teach_loops_step2: "强化学习 __Loops Activity__ 这个概念，参考 [CS1 课程指南](/teachers/resources/cs1)."
+    teacher_quest_teach_variables: "学习 `变量`."
+    teacher_quest_teach_variables_step1: "班级中至少有 75% 的学生通过该关卡 __Known Enemy__."
+    teacher_quest_teach_variables_step2: "通过使用 [组队编程活动](/teachers/resources/pair-programming)学习如何与团队合作."
+    teacher_quest_kithgard_gates_100: "和你的班级一起逃离 Kithgard Gates."
+    teacher_quest_kithgard_gates_100_step1: "班级中至少有 75% 的学生通过该关卡 __Kithgard Gates__."
+    teacher_quest_kithgard_gates_100_step2: "引导学生通过思考来解决难题 [Engineering Cycle Worksheet](http://files.codecombat.com/docs/resources/EngineeringCycleWorksheet.pdf)."
+    teacher_quest_wakka_maul_100: "准备在 Wakka Maul 中决斗."
+    teacher_quest_wakka_maul_100_step1: "班级中至少有 75% 的学生通过该关卡 __Wakka Maul__."
+    teacher_quest_wakka_maul_100_step2: "在 [资源中心](/teachers/resources) 中查看 [竞技场指南](/teachers/resources/arenas) ，了解如何在竞技中获得胜利."
+    teacher_quest_reach_gamedev: "探索新的世界!"
+    teacher_quest_reach_gamedev_step1: "[获得许可证](/teachers/licenses) 以便您的学生可以探索新的世界, 如 Game Development 和 Web Development!"
+    teacher_quest_done: "希望你的学生学习更多的代码? 现在就请联系我们的 [校园专家](mailto:schools@codecombat.com) !"
+    teacher_quest_keep_going: "继续！ 这是你下一步可以做的:"
+    teacher_quest_more: "查看所有任务"
+    teacher_quest_less: "查看基础任务"
+    refresh_to_update: "(刷新页面查看更新)"
+    view_project_gallery: "查看项目图库"
+    office_hours: "教师网络研讨会"
+    office_hours_detail: "了解如何跟上学生的步伐,创建游戏并开始他们的编程之旅! 快来参加我们的"
+    office_hours_link: "教师网络研讨会"
+    office_hours_detail_2: "."
+    success: "成功"
+    in_progress: "进行中"
+    not_started: "没有开始"
+    mid_course: "课程进行中"
+    end_course: "课程已结束"
+    none: "还没有发现"
+    explain_open_ended: "注意:鼓励学生创造性地解决这个问题——下面提供了一个可能的解决方法"
+    level_label: "级别"
+    time_played_label: "游戏时间："
+    back_to_resource_hub: "返回资源仓库"
+    back_to_course_guides: "返回课程指导"
+    print_guide: "打印指导手册"
+    combo: "组合"
+    combo_explanation: "学生至少使用一个列出的概念组合来挑战关卡，点击进度点查看学生代码。"
+    concept: "概念"
+    sync_google_classroom: "同步谷歌教室"
+#    try_ozaria_footer: "Try our new adventure game, Ozaria!"
+
+#  teacher_ozaria_encouragement_modal:
+#    title: "Build Computer Science Skills to Save Ozaria"
+#    sub_title: "You are invited to try the new adventure game from CodeCombat"
+#    cancel: "Back to CodeCombat"
+#    accept: "Try First Unit Free"
+#    bullet1: "Deepen student connection to learning through an epic story and immersive gameplay"
+#    bullet2: "Teach CS fundamentals, Python or JavaScript and 21st century skills"
+#    bullet3: "Unlock creativity through capstone projects"
+#    bullet4: "Support instructions through dedicated curriculum resources"
+#    you_can_return: "You can always return to CodeCombat"
+
+  share_licenses:
+    share_licenses: "分配许可"
+    shared_by: "分享自："
+    add_teacher_label: "请准确地输入老师的邮箱："
+    add_teacher_button: "添加老师"
+#    subheader: "You can make your licenses available to other teachers in your organization. Each license can only be used for one student at a time."
+    teacher_not_found: "没有找到教室。请确保教室已经创建了一个教师账户。"
+    teacher_not_valid: "该教师账户无效，只有教师账户可以分享许可证。"
+    already_shared: "成功分享许可证给教师。"
+#    have_not_shared: "You've not shared these licenses with that teacher."
+#    teachers_using_these: "Teachers who can access these licenses:"
+#    footer: "When teachers revoke licenses from students, the licenses will be returned to the shared pool for other teachers in this group to use."
+    you: "（您）"
+    one_license_used: "（1个许可证被使用）"
+    licenses_used: "（__licensesUsed__个许可证被使用）"
+    more_info: "更多信息"
+
+  sharing:
+    game: "游戏"
+    webpage: "网页"
+    your_students_preview: "您的学生可以点击这里查看他们的完成项目，在老师预览中不可用。"
+    unavailable: "老师预览中不提供链接共享。"
+    share_game: "分享此游戏"
+    share_web: "分享此网页"
+    victory_share_prefix: "将这个链接分享给亲朋好友，让他们"
+    victory_share_prefix_short: "将这个链接分享给亲朋好友，让他们"
+    victory_share_game: "玩你的游戏关卡"
+    victory_share_web: "看你制作的网页"
+    victory_share_suffix: "吧。"
+    victory_course_share_prefix: "此链接将你刚制作的"
+    victory_course_share_game: "游戏"
+    victory_course_share_web: "网页"
+    victory_course_share_suffix: "分享给亲朋好友。"
+    copy_url: "复制 URL"
+    share_with_teacher_email: "发送给你的老师"
+
+  game_dev:
+    creator: "创建者"
+
+  web_dev:
+    image_gallery_title: "图库"
+    select_an_image: "选择您要使用的图片"
+    scroll_down_for_more_images: "(向下滚动查看更多图片)"
+    copy_the_url: "复制以下网址"
+    copy_the_url_description: "如果您想要替换现有的图片，这很有用。"
+    copy_the_img_tag: "复制 <img> 标签"
+    copy_the_img_tag_description: "如果您想要插入新图片，这很有用。"
+    copy_url: "复制网址"
+    copy_img: "复制 <img>"
+    how_to_copy_paste: "如何复制/粘贴"
+    copy: "复制"
+    paste: "粘贴"
+    back_to_editing: "返回编辑"
+
+  classes:
+    archmage_title: "大法师"
+    archmage_title_description: "(代码编写人员)"
+    archmage_summary: "如果您是对教育类游戏感兴趣的程序开发者，那么就选择大法师来为CodeCombat编写代码吧！"
+    artisan_title: "工匠师"
+    artisan_title_description: "(关卡创造人员)"
+    artisan_summary: "建立游戏关卡并分享给朋友们。那么就选择工匠来教其他人编程吧。"
+    adventurer_title: "冒险家"
+    adventurer_title_description: "(关卡测试人员)"
+    adventurer_summary: "提前一周免费游玩我们最新发布的关卡，并帮助我们在发布之前寻找程序错误"
+    scribe_title: "文书"
+    scribe_title_description: "(文档编辑人员)"
+    scribe_summary: "好代码需要好文档，来自全世界数百万的玩家一起编写，编辑以及提高文档的可读性"
+    diplomat_title: "外交官"
+    diplomat_title_description: "(翻译人员)"
+    diplomat_summary: "CodeCombat已经被我们的外交官们翻译成45多种世界语言。在翻译的同时体验翻译的乐趣吧！"
+    ambassador_title: "使节"
+    ambassador_title_description: "(用户支持人员)"
+    ambassador_summary: "指导我们亲爱的论坛用户并为他们的问题引出一个正确的方向。我们的使节将CodeCombat带到这个世界上。"
+    teacher_title: "老师"
+
+  editor:
+    main_title: "CodeCombat编辑器"
+    article_title: "指令编辑器"
+    thang_title: "物品编辑器"
+    level_title: "关卡编辑器"
+    course_title: "课程编辑器"
+    achievement_title: "目标编辑器"
+    poll_title: "调查编辑器"
+    back: "后退"
+    revert: "还原"
+    revert_models: "还原模式"
+    pick_a_terrain: "选择地形"
+    dungeon: "地牢"
+    indoor: "室内"
+    desert: "沙漠"
+    grassy: "草地"
+    mountain: "高山"
+    glacier: "冰川"
+    small: "小的"
+    large: "大的"
+    fork_title: "派生新版本"
+    fork_creating: "正在执行派生..."
+    generate_terrain: "生成地形"
+    more: "更多"
+    wiki: "维基"
+    live_chat: "在线聊天"
+    thang_main: "主菜单"
+    thang_spritesheets: "贴图集"
+    thang_colors: "颜色"
+    level_some_options: "有哪些选项？"
+    level_tab_thangs: "物体"
+    level_tab_scripts: "脚本"
+    level_tab_components: "组件"
+    level_tab_systems: "系统"
+    level_tab_docs: "文档"
+    level_tab_thangs_title: "目前所有物体"
+    level_tab_thangs_all: "所有"
+    level_tab_thangs_conditions: "启动条件"
+    level_tab_thangs_add: "增加物体"
+    level_tab_thangs_search: "查找物体"
+    add_components: "添加组件"
+    component_configs: "组件配置"
+    config_thang: "双击配置一个物体"
+    delete: "删除"
+    duplicate: "复制"
+    stop_duplicate: "停止复制"
+    rotate: "旋转"
+    level_component_tab_title: "目前所有组件"
+    level_component_btn_new: "创建新的组件"
+    level_systems_tab_title: "目前所有系统"
+    level_systems_btn_new: "创建新的系统"
+    level_systems_btn_add: "增加系统"
+    level_components_title: "返回到所有物体主页"
+    level_components_type: "类型"
+    level_component_edit_title: "编辑组件"
+    level_component_config_schema: "配置模式"
+    level_system_edit_title: "编辑系统"
+    create_system_title: "创建新的系统"
+    new_component_title: "创建新的组件"
+    new_component_field_system: "系统"
+    new_article_title: "创建一个新物品"
+    new_thang_title: "创建一个新物品类型"
+    new_level_title: "创建一个新关卡"
+    new_article_title_login: "登录以创建新指令"
+    new_thang_title_login: "登录以创建新物品"
+    new_level_title_login: "登录以创建新关卡"
+    new_achievement_title: "创建新目标"
+    new_achievement_title_login: "登录以创建新目标"
+    new_poll_title: "创建新调查"
+    new_poll_title_login: "登陆以创建新调查"
+    article_search_title: "在这里搜索物品"
+    thang_search_title: "在这里搜索物品类型"
+    level_search_title: "在这里搜索关卡"
+    achievement_search_title: "搜索目标"
+    poll_search_title: "搜寻投票"
+    read_only_warning2: "提示：您不能保存任何编辑，因为您没有登陆"
+    no_achievements: "这个关卡还没有被赋予任何目标。"
+    achievement_query_misc: "关闭杂项的成就"
+    achievement_query_goals: "关闭通关的成就"
+    level_completion: "关卡完成"
+    pop_i18n: "完善 I18N"
+    tasks: "任务"
+    clear_storage: "清除您本地的变更"
+    add_system_title: "添加系统到关卡"
+    done_adding: "添加完毕"
+
+  article:
+    edit_btn_preview: "预览"
+    edit_article_title: "编辑提示"
+
+  polls:
+    priority: "优先次序"
+
+  contribute:
+    page_title: "贡献"
+    intro_blurb: "CodeCombat是完全开源的软件！上千个玩家帮助了我们建设了这整个游戏，就是您今天看到的一样！快加入我们然后为CodeCombat打开新的章节，将世界带领进入编程世代前进吧！" # {change}
+    alert_account_message_intro: "您好！"
+    alert_account_message: "想要订阅邮件？ 您必须先登录"
+    archmage_introduction: "制作游戏时，最令人激动的事莫过于整合诸多东西。图像、音响、实时网络交流、社交网络，从底层数据库管理到服务器运维，再到用户界面的设计和实现。制作游戏有很多事情要做，所以如果您有编程经验,  那么您应该选择这个职业。我们会很高兴在制作史上最好编程游戏的路上有您的陪伴。"
+    class_attributes: "职业说明"
+    archmage_attribute_1_pref: "了解 "
+    archmage_attribute_1_suf: "，或者想要学习。我们的多数代码都是用它写成的。如果您喜欢 Ruby 或者 Python，那您肯定会感到非常熟悉。它就是 JavaScript，但它的语法更友好。"
+    archmage_attribute_2: "编程经验和干劲。我们可以帮您走上正轨，但我们恐怕没多少时间培训您。"
+    how_to_join: "如何加入"
+    join_desc_1: "谁都可以加入！先看看我们的"
+    join_desc_2: "，然后勾选下面的复选框，这样您就会作为勇敢的大法师收到我们的邮件。如果您想和开发人员聊天或者更深入地参与，可以"
+    join_desc_3: " 或者去我们的"
+    join_desc_4: "，然后我们有话好说！"
+    join_url_email: "给我们发邮件"
+    join_url_slack: "公共 Slack 频道"
+    archmage_subscribe_desc: "通过电子邮件获得新的编码机会和公告。"
+    artisan_introduction_pref: "我们必须设计更多的关卡！ 大家为了更多的游戏内容在高声呐喊，但是我们靠自己只能创建这些。现在您的电脑就是第一关！我们的关卡编辑器刚刚完成了基本功能，所以创造关卡的时候请小心使用。只要您有制作关卡的灵感，不管是简单的for循环还是"
+    artisan_introduction_suf: "这种东西，这个职业都很适合您。"
+    artisan_attribute_1: "最好有创建地图的经历，比如使用暴雪的关卡编辑器，但这不是必须的条件。"
+    artisan_attribute_2: "渴望完成全部的测试和迭代。要做一个优秀的关卡，您需要把它给别的玩家观察他们怎么玩，然后时刻准备着找到很多细节去打磨。"
+    artisan_attribute_3: "探险家般的忍耐力。我们的关卡编辑器非常的初级，还会有很多不好用的地方。唔，我已经提前告诉您了~"
+    artisan_join_desc: "如下步骤使用关卡编辑器:"
+    artisan_join_step1: "阅读文档。"
+    artisan_join_step2: "创建一个新关卡 以及探索已经存在的关卡。"
+    artisan_join_step3: "来我们的Slack聊天室寻求帮助。"
+    artisan_join_step4: "把您的关卡发到论坛让别人给您评价。"
+    artisan_subscribe_desc: "通过电子邮件获得关卡编辑器更新和公告。"
+    adventurer_introduction: "现在就来了解您的角色吧：您是一辆战车，并将要承担沉重的攻击。我们需要人来尝试下我们新开的关卡以了解怎么样才能使每一样东西更美好。一开始虽然会非常辛苦；可是制造出一个最好的游戏是一个很长的过程，在这个过程中，没有人可以第一次就成功。如果您挺过去了并且自我感觉良好，那么这个位置就是为您而准备的。"
+    adventurer_attribute_1: "学习的渴望！您想要学好怎么编程，与此同时我们也想要教您怎么编程。虽然您可能会觉得大多数时间您都在教导别人。"
+    adventurer_attribute_2: "魅力！温柔但懂得表达事态；了解并知道如何改进。"
+    adventurer_join_pref: "无论是与工匠一起工作，或者只是在下面的格子打勾以获得新关卡测试的通知，我们都会发布关于对新关卡的看法。"
+    adventurer_forum_url: "我们的论坛"
+    adventurer_join_suf: "如果您更喜欢以这些方式被通知, 那就在这里注册吧！"
+    adventurer_subscribe_desc: "通过电子邮件获得新关卡通知。"
+    scribe_introduction_pref: "CodeCombat不只是拥有一堆等级或关卡。它也是个知识的源泉，一个编程的宝典，一个人人都可以参与的，有趣的游戏。比起每个工匠都要详细解释其中的奥秘，他们可以以更简单的方式将自己的东西连接到一篇已经准备好的文章给玩家阅读。 有些东西就像 "
+    scribe_introduction_url_mozilla: "Mozilla开发者社区"
+    scribe_introduction_suf: " 被建成。如果您那有趣的想法能把编程的理论简易的带出来，那么这里就是您的所属了。"
+    scribe_attribute_1: "您需要具备在文字语言上的技能。不仅仅是语法和拼音，重要的是能传达各种各样想法给别人。"
+    contact_us_url: "联系我们"
+    scribe_join_description: "介绍下您自己, 比如您的编程经历和您喜欢的东西, 我们将会努力的去了解您！！"
+    scribe_subscribe_desc: "通过电子邮件获得写作新文档的通知。"
+    diplomat_introduction_pref: "如果说我们从"
+    diplomat_launch_url: "十月的发布"
+    diplomat_introduction_suf: "中得到了什么启发：那就是全世界的人都很喜欢CodeCombat。我们召集了一群翻译者，希望能尽快地把网站上的信息翻译成各国文字。如果您对CodeCombat的内容很感兴趣，想让世界上的每一个人都有机会一同体验，就快来成为外交官吧。"
+    diplomat_attribute_1: "既会说流利的英语，也熟悉自己的语言。编程是一件很复杂的事情，而要翻译复杂的概念，您必须对两种语言都在行！"
+    diplomat_i18n_page_prefix: "您可以在我们的"
+    diplomat_i18n_page: "翻译页面"
+    diplomat_i18n_page_suffix: "开始翻译游戏，或者在我们的GitHub页面上进行。"
+    diplomat_join_pref_github: "在"
+    diplomat_github_url: "GitHub"
+    diplomat_join_suf_github: "找到中文语言文件 (简体中文的是:CodeCombat/app/locale/zh-HANS.coffee)，在线编辑它，完成后提交一个合并请求。您也可以勾上下面这个复选框来关注最新的国际化开发！"
+    diplomat_subscribe_desc: "接受有关国际化开发和翻译任务的邮件"
+    ambassador_introduction: "这是一个正在成长的社区，而您将成为我们与世界的联结点。大家可以通过各种即时聊天平台、邮件、或是参与者众多的社交网络来认识及讨论我们的游戏。如果您喜欢帮助大家，一起参与各类项目，一起获得乐趣。与我们同行，一起感受CodeCombat的脉搏，绝对是一个适合您的职业。"
+    ambassador_attribute_1: "具备出色的沟通能力。能够分辨出玩家遇到的问题并帮助他们解决问题。与此同时，和我们保持联系，帮助反馈玩家们的喜怒哀乐！"
+    ambassador_join_desc: "介绍一下您自己：您做过什么？您喜欢做什么？我们将从这里开始了解您！"
+    ambassador_join_note_strong: "注意"
+    ambassador_join_note_desc: "我们其中一个想法是要制造一个当玩家们在解决问题上有难度时可以要求其他比较强的玩家来协助他们的多人游戏。这将会是一个很好的办法让使节们可以更容易地协助玩家！如有任何更新，我们会在第一时间通知您！"
+    ambassador_subscribe_desc: "通过电子邮件获得支持系统的现状，以及多人游戏方面的新进展。"
+    teacher_subscribe_desc: "接收针对老师的更新和公告邮件。"
+    changes_auto_save: "在您勾选复选框后，更改将自动保存。"
+    diligent_scribes: "我们勤奋的文书:"
+    powerful_archmages: "我们强力的大法师:"
+    creative_artisans: "我们极具创意的工匠:"
+    brave_adventurers: "我们勇敢的冒险家:"
+    translating_diplomats: "我们遍及世界的外交官:"
+    helpful_ambassadors: "我们亲切的使节:"
+
+  ladder:
+#    title: "Multiplayer Arenas"
+#    arena_title: "__arena__ | Multiplayer Arenas"
+    my_matches: "我的对手"
+    simulate: "模拟"
+    simulation_explanation: "通过模拟游戏，您可以让您的游戏更快的得到评分。"
+    simulation_explanation_leagues: "你会主要给在你的部落或者课程的同伴帮忙模拟游戏。"
+    simulate_games: "模拟游戏！"
+    games_simulated_by: "由您模拟过的游戏数:"
+    games_simulated_for: "为您模拟过的游戏数:"
+    games_in_queue: "游戏正在列队中："
+    games_simulated: "模拟次数"
+    games_played: "被模拟次数"
+    ratio: "比率"
+    leaderboard: "排行榜"
+    battle_as: "我要加入这一方 "
+    summary_your: "您在 "
+    summary_matches: "的比赛 - "
+    summary_wins: " 胜利, "
+    summary_losses: " 失败"
+    rank_no_code: "没有新代码可供评分"
+    rank_my_game: "为我的游戏评分！"
+    rank_submitting: "正在提交..."
+    rank_submitted: "提交评分"
+    rank_failed: "评分失败"
+    rank_being_ranked: "已评价"
+    rank_last_submitted: "已提交"
+    help_simulate: "模拟游戏需要帮助？"
+    code_being_simulated: "您的新代码正在被其他玩家模拟评分。排名将会在新对战结束后刷新。"
+    no_ranked_matches_pre: "没有排名对决给 "
+    no_ranked_matches_post: " 啊哈哟！去和其他玩家打过再回来这里查看您游戏排名吧！"
+    choose_opponent: "选择一个对手"
+    select_your_language: "选择您使用的语言！"
+    tutorial_play: "玩教程"
+    tutorial_recommended: "如果您从未玩过的话,推荐先玩下教程"
+    tutorial_skip: "跳过教材"
+    tutorial_not_sure: "不知道怎么玩?"
+    tutorial_play_first: "先玩一次教程。"
+    simple_ai: "简单电脑"
+    warmup: "热身"
+    friends_playing: "联机"
+    log_in_for_friends: "登陆然后跟朋友一起玩！"
+    social_connect_blurb: "连接然后与朋友对战！"
+    invite_friends_to_battle: "邀请您的朋友参加战斗！"
+    fight: "战斗！"
+    watch_victory: "观看您的胜利"
+    defeat_the: "击败"
+    watch_battle: "观看战役"
+    tournament_started: "，锦标赛已开始"
+    tournament_ends: "锦标赛结束"
+    tournament_ended: "锦标赛已结束"
+    tournament_rules: "锦标赛规则"
+    tournament_blurb: "写代码，收金币，建军队，碎敌军，赢奖品，以及在我们奖励多达$40,000的Greed tournament里升级您的事业！ 快去查阅详情！"
+    tournament_blurb_criss_cross: "赢得竞拍，建造道路，智胜对手，夺取宝石，在纵横交错锦标赛中完成生涯晋级！ 现在就查看详情！"
+    tournament_blurb_zero_sum: "在红方法师和蓝方法师的山峰对决中, 同时在收集金币和战术上发挥你的代码创造力。竞赛在3月27日开始, 在4月6日(星期一)的下午5点(PDT 太平洋时区)结束。为乐趣和荣耀竞赛吧！ 浏览了解更多"
+    tournament_blurb_ace_of_coders: "我的主场我主宰，在天寒地冻的冰原上和自己的镜像大战一场吧！本次比赛将由9月16日星期三开办到10月14日下午5点PDT。详情请查看"
+    tournament_blurb_blog: "关注我们的博客"
+    rules: "规则"
+    winners: "胜利者"
+    league: "联盟"
+    red_ai: "红方 AI" # "Red AI Wins", at end of multiplayer match playback
+    blue_ai: "蓝方 AI"
+    wins: "胜利" # At end of multiplayer match playback
+    humans: "红方" # Ladder page display team name
+    ogres: "蓝方"
+    live_tournament: "最近的比赛"
+    awaiting_tournament_title: "比赛未开始"
+    awaiting_tournament_blurb: "比赛将于6月22日下午开启，敬请期待"
+    tournament_end_desc: "比赛暂未开放"
+
+  user:
+#    user_title: "__name__ - Learn to Code with CodeCombat"
+    stats: "成就"
+    singleplayer_title: "单人游戏关卡"
+    multiplayer_title: "多人游戏关卡"
+    achievements_title: "成就"
+    last_played: "最近一次通关"
+    status: "状态"
+    status_completed: "已完成"
+    status_unfinished: "未完成"
+    no_singleplayer: "还未玩过任何单人游戏关卡。"
+    no_multiplayer: "还未玩过任何多人游戏关卡。"
+    no_achievements: "还未得到任何成就。"
+    favorite_prefix: "最喜爱的语言是 "
+    favorite_postfix: "。"
+    not_member_of_clans: "还不是任何一个部落里的成员。"
+    certificate_view: "查看证书"
+    certificate_click_to_view: "点击查看证书"
+    certificate_course_incomplete: "课程未完成"
+    certificate_of_completion: "课程完成证书"
+    certificate_endorsed_by: "颁发人 "
+    certificate_stats: "学习数据"
+    certificate_lines_of: "行代码-语言"
+    certificate_levels_completed: "个关卡通关完成"
+    certificate_for: "课程名称"
+#    certificate_number: "No."
+
+  achievements:
+    last_earned: "最近取得的"
+    amount_achieved: "数量"
+    achievement: "成就"
+    current_xp_prefix: ""
+    current_xp_postfix: "当前总共"
+    new_xp_prefix: ""
+    new_xp_postfix: "取得的"
+    left_xp_prefix: ""
+    left_xp_infix: "直到等级"
+    left_xp_postfix: ""
+
+  account:
+    title: "账户"
+    settings_title: "账户设置"
+    unsubscribe_title: "取消订阅"
+    payments_title: "支付"
+    subscription_title: "订阅"
+    invoices_title: "发票"
+#    prepaids_title: "Prepaids"
+    payments: "支付方式"
+    prepaid_codes: "预付费码"
+    purchased: "已购买"
+    subscribe_for_gems: "订阅以获得宝石"
+    subscription: "订阅"
+    invoices: "票据"
+    service_apple: "设备：苹果"
+    service_web: "设备：网页"
+    paid_on: "支付"
+    service: "服务"
+    price: "价格"
+    gems: "宝石"
+    active: "激活"
+    subscribed: "已订阅"
+    unsubscribed: "取消订阅"
+    active_until: "已激活直到"
+    cost: "花费"
+    next_payment: "下次付款"
+    card: "银行卡"
+    status_unsubscribed_active: "您还没有订阅所以不会被收费，但您的账号还可以正常使用"
+    status_unsubscribed: "只要一个CodeCombat订阅，您就可以玩转全部新关卡，用遍全部英雄，物品，以及获得更多的宝石哦！"
+    not_yet_verified: "尚未验证。"
+    resend_email: "重发验证邮件"
+    email_sent: "邮件已发送！检查您的收件箱"
+    verifying_email: "验证邮箱地址中..."
+    successfully_verified: "验证成功！"
+    verify_error: "验证时出了点问题 :("
+#    unsubscribe_from_marketing: "Unsubscribe __email__ from all CodeCombat marketing emails?"
+#    unsubscribe_button: "Yes, unsubscribe"
+    unsubscribe_failed: "失败"
+    unsubscribe_success: "成功"
+
+  account_invoices:
+    amount: "金额（美元）"
+    declined: "您的付款被拒"
+    invalid_amount: "请输入美元金额。"
+    not_logged_in: "登录或创建一个账号来获得发票。"
+    pay: "付费发票"
+    purchasing: "购买中···"
+    retrying: "服务器错误，重试中。"
+    success: "支付成功。谢谢！"
+
+  account_prepaid:
+    purchase_code: "购买订阅码"
+    purchase_code1: "订阅码可以为一个或多个CodeCombat账号兑换额外的订阅时间。" # {change}
+    purchase_code2: "每个CodeCombat账号每次只能兑换一个订阅码。"
+    purchase_code3: "订阅码时间会在账号现有的订阅时间基础上延长。"
+    purchase_code4: "订阅码适用于玩独立版CodeCombat的账号，不能代替“课堂”版本的学生许可。"
+    purchase_code5: "有关学生许可的更多信息, 请联系"
+    users: "玩家"
+    months: "月份"
+    purchase_total: "总共"
+    purchase_button: "提交购买"
+    your_codes: "你的订阅码"
+    redeem_codes: "兑换订阅码"
+    prepaid_code: "预购码"
+    lookup_code: "查看预购码"
+    apply_account: "使用到您的账户"
+    copy_link: "您可以复制此代码链接发送给别人。"
+    quantity: "数量"
+    redeemed: "兑换"
+    no_codes: "还没有兑换码！"
+    you_can1: "您可以"
+    you_can2: "购买一个预购码"
+    you_can3: "使用在您的账户或者给其他人。"
+
+  ozaria_chrome:
+    sound_off: "关闭声音"
+    sound_on: "开启声音"
+    back_to_map: "返回地图"
+    level_options: "关卡选项"
+    restart_level: "重新开始这个关卡"
+
+  impact:
+    hero_heading: "创建世界一流的计算机科学课程"
+    hero_subheading: "我们赋能教育工作者，激励全球学习者"
+    featured_partner_story: "合作伙伴故事"
+    partner_heading: "美国最好的中学之一成功开展编程教学"
+    partner_school: "Bobby Duke 中学"
+    featured_teacher: "Scott Baily"
+    teacher_title: "信息技术老师, 科切拉谷，加利福尼亚"
+    implementation: "总体情况"
+    grades_taught: "面向年级"
+    length_use: "使用时期"
+    length_use_time: "3年"
+    students_enrolled: "今年报名的学生"
+    students_enrolled_number: "130"
+    courses_covered: "讲授课程"
+    course1: "计算机科学 1"
+    course2: "计算机科学 2"
+    course3: "计算机科学 3"
+    course4: "计算机科学 4"
+    course5: "游戏开发 1"
+    fav_features: "最爱功能"
+    responsive_support: "即时支持"
+    immediate_engagement: "场景交互"
+    paragraph1: "Bobby Duke 中学坐落在南加州科切拉谷山脉东西向，距离萨尔顿海33英里以南，共有697名学生。"
+    paragraph2: "Bobby Duke 中学的情况反映了科切拉谷居民和学生在社区内面临的社会经济挑战。学校里95%以上的中学生符合免费或补助餐食条件，超过40%的学生被归类为英语学习者，因此讲授21世纪技能是 Bobby Duke 中学的首要任务，信息技术老师 Scott Baily 说道。"
+    paragraph3: "Baily 深刻了解到在如今优先考虑和计算技能成为刚需的工作环境中，教会学生编写代码是获得机会的关键途径。因此，他决定接受一个令人兴奋的挑战，创建学校里唯一一门编程课程，并找到一个经济上能够负担，内容上即时反馈，并能吸引不同学习能力及不同背景学生们的解决方案。"
+    teacher_quote: "当我开始接触 CodeCombat 并开始让我的学生使用它时，就像点亮了我脑子里的一盏灯一样。这与与我接触的其他编程产品有着日与夜的差别，没有比 CodeCombat 更好的产品。"
+    quote_attribution: "Scott Baily, 信息技术老师"
+    read_full_story: "阅读全文"
+    more_stories: "更多合作伙伴故事"
+    partners_heading_1: "在一个班级中实现多个计算机科学课程的开展"
+    partners_school_1: "Preston 高中"
+    partners_heading_2: "在AP考试中脱颖而出 "
+    partners_school_2: "River Ridge 高中"
+    partners_heading_3: "零经验讲授计算机科学"
+    partners_school_3: "Riverdale 高中"
+    download_study: "下载研究报告"
+    teacher_spotlight: "老师和学生们的高光时刻"
+    teacher_name_1: "Amanda Henry"
+    teacher_title_1: "康复指导员"
+    teacher_location_1: "莫尔黑德，肯塔基州"
+    spotlight_1: "凭着一心想要帮助那些急需第二次机会的人的热情和动力，阿曼达改变了这群需要积极榜样的学生的生活。没有计算机科学经验的她，带领学生在区域编程竞赛中取得了成功。"
+    teacher_name_2: "Kaila, 学生"
+    teacher_title_2: "梅斯维尔社区与技术学院 列克星敦，肯塔基州"
+    teacher_location_2: "Lexington, Kentucky"
+    spotlight_2: "凯拉是一个从未想过会敲打代码的学生，更不用说考入通往光明未来的大学。"
+    teacher_name_3: "Susan Jones-Szabo"
+    teacher_title_3: "教师图书馆员"
+    teacher_school_3: "Ruby Bridges 小学"
+    teacher_location_3: "阿拉米达，加利福尼亚州"
+    spotlight_3: "苏珊在她的班级中提倡一种公平的氛围，每个人都能以自己的方式成功。犯错和挣扎也是受欢迎的，因为每个人都能从挑战中学习，包括老师。"
+    continue_reading_blog: "在博客中继续阅读..."
+
+  loading_error:
+    could_not_load: "载入失败" # {change}
+    connection_failure: "连接失败"
+    connection_failure_desc: "看起来你还没连接网络！检查网络连接后刷新此页面。"
+    login_required: "需要登录"
+    login_required_desc: "你需要登录才能访问此页面。"
+    unauthorized: "您需要登录才行。您是不是禁用了cookie？"
+    forbidden: "权限不足"
+    forbidden_desc: "啊，这里没什么可看的。确保你登录了正确的账户，或者点击下面的链接继续编程！"
+    user_not_found: "用户不存在"
+    not_found: "页面不存在"
+    not_found_desc: "嗯，这里什么都没有。点击下面的链接继续编程！"
+    not_allowed: "方法不允许。"
+    timeout: "服务器超时"
+    conflict: "资源冲突。"
+    bad_input: "错误输入。"
+    server_error: "服务器错误。"
+    unknown: "未知错误"
+    error: "错误"
+    general_desc: "出了点问题，估计是我们的错。请您稍后刷新页面，或者点击下面的链接继续编程！"
+
+  resources:
+    level: "等级"
+    patch: "补丁"
+    patches: "补丁"
+    system: "系统"
+    systems: "系统"
+    component: "组件"
+    components: "组件"
+    hero: "英雄"
+    campaigns: "战役"
+
+  concepts:
+    advanced_css_rules: "高级CSS规则"
+    advanced_css_selectors: "高级CSS选择器"
+    advanced_html_attributes: "高级HTML属性"
+    advanced_html_tags: "高级HTML标签"
+    algorithm_average: "均值算法"
+    algorithm_find_minmax: "最值算法"
+    algorithm_search_binary: "二分查找算法"
+    algorithm_search_graph: "图搜索算法"
+    algorithm_sort: "排序算法"
+    algorithm_sum: "求和算法"
+    arguments: "参数"
+    arithmetic: "四则运算"
+    array_2d: "二维数组"
+    array_index: "数组下标"
+    array_iterating: "数组迭代"
+    array_literals: "数组字面量"
+    array_searching: "数组遍历"
+    array_sorting: "数组排序"
+    arrays: "数组"
+    basic_css_rules: "基础CSS规则"
+    basic_css_selectors: "基础CSS选择器"
+    basic_html_attributes: "基础HTML属性"
+    basic_html_tags: "基础HTML标签"
+    basic_syntax: "基本语法"
+    binary: "二进制"
+    boolean_and: "布尔值与"
+    boolean_inequality: "布尔值不等于"
+    boolean_equality: "布尔值等于"
+    boolean_greater_less: "Boolean Greater/Less"
+    boolean_logic_shortcircuit: "布尔值逻辑短路"
+    boolean_not: "布尔值非"
+    boolean_operator_precedence: "布尔值运算优先级"
+    boolean_or: "布尔值或"
+#    boolean_with_xycoordinates: "Coordinate Comparison"
+    bootstrap: "Bootstrap"
+    break_statements: "Break语句"
+    classes: "类"
+    continue_statements: "Continue语句"
+    dom_events: "DOM事件"
+    dynamic_styling: "动态样式"
+    events: "事件"
+#    event_concurrency: "Event Concurrency"
+#    event_data: "Event Data"
+#    event_handlers: "Event Handlers"
+#    event_spawn: "Spawn Event"
+    for_loops: "For循环"
+#    for_loops_nested: "Nested For Loops"
+#    for_loops_range: "For Loops Range"
+    functions: "函数"
+    functions_parameters: "参数"
+    functions_multiple_parameters: "多个参数"
+    game_ai: "游戏AI"
+    game_goals: "游戏目标"
+#    game_spawn: "Game Spawn"
+    graphics: "图形"
+    graphs: "图"
+    heaps: "堆"
+    if_condition: "If条件语句"
+    if_else_if: "If/Else If语句"
+    if_else_statements: "If/Else语句"
+    if_statements: "If语句"
+    if_statements_nested: "嵌套的If语句"
+    indexing: "数组下标"
+#    input_handling_flags: "Input Handling - Flags"
+#    input_handling_keyboard: "Input Handling - Keyboard"
+#    input_handling_mouse: "Input Handling - Mouse"
+#    intermediate_css_rules: "Intermediate CSS Rules"
+#    intermediate_css_selectors: "Intermediate CSS Selectors"
+#    intermediate_html_attributes: "Intermediate HTML Attributes"
+#    intermediate_html_tags: "Intermediate HTML Tags"
+    jquery: "jQuery"
+    jquery_animations: "jQuery动画"
+    jquery_filtering: "jQuery元素过滤"
+    jquery_selectors: "jQuery选择器"
+    length: "数组长度"
+#    math_coordinates: "Coordinate Math"
+    math_geometry: "几何"
+    math_operations: "数学程序库操作"
+#    math_proportions: "Proportion Math"
+#    math_trigonometry: "Trigonometry"
+    object_literals: "对象字面量"
+    parameters: "形参"
+    programs: "程序"
+    properties: "属性"
+#    property_access: "Accessing Properties"
+#    property_assignment: "Assigning Properties"
+#    property_coordinate: "Coordinate Property"
+#    queues: "Data Structures - Queues"
+#    reading_docs: "Reading the Docs"
+    recursion: "递归"
+    return_statements: "return语句"
+    stacks: "数据结构 - 栈"
+    strings: "字符串"
+    strings_concatenation: "连接字符串"
+    strings_substrings: "子串"
+    trees: "数据结构 - 树"
+    variables: "变量"
+    vectors: "向量"
+    while_condition_loops: "使用条件语句的While循环"
+    while_loops_simple: "While循环"
+    while_loops_nested: "While嵌套循环"
+    xy_coordinates: "坐标对"
+    advanced_strings: "高级字符串" # Rest of concepts are deprecated
+    algorithms: "算法"
+    boolean_logic: "布尔逻辑"
+    basic_html: "基础 HTML"
+    basic_css: "基础 CSS"
+    basic_web_scripting: "基础 Web 脚本"
+    intermediate_html: "中级 HTML"
+    intermediate_css: "中级 CSS"
+    intermediate_web_scripting: "中级 Web 脚本"
+    advanced_html: "高级 HTML"
+    advanced_css: "高级 CSS"
+    advanced_web_scripting: "高级 Web 脚本"
+    input_handling: "输入处理"
+    while_loops: "While循环"
+    place_game_objects: "设置游戏对象"
+    construct_mazes: "设置迷宫"
+    create_playable_game: "创建可参与可分享的游戏关卡"
+    alter_existing_web_pages: "更改现有网页"
+    create_sharable_web_page: "创建一个可分享的网页"
+    basic_input_handling: "基本输入"
+    basic_game_ai: "基本游戏AI"
+    basic_javascript: "基本JavaScript"
+    basic_event_handling: "基本事件处理"
+    create_sharable_interactive_web_page: "创建一个可分享的互动网页"
+
+  anonymous_teacher:
+    notify_teacher: "通知老师"
+    create_teacher_account: "创建免费的老师账号"
+    enter_student_name: "你的名字:"
+    enter_teacher_email: "你的老师的电子邮箱:"
+    teacher_email_placeholder: "teacher.email@example.com"
+    student_name_placeholder: "输入你的名字"
+    teachers_section: "老师:"
+    students_section: "学生:"
+    teacher_notified: "已经通知给你的老师，你想要在课堂上玩更多CodeCombat游戏。"
+
+  delta:
+    added: "被添加"
+    modified: "被修改"
+    not_modified: "未修改"
+    deleted: "被删除"
+    moved_index: "被移动的索引"
+    text_diff: "文本变化"
+    merge_conflict_with: "合并冲突发生在"
+    no_changes: "没有变化"
+
+  legal:
+    page_title: "法律"
+    opensource_introduction: "CodeCombat是开源社区的一部分"
+    opensource_description_prefix: "查看 "
+    github_url: "我们的GitHub"
+    opensource_description_center: "并做您想做的修改吧！CodeCombat是构筑在几十个开源项目之上的，然后我们都喜欢它们。看 "
+    archmage_wiki_url: "我们大法师的维基页"
+    opensource_description_suffix: " 看看是哪些人让这个游戏成为可能。"
+    practices_title: "尊重最佳实践"
+    practices_description: "这是我们对您的承诺，即玩家，尽管这在法律用语中略显不足。"
+    privacy_title: "隐私"
+    privacy_description: "我们不会泄露您的个人信息。"
+    security_title: "安全"
+    security_description: "我们竭力保证您的个人信息安全性。作为一个开源项目，任何人都可以检讨并改善我们自由开放的网站的安全性。"
+    email_title: "电子邮件"
+    email_description_prefix: "我们不会发给您垃圾邮件。通过"
+    email_settings_url: "您的电子邮件设置"
+    email_description_suffix: "或者我们发送的邮件中的链接，您可以随时更改您的偏好设置或者随时取消订阅。"
+    cost_title: "花费"
+    cost_description: "CodeCombat的所有核心关卡都是免费的，需要${{price}}美元/月的订阅费来访问额外关卡分支并获得{{gems}}宝石奖励每月。你可以通过勾选来取消，我们提供100%退款保证。"
+    copyrights_title: "版权与许可"
+    contributor_title: "贡献者许可协议"
+    contributor_description_prefix: "所有对本网站或是GitHub代码库的贡献都依照我们的"
+    cla_url: "贡献者许可协议（CLA）"
+    contributor_description_suffix: "而这在您贡献之前就应该已经同意。"
+    code_title: "代码 - MIT" # {change}
+#    client_code_description_prefix: "All client-side code for codecombat.com in the public GitHub repository and in the codecombat.com database, is licensed under the"
+    mit_license_url: "MIT 许可证"
+    code_description_suffix: "这包括所有CodeCombat公开的制作关卡用的系统和组件代码。"
+    art_title: "美术和音乐 - Creative Commons"
+    art_description_prefix: "所有共通的内容都在"
+    cc_license_url: "Creative Commons Attribution 4.0 International License"
+    art_description_suffix: "条款下公开。共通内容是指所有CodeCombat发布出来用于制作关卡的内容。这包括："
+    art_music: "音乐"
+    art_sound: "声效"
+    art_artwork: "图画"
+    art_sprites: "精灵"
+    art_other: "所有制作关卡时公开的，不是代码的创造性产品。"
+    art_access: "目前还没有简便通用的下载素材的方式。一般来讲，从网站上使用的URL下载，或者联系我们寻找帮助。当然您也可以帮我们扩展网站，让这些资源更容易下载。"
+    art_paragraph_1: "关于署名，请说明并在使用处附近，或对媒体形式来说合适的地方提供一个CodeCombat.com 的链接。举例："
+    use_list_1: "如果是用在电影里或者其他游戏里，请在制作人员表中加入CodeCombat.com。"
+    use_list_2: "如果用在网站上，将链接在使用的地方附近，比如图片下面，或者一个您放置其他 Creative Commons 署名和开源软件协议的专门页面。如果您的内容明确提到关于CodeCombat，那您就不需要额外署名。"
+    art_paragraph_2: "如果您使用的内容不是由CodeCombat制作，而是由CodeCombat.com 上其他的用户制作的，那您应该给他们署名。如果相应资源的页面上有署名指示，那您应该遵循那些指示。"
+    rights_title: "版权所有"
+    rights_desc: "所有关卡由他们自己版权所有。这包括"
+    rights_scripts: "脚本"
+    rights_unit: "单元配置"
+    rights_writings: "作品"
+    rights_media: "声音、音乐以及其他专门为某个关卡制作，而不对其他关卡开放的创造性内容"
+    rights_clarification: "澄清：所有在关卡编辑器里公开用于制作关卡的资源都是在CC协议下发布的，而使用关卡编辑器制作，或者在关卡制作过程中上传的内容则不是。"
+    nutshell_title: "简而言之"
+    nutshell_description: "我们在关卡编辑器里公开的任何资源，您都可以在制作关卡时随意使用，但我们保留限制在CodeCombat.com 之上创建的关卡本身传播的权利，因为我们以后可能决定为它们收费。"
+    nutshell_see_also: "参见:"
+    canonical: "这篇说明的英文版本是权威版本。如果各个翻译版本之间有任何冲突，请以英文版为准。"
+    third_party_title: "第三方服务"
+    third_party_description: "CodeCombat使用了下列第三方服务（除了别的以外）："
+    cookies_message: "CodeCombat使用了必要和非必要的cookies."
+    cookies_deny: "禁用非必要的cookies"
+
+  ladder_prizes:
+    title: "竞标赛奖项" # This section was for an old tournament and doesn't need new translations now.
+    blurb_1: "这些奖项将会奖励，依据"
+    blurb_2: "锦标赛规则"
+    blurb_3: ", 给顶尖的人类和怪物玩家。"
+    blurb_4: "两队表示获得的奖励将会是两倍！"
+    blurb_5: "(将有两个第一名, 两个第二名, 以此类推。)"
+    rank: "排名"
+    prizes: "奖项"
+    total_value: "总价值"
+    in_cash: "现金"
+    custom_wizard: "定制CodeCombat巫师"
+    custom_avatar: "定制CodeCombat头像"
+    heap: "给六个月的\"Startup\"访问"
+    credits: "信用卡"
+    one_month_coupon: "优惠: 选择 Rails 或者 HTML"
+    one_month_discount: "折扣 30% : 选择 Rails 或者 HTML"
+    license: "许可证"
+    oreilly: "您选择的电子书"
+
+  calendar:
+    year: "年"
+    day: "日"
+    month: "月"
+    january: "一月"
+    february: "二月"
+    march: "三月"
+    april: "四月"
+    may: "五月"
+    june: "六月"
+    july: "七月"
+    august: "八月"
+    september: "九月"
+    october: "十月"
+    november: "十一月"
+    december: "十二月"
+
+  code_play_create_account_modal:
+#    title: "You did it!" # This section is only needed in US, UK, Mexico, India, and Germany
+#    body: "You are now on your way to becoming a master coder. Sign up to receive an extra <strong>100 Gems</strong> & you will also be entered for a chance to <strong>win $2,500 & other Lenovo Prizes</strong>."
+    sign_up: "注册 & 继续编程 ▶"
+#    victory_sign_up_poke: "Create a free account to save your code & be entered for a chance to win prizes!"
+#    victory_sign_up: "Sign up & be entered to <strong>win $2,500</strong>"
+
+  server_error:
+    email_taken: "电子邮件已注册。"
+    username_taken: "用户名已注册。"
+
+  esper:
+    line_no: "第$1行: "
+    uncaught: "未捕获的错误$1" # $1 will be an error type, eg "Uncaught SyntaxError"
+    reference_error: "参考错误："
+    argument_error: "参数错误："
+    type_error: "拼写错误："
+    syntax_error: "语法错误："
+    error: "错误："
+    x_not_a_function: "`$1` 不是一个函数"
+    x_not_defined: "`$1` 未定义"
+    spelling_issues: "请注意拼写错误：你指的是`$1`而不是`$2`, 对吗？"
+    capitalization_issues: "请注意大小写：`$1` 应该改成 `$2`？"
+#    py_empty_block: "Empty $1. Put 4 spaces in front of statements inside the $2 statement."
+    fx_missing_paren: "如果你需要把`$1` 称为函数，你需要加`()`'s"
+    unmatched_token: "非匹配`$1`。每一个开放的`$2`都需要一个`$3`配对。"
+    unterminated_string: "未终止字符串，在字符串末加上另外一个`\"`。"
+#    missing_semicolon: "Missing semicolon."
+#    missing_quotes: "Missing quotes. Try `$1`"
+#    argument_type: "`$1`'s argument `$2` should have type `$3`, but got `$4`: `$5`."
+#    argument_type2: "`$1`'s argument `$2` should have type `$3`, but got `$4`."
+    target_a_unit: "攻击一个目标。"
+    attack_capitalization: "攻击 $1，而不是 $2。  (重要：请区分大小写。)"
+#    empty_while: "Empty while statement. Put 4 spaces in front of statements inside the while statement."
+#    line_of_site: "`$1`'s argument `$2` has a problem. Is there an enemy within your line-of-sight yet?"
+    need_a_after_while: "在`$2`后面需要一个`$1`。"
+#    too_much_indentation: "Too much indentation at the beginning of this line."
+#    missing_hero: "Missing `$1` keyword; should be `$2`."
+    takes_no_arguments: "`$1`无参数。"
+#    no_one_named: "There's no one named \"$1\" to target."
+#    separated_by_comma: "Function calls paramaters must be seperated by `,`s"
+#    protected_property: "Can't read protected property: $1"
+#    need_parens_to_call: "If you want to call `$1` as function, you need `()`'s"
+#    expected_an_identifier: "Expected an identifier and instead saw '$1'."
+#    unexpected_identifier: "Unexpected identifier"
+#    unexpected_end_of: "Unexpected end of input"
+#    unnecessary_semicolon: "Unnecessary semicolon."
+#    unexpected_token_expected: "Unexpected token: expected $1 but found $2 while parsing $3"
+    unexpected_token: "意外的标记$1"
+    unexpected_token2: "意外的标记"
+    unexpected_number: "意外的数字"
+    unexpected: "意外的 '$1'。"
+#    escape_pressed_code: "Escape pressed; code aborted."
+#    target_an_enemy: "Target an enemy by name, like `$1`, not the string `$2`."
+#    target_an_enemy_2: "Target an enemy by name, like $1."
+#    cannot_read_property: "Cannot read property '$1' of undefined"
+#    attempted_to_assign: "Attempted to assign to readonly property."
+#    unexpected_early_end: "Unexpected early end of program."
+#    you_need_a_string: "You need a string to build; one of $1"
+#    unable_to_get_property: "Unable to get property '$1' of undefined or null reference" # TODO: Do we translate undefined/null?
+#    code_never_finished_its: "Code never finished. It's either really slow or has an infinite loop."
+#    unclosed_string: "Unclosed string."
+#    unmatched: "Unmatched '$1'."
+#    error_you_said_achoo: "You said: $1, but the password is: $2. (Capital letters are important.)"
+#    indentation_error_unindent_does: "Indentation Error: unindent does not match any outer indentation level"
+#    indentation_error: "Indentation error."
+#    need_a_on_the: "Need a `:` on the end of the line following `$1`."
+#    attempt_to_call_undefined: "attempt to call '$1' (a nil value)"
+#    unterminated: "Unterminated `$1`"
+    target_an_enemy_variable: "以变量$1作为目标, 而不是字符串$2。 (试着用$3。)"
+#    error_use_the_variable: "Use the variable name like `$1` instead of a string like `$2`"
+#    indentation_unindent_does_not: "Indentation unindent does not match any outer indentation level"
+#    unclosed_paren_in_function_arguments: "Unclosed $1 in function arguments."
+#    unexpected_end_of_input: "Unexpected end of input"
+#    there_is_no_enemy: "There is no `$1`. Use `$2` first." # Hints start here
+    try_herofindnearestenemy: "试一试 `$1`"
+    there_is_no_function: "没有`$1`函数，但是`$2`有`$3`函数。"
+#    attacks_argument_enemy_has: "`$1`'s argument `$2` has a problem."
+#    is_there_an_enemy: "Is there an enemy within your line-of-sight yet?"
+    target_is_null_is: "目标$1出现了问题（可能是null）。这里总是有攻击目标吗？(试着用$2。)"
+    hero_has_no_method: "`$1` 没有 `$2` 函数。"
+    there_is_a_problem: "你的代码有问题。"
+#    did_you_mean: "Did you mean $1? You do not have an item equipped with that skill."
+#    missing_a_quotation_mark: "Missing a quotation mark. "
+#    missing_var_use_var: "Missing `$1`. Use `$2` to make a new variable."
+#    you_do_not_have: "You do not have an item equipped with the $1 skill."
+#    put_each_command_on: "Put each command on a separate line"
+    are_you_missing_a: "在`$2`后面有没有`$1`？"
+#    your_parentheses_must_match: "Your parentheses must match."
+
+#  apcsp:
+#    title: "AP Computer Science Principals | College Board Endorsed"
+#    meta_description: "CodeCombat’s comprehensive curriculum and professional development program are all you need to offer College Board’s newest computer science course to your students."
+#    syllabus: "AP CS Principles Syllabus"
+#    syllabus_description: "Use this resource to plan CodeCombat curriculum for your AP Computer Science Principles class."
+#    computational_thinking_practices: "Computational Thinking Practices"
+#    learning_objectives: "Learning Objectives"
+#    curricular_requirements: "Curricular Requirements"
+#    unit_1: "Unit 1: Creative Technology"
+#    unit_1_activity_1: "Unit 1 Activity: Technology Usability Review"
+#    unit_2: "Unit 2: Computational Thinking"
+#    unit_2_activity_1: "Unit 2 Activity: Binary Sequences"
+#    unit_2_activity_2: "Unit 2 Activity: Computing Lesson Project"
+#    unit_3: "Unit 3: Algorithms"
+#    unit_3_activity_1: "Unit 3 Activity: Algorithms - Hitchhiker's Guide"
+#    unit_3_activity_2: "Unit 3 Activity: Simulation - Predator & Prey"
+#    unit_3_activity_3: "Unit 3 Activity: Algorithms - Pair Design and Programming"
+#    unit_4: "Unit 4: Programming"
+#    unit_4_activity_1: "Unit 4 Activity: Abstractions"
+#    unit_4_activity_2: "Unit 4 Activity: Searching & Sorting"
+#    unit_4_activity_3: "Unit 4 Activity: Refactoring"
+#    unit_5: "Unit 5: The Internet"
+#    unit_5_activity_1: "Unit 5 Activity: How the Internet Works"
+#    unit_5_activity_2: "Unit 5 Activity: Internet Simulator"
+#    unit_5_activity_3: "Unit 5 Activity: Chat Room Simulation"
+#    unit_5_activity_4: "Unit 5 Activity: Cybersecurity"
+#    unit_6: "Unit 6: Data"
+#    unit_6_activity_1: "Unit 6 Activity: Introduction to Data"
+#    unit_6_activity_2: "Unit 6 Activity: Big Data"
+#    unit_6_activity_3: "Unit 6 Activity: Lossy & Lossless Compression"
+#    unit_7: "Unit 7: Personal & Global Impact"
+#    unit_7_activity_1: "Unit 7 Activity: Personal & Global Impact"
+#    unit_7_activity_2: "Unit 7 Activity: Crowdsourcing"
+#    unit_8: "Unit 8: Performance Tasks"
+#    unit_8_description: "Prepare students for the Create Task by building their own games and practicing key concepts."
+#    unit_8_activity_1: "Create Task Practice 1: Game Development 1"
+#    unit_8_activity_2: "Create Task Practice 2: Game Development 2"
+#    unit_8_activity_3: "Create Task Practice 3: Game Development 3"
+#    unit_9: "Unit 9: AP Review"
+#    unit_10: "Unit 10: Post-AP"
+#    unit_10_activity_1: "Unit 10 Activity: Web Quiz"
+
+  parent_landing:
+    slogan_quote: "\"CodeCombat 真的很有趣，我学会了编程.\""
+#    quote_attr: "5th Grader, Oakland, CA"
+    refer_teacher: "分享给老师"
+    focus_quote: "成就孩子的未来"
+    value_head1: "学习真实代码最互动的方式"
+    value_copy1: "CodeCombat 是孩子的个人老师。与国家计算机课程标准一致, 你的孩子将学习编程、算法, 建立网站, 甚至设计自己的游戏。"
+    value_head2: "掌握21世纪必备技能"
+    value_copy2: "CodeCombat 帮助提高孩子们的逻辑思辨思维和应变能力，教会你的孩子如何在科技世界中入门及成长。"
+    value_head3: "孩子们喜欢的卡通人物"
+    value_copy3: "趣味和互动对发育中的大脑十分重要, 所以我们把尽可能多的知识融入到每个孩子都喜爱的互动游戏环境中。"
+    dive_head1: "不仅仅是成为软件工程师"
+    dive_intro: "计算机科学技能应用广泛，看看下面的例子吧"
+    medical_flag: "医学应用"
+    medical_flag_copy: "从人类基因组的映射到核磁共振，代码让我们以前所未有的方式，了解身体。"
+    explore_flag: "太空探索"
+    explore_flag_copy: "由于人类计算机的辛勤工作，阿波罗登上了月球。科学家们利用计算机程序分析行星的引力并寻找新恒星。"
+    filmaking_flag: "制作电影和动画"
+    filmaking_flag_copy: "从《侏罗纪公园》的机器人到梦工厂和皮克斯令人难以置信的动画，没有幕后的数字创意，电影就没有那样精彩了。"
+    dive_head2: "游戏对于学习很重要"
+    dive_par1: "多项研究发现，基于互动的学习能够促进知识的吸收。"
+    dive_link1: "如认识发展的行业报告，"
+    dive_par2: "在孩子证明了这一点。"
+    dive_link2: "这种高效的方法"
+    dive_par3: "使孩子们在学习过程中"
+    dive_link3: "对知识点的掌握更加扎实"
+    dive_par4: "，"
+    dive_link4: "精力更加集中"
+    dive_par5: "，更能收获成就感。"
+    dive_par6: "基于游戏的学习对培养孩子们的抗压性也有好处。"
+    dive_link5: "不怕失败"
+    dive_par7: "，解决问题，和"
+    dive_par8: "。科学就是告诉我们已经知道了的道理，孩子们在游戏互动中学习效率最高。"
+    dive_link6: "管理协作能力"
+    dive_head3: "和老师一起组队"
+    dive_3_par1: "将来，"
+    dive_3_link1: "就像阅读和写作一样，编程会成为一种基本的生活技能"
+    dive_3_par2: "。我们与老师密切合作，设计开发最适合孩子们的内容，我们等不及让你的孩子参与进来。像 CodeCombat 这样的教学项目最好是由老师长期进行。帮助我们通过介绍您孩子的老师来建立这种联系!"
+    mission: "我们的使命是教学与参与"
+    mission1_heading: "为今天的一代而编程"
+    mission2_heading: "为未来而准备"
+    mission3_heading: "来自家长的支持"
+    mission1_copy: "我们的教育专家与教师紧密合作，以满足孩子们在教育领域的需求。孩子们所学技能可以在游戏之外应用，因为他们掌握了解决问题的能力，不管他们的学习方式是什么。"
+    mission2_copy: "2016年的一项调查显示，3-5年级中64%的女生想学习编程。2015年需要的编码技能相关岗位有700万个。我们创建 CodeCombat 是希望每个孩子都应有机会去创造他们最好的未来。"
+    mission3_copy: "在 CodeCombat 中，我们是父母，我们程序员，我们是教育工作者。但最重要的是，我们相信无论孩子们决定做什么，都应该给予他们最好的成功机会。"
+
+  parent_modal:
+    refer_teacher: "指导老师"
+    name: "你的名字"
+    parent_email: "你的邮箱"
+    teacher_email: "你的老师的邮箱"
+    message: "消息"
+    custom_message: "我刚刚找到了CodeCombat，我想它对你的教室来说是个很棒的程序!它是一个符合标准课程的计算机科学学习平台。计算机知识是非常重要的，我认为这将是一个让学生学习编码的好方法。"
+    send: "发送邮件"
+
+  hoc_2018:
+#    banner: "Welcome to Hour of Code 2019!"
+    page_heading: "教你的学生如何建立自己的街机游戏!" # {change}
+#    step_1: "Step 1: Watch Video Overview"
+#    step_2: "Step 2: Try it Yourself"
+#    step_3: "Step 3: Download Lesson Plan"
+#    try_activity: "Try Activity"
+    download_pdf: "下载 PDF"
+#    teacher_signup_heading: "Turn Hour of Code into a Year of Code"
+#    teacher_signup_blurb: "Everything you need to teach computer science, no prior experience needed."
+#    teacher_signup_input_blurb: "Get first course free:"
+#    teacher_signup_input_placeholder: "Teacher email address"
+#    teacher_signup_input_button: "Get CS1 Free"
+#    activities_header: "More Hour of Code Activities"
+#    activity_label_1: "Escape the Dungeon!"
+#    activity_label_2: " Beginner: Build a Game!"
+#    activity_label_3: "Advanced: Build an Arcade Game!"
+#    activity_button_1: "View Lesson"
+    about: "关于 CodeCombat 游戏" # {change}
+    about_copy: "CodeCombat 是一个基于游戏的计算机科学程序，用于教真正的 Python 和 JavaScript。CodeCombat 的标准课程是围绕学生喜欢的游戏进行设计。超过1200万的学生已经学会在 CodeCombat 上编程!" # {change}
+    point1: "✓ 搭建完毕"
+    point2: "✓ 分区完毕"
+    point3: "✓ 过程和总结评价" # {change}
+    point4: "✓ 基于项目的课程"
+    point5: "✓ 学生追踪"
+    point6: "✓ 全部课程计划"
+#    title: "HOUR OF CODE 2019"
+#    acronym: "HOC"
+
+#  hoc_2018_interstitial:
+#    welcome: "Welcome to CodeCombat's Hour of Code 2019!"
+#    educator: "I'm an educator"
+#    show_resources: "Show me teacher resources!"
+#    student: "I'm a student"
+#    ready_to_code: "I'm ready to code!"
+
+#  hoc_2018_completion:
+#    congratulations: "Congratulations on completing <b>Code, Play, Share!</b>"
+#    send: "Send your Hour of Code game to friends and family!"
+#    copy: "Copy URL"
+#    get_certificate: "Get a certificate of completion to celebrate with your class!"
+#    get_cert_btn: "Get Certificate"
+#    first_name: "First Name"
+#    last_initial: "Last Initial"
+#    teacher_email: "Teacher's email address"
+
+  school_administrator:
+#    title: "School Administrator Dashboard"
+    my_teachers: "我的老师"
+    last_login: "上次登录"
+    licenses_used: "已使用许可证"
+    total_students: "学生总数"
+    active_students: "活跃学生数"
+    projects_created: "学生作品数"
+    other: "其他"
+    notice: "以下学校管理员对您的课堂数据有查看权限："
+    add_additional_teacher: "需要添加其他的教师吗？请联系您的CodeCombat客户经理或发送电子邮件至china@codecombat.com。 "
+    license_stat_description: "可用许可证是教师可用的许可证总数，包括共享的许可证。"
+    students_stat_description: "学生总数是所有教室的所有学生数，不管他们是否使用了许可证，都会被计算在内。"
+    active_students_stat_description: "活跃学生数是在过去60天登录CodeCombat的学生人数。"
+    project_stat_description: "学生作品数是学生已经创建的游戏开发和网页开发作品的总数。"
+    no_teachers: "你目前没有管理其他老师。"
+
+#  interactives:
+#    phenomenal_job: "Phenomenal Job!"
+#    try_again: "Whoops, try again!"
+#    select_statement_left: "Whoops, select a statement from the left before hitting \"Submit.\""
+#    fill_boxes: "Whoops, make sure to fill all boxes before hitting \"Submit.\""
+
+  browser_recommendation:
+#    title: "CodeCombat works best on Chrome!"
+    pitch_body: "为保证 CodeCombat 的所有功能正常运行，建议使用 Chrome 浏览器进行学习。"
+    download: "下载 CHROME 浏览器"
+    ignore: "忽略"


Update zh-HANS.coffee (#4924)

Update zh-HANS.coffee (#4924)

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -99,8 +99,8 @@
     help_pref: "需要帮助？请发邮件至："
     help_suff: " 我们会很快与您联系！"
     resource_hub: "资源中心"
-#    apcsp: "AP CS Principles"
-#    parent: "Parents"
+    apcsp: "美联社计算机科学原则"
+    parent: "家长参与"

   modal:
     close: "关闭"
@@ -138,7 +138,7 @@
     poll: "投票" # Tooltip on poll button from /play
     next: "下一页" # Go from choose hero to choose inventory before playing a level
     change_hero: "重新选择英雄" # Go back from choose inventory to choose hero
-#    change_hero_or_language: "Change Hero or Language"
+    change_hero_or_language: "切换英雄或者语言"
     buy_gems: "购买宝石"
     subscribers_only: "仅订阅者可用！"
     subscribe_unlock: "订阅解锁！"
@@ -236,7 +236,7 @@
     create_individual_header: "创建独立账号"
     email_announcements: "通过邮件接收新关卡和功能通知！"
     sign_in_to_continue: "登录或创建一个账号继续"
-#    teacher_email_announcements: "Keep me updated on new teacher resources, curriculum, and courses!"
+    teacher_email_announcements: "让我们在教师资源和课程上进行不断更新！"
     creating: "账户创建中……"
     sign_up: "注册"
     log_in: "登录"
@@ -297,11 +297,11 @@
     teacher_students_can_start_now: "学生将在第一堂课上开始玩游戏，立即开始介绍计算机科学吧！"
     teacher_list_create_class: "在下一个屏幕上，你能否创建一个新的班级"
     teacher_list_add_students: "通过点击‘查看班级’按钮来增加学生，之后将班级代码或者URL发送给你的学生。如果他们有邮箱，你也可以通过邮箱邀请他们。"
-#    teacher_list_resource_hub_1: "Check out the"
+    teacher_list_resource_hub_1: "查看"
     teacher_list_resource_hub_2: "课程指南"
-#    teacher_list_resource_hub_3: "for solutions to every level, and the"
+    teacher_list_resource_hub_3: "每一个登记的解决方案，和"
     teacher_list_resource_hub_4: "资源仓库"
-#    teacher_list_resource_hub_5: "for curriculum guides, activities, and more!"
+    teacher_list_resource_hub_5: "课程指导，课程活动和更多信息！"
     teacher_additional_questions: "就是这样！如果您还需要额外的帮助或者遇到了什么问题，请联系__supportEmail__。"
     dont_use_our_email_silly: "请不要在这里填写我们的邮箱地址！要填写你家长的邮箱地址。"
     want_codecombat_in_school: "想要一辈子都玩CodeCombat?"
@@ -367,7 +367,7 @@
     rejected: "未接受"
     withdrawn: "已撤回"
     accept: "接受"
-#    accept_and_save: "Accept&Save"
+    accept_and_save: "接受并保存"
     reject: "拒绝"
     withdraw: "撤回"
     submitter: "提交者"
@@ -416,7 +416,7 @@
     learn_more: "学习更多"
     more: "更多"
     fewer: "更少"
-#    with: "with"
+    with: "和"

   units:
     second: "秒"
@@ -441,7 +441,7 @@
     edit_level: "编辑关卡"
     keep_learning: "不断学习"
     explore_codecombat: "探索CodeCombat"
-#    finished_hoc: "I'm finished with my Hour of Code"
+    finished_hoc: "我完成了我的编程练习时间"
     get_certificate: "获取你的证书！"
     level_complete: "关卡已完成"
     completed_level: "完成关卡:"
@@ -521,8 +521,8 @@
     skip_tutorial: "跳过（esc）"
     keyboard_shortcuts: "快捷键"
     loading_start: "开始"
-#    loading_start_combo: "Start Combo Challenge"
-#    loading_start_concept: "Start Concept Challenge"
+    loading_start_combo: "开始组合挑战"
+    loading_start_concept: "开始概念挑战"
     problem_alert_title: "修正您的代码"
     time_current: "现在:"
     time_total: "最大:"
@@ -601,12 +601,12 @@
     tip_programming_not_about_computers: "计算机科学就是有关计算机的，正如天文学就是有关望远镜的。 - 艾兹格·迪杰斯特拉"
     tip_mulan: "相信你能行，你就能做到。 - 迪士尼《花木兰》"
     project_complete: "关卡完成！"
-#    share_this_project: "Share this project with friends or family:"
-#    ready_to_share: "Ready to publish your project?"
-#    click_publish: "Click \"Publish\" to make it appear in the class gallery, then check out what your classmates built! You can come back and continue to work on this project. Any further changes will automatically be saved and shared with your classmates."
-#    already_published_prefix: "Your changes have been published to the class gallery."
-#    already_published_suffix: "Keep experimenting and making this project even better, or see what the rest of your class has built! Your changes will automatically be saved and shared with your classmates."
-#    view_gallery: "View Gallery"
+    share_this_project: "和你的朋友家人分享这个项目吧 "
+    ready_to_share: "准备好发布你的项目吗？"
+    click_publish: "点击“发布”按钮，让它出现在课堂展览中。你可以检查你的同学们创建的内容。你可以回来继续在这个项目中工作。未来任何的改变都会自动被保存并且分享给你的同学们！"
+    already_published_prefix: "你的改变已经发布到班级的展览中。"
+    already_published_suffix: "不断尝试并且让这个项目变得更好，或者查看你的同学们已经创造的东西！你的改变将会自动保存并且与你的同学们分享！"
+    view_gallery: "查看展览"
     project_published_noty: "您的关卡已发布！"
     keep_editing: "继续编辑"

@@ -617,10 +617,10 @@
     html: "HTML"
     math: "数学"
     array: "数组"
-#    object: "Object"
+    object: "对象"
     string: "字符串"
     function: "函数"
-#    vector: "Vector"
+    vector: "向量"
     date: "日期"
     jquery: "jQuery"
     json: "JSON"
@@ -631,11 +631,11 @@
 #    title: "Keep Learning with Amazon!"
 #    congrats: "Congratulations on conquering that challenging Hour of Code!"
 #    educate_1: "Now, keep learning about coding and cloud computing with AWS Educate, an exciting, free program from Amazon for both students and teachers. With AWS Educate, you can earn cool badges as you learn about the basics of the cloud and cutting-edge technologies such as gaming, virtual reality, and Alexa."
-#    educate_2: "Learn more and sign up here"
+    educate_2: "了解更多并登陆"
 #    future_eng_1: "You can also try to build your own school facts skill for Alexa"
-#    future_eng_2: "here"
+    future_eng_2: "这里"
 #    future_eng_3: "(device is not required). This Alexa activity is brought to you by the"
-#    future_eng_4: "Amazon Future Engineer"
+    future_eng_4: "亚马逊未来的工程师"
 #    future_eng_5: "program which creates learning and work opportunities for all K-12 students in the United States who wish to pursue computer science."

   play_game_dev_level:
@@ -1422,29 +1422,29 @@
     current_hero: "当前英雄:"
     current_hero_female: "当前英雄:"
     web_dev_language_transition: "在本课中，所有的编程语言都是使用HTML和JavaScript编写。之前使用Python语言学习的班级会有额外的JavaScript编程介绍关卡来过渡。已经选择JavaScript编程语言的班级将会跳过这些介绍关卡。 "
-#    course_membership_required_to_play: "You'll need to join a course to play this level."
-#    license_required_to_play: "Ask your teacher to assign a license to you so you can continue to play CodeCombat!"
+    course_membership_required_to_play: "你将需要加入一个课程来玩这个关卡"
+    license_required_to_play: "询问你的老师来注册一个许可证，之后你可以继续玩CodeCombat游戏！"
     update_old_classroom: "新学年, 新关卡!"
     update_old_classroom_detail: "为确保您获得最新关卡，请您尽量在本学期创建新的班级，方法是单击"
     teacher_dashboard: "老师面板"
     update_old_classroom_detail_2: "创建新班级，并为学生提供出现的新班级代码。"
     view_assessments: "查看评估"
     view_challenges: "查看挑战关卡"
-#    challenge: "Challenge:"
+    challenge: "挑战"
     challenge_level: "挑战关卡:"
-#    status: "Status:"
+    status: "状态"
     assessments: "评估"
-#    challenges: "Challenges"
-#    level_name: "Level Name:"
-#    keep_trying: "Keep Trying"
-#    start_challenge: "Start Challenge"
-#    locked: "Locked"
-#    concepts_used: "Concepts Used:"
-#    show_change_log: "Show changes to this course's levels"
-#    hide_change_log: "Hide changes to this course's levels"
+    challenges: "挑战"
+    level_name: "关卡名称："
+    keep_trying: "开始尝试"
+    start_challenge: "开始挑战"
+    locked: "锁定"
+    concepts_used: "使用的概念"
+    show_change_log: "显示这个课程等级的变化"
+    hide_change_log: "隐藏这个课程等级的变化"

   project_gallery:
-#    no_projects_published: "Be the first to publish a project in this course!"
+    no_projects_published: "成为第一个在这个课程中发布项目的人吧！"
     view_project: "查看项目"
     edit_project: "编辑项目"

@@ -1504,7 +1504,7 @@
     enroll_student: "注册学生"
     apply_license: "使用许可"
     revoke_license: "终止许可"
-#    revoke_licenses: "Revoke All Licenses"
+    revoke_licenses: "终止所有许可"
     course_progress: "课程进度"
     not_applicable: "N/A"
     edit: "修改"
@@ -1520,7 +1520,7 @@
     started: "已开始"
     no_progress: "无进度"
     not_required: "不需要"
-#    view_student_code: "Click to view student code"
+    view_student_code: "点击查看学生的代码"
     select_course: "选择查看"
     progress_color_key: "进度颜色指示："
     level_in_progress: "正在通关"
@@ -1571,7 +1571,7 @@
     licenses_remaining: "剩余许可证"
     one_license_used: "1个许可证已被使用" # {change}
     num_licenses_used: "__numLicensesUsed__个许可证已被使用" # {change}
-#    starter_licenses: "starter licenses"
+    starter_licenses: "开始使用许可证"
     start_date: "起始日期："
     end_date: "结束日期："
     get_enrollments_blurb: " 我们会帮您解决您的班级、学校或地区的需求。"


Minor bug fix: clear all intervals

Minor bug fix: clear all intervals

--- client/app/MainApp/monitor_status.coffee
+++ client/app/MainApp/monitor_status.coffee
@@ -237,4 +237,7 @@
     log 'disconnected, stopping broker ping'

     clearInterval brokerInterval
-    brokerInterval = null
+    clearTimeout failureCallback
+    brokerInterval  = null
+    failureCallback = null
+    lastPong        = null


don't modifiy path if it can't be parsed

--- app/controllers/todolists_controller.coffee
+++ app/controllers/todolists_controller.coffee
@@ -18,7 +18,7 @@
             try
                 list.path = JSON.parse list.path
             catch error
-                list.path = ""
+
         send length: todoLists.length, rows: todoLists

 ###
@@ -43,7 +43,6 @@
 # exist it is created.
 ###
 before 'load tree', ->
-
     createTreeCb = (err, tree) ->
         if err
             console.log err


fix from hotkey api change in ba8b65b789d78e16f0eb8c51113e1ca212446e81

--- src/undo-manager.coffee
+++ src/undo-manager.coffee
@@ -56,9 +56,11 @@
   initListeners: ->
     @editor.keyboard.addHotkey(Scribe.Keyboard.HOTKEYS.UNDO, =>
       this.undo()
+      return false
     )
     @editor.keyboard.addHotkey(Scribe.Keyboard.HOTKEYS.REDO, =>
       this.redo()
+      return false
     )
     @ignoringChanges = false
     @editor.on(Scribe.Editor.events.USER_TEXT_CHANGE, (delta) =>


Fix the reduce function. Yet again...

Fix the reduce function. Yet again...

--- client/controller/vote-controller.coffee
+++ client/controller/vote-controller.coffee
@@ -29,4 +29,4 @@
     - sum(speaker.proposals().map((p) -> p.voteCount()))
   )

-sum = (arr) -> if arr.length == 0 return 0 else return _.reduce(arr, (sum, num) -> sum + num)+sum = (arr) -> _.reduce(arr, ((sum, num) -> sum + num), 0)


Identify error

Identify error

--- client/lib/cordova/facebook-login.coffee
+++ client/lib/cordova/facebook-login.coffee
@@ -16,12 +16,12 @@
 		facebookConnectPlugin.getLoginStatus (response) ->
 			if response.status isnt "connected"
 				facebookConnectPlugin.login ["public_profile", "email"], fbLoginSuccess, (error) ->
-					console.log(JSON.stringify(error), error)
+					console.log('login', JSON.stringify(error), error)
 			else
 				fbLoginSuccess(response)

 		, (error) ->
-			console.log(JSON.stringify(error), error)
+			console.log('getLoginStatus', JSON.stringify(error), error)

 	else
 		Facebook.requestCredential(options, credentialRequestCompleteCallback)


dynamic bind mouseover

dynamic bind mouseover

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -67,8 +67,14 @@
 			$(@).after(el)
 			$(@).remove()
 		@restoreSelection()
-	assignNewNameToCurrentSelectedElement: ->
-		@getSelectedElement().attr 'name', @rand()
+	assignNameAttribute: (jqel)->
+		jqel.attr 'name', @rand()
+	bindTo: (jqel)->
+		jqel.on 'mousemove', (e)->
+			parentOffset = $(@).parent().offset()
+
+			thisOffset = $(@).offset()
+			console.log "x:#{e.pageX - thisOffset.left} , y:#{e.pageY - thisOffset.top}"
 	update: ->
 		self = @
 		# giving names
@@ -166,7 +172,8 @@
 		switch e.keyCode
 			when 13
 				@divsToPs()
-				@assignNewNameToCurrentSelectedElement()
+				@assignNameAttribute @getSelectedElement()
+				@bindTo @getSelectedElement()
 			when 16
 				@status.shift = false
 				$('.shift').addClass('hidden')
@@ -188,7 +195,10 @@
 		@articleDeleteURL = options.articleDeleteURL

 	init: ()->
-		$('#editor').html '

'
+		self = @
+		$('#editor').html '

' if @status.new
+		$('#editor').children().each ->
+			self.bindTo $(@)
 		@update()
 		@checkTitleEmpty()
 		@checkEmpty()
@@ -212,6 +222,9 @@
 					item.name
 				$.each attributes, (i, key)=>
 					$(@).removeAttr(key)
+			self = @
+			$(pasteElement).children().each ->
+				@bindTo $(@)
 			document.execCommand 'insertHtml', false, $(pasteElement).text()
 		.blur =>
 			@updateTitlePrompt(false)


chore(config): Alignment.

chore(config): Alignment.

--- lib/config.coffee
+++ lib/config.coffee
@@ -4,18 +4,18 @@

     properties:
       align:
-        title:    'Alignment "="'
+        title:       'Alignment "="'
         description: 'Controls the alignment of the "=" operator to the left or right hand side (e.g. "x= 1", "x =1").'
-        type:    'string'
-        enum:    ['left', 'right']
-        default: 'right'
+        type:        'string'
+        enum:        ['left', 'right']
+        default:     'right'

       padding:
-        title:    'Padding "="'
+        title:       'Padding "="'
         description: 'Controls the surrounding padding of the "=" operator (e.g. "= ", " =", " = ", "=").'
-        type:    'string'
-        enum:    ['left', 'right', 'both', 'none']
-        default: 'both'
+        type:        'string'
+        enum:        ['left', 'right', 'both', 'none']
+        default:     'both'

   ':':
     type: 'object'
@@ -29,26 +29,26 @@
         default: 'left'

       padding:
-        title:    'Padding ":"'
+        title:       'Padding ":"'
         description: 'Controls the surrounding padding of the ":" operator (e.g. ": ", " :", " : ", ":").'
-        type:    'string'
-        enum:    ['left', 'right', 'both', 'none']
-        default: 'right'
+        type:        'string'
+        enum:        ['left', 'right', 'both', 'none']
+        default:     'right'

   '=>':
     type: 'object'

     properties:
       align:
-        title:    'Alignment "=>"'
+        title:       'Alignment "=>"'
         description: 'Controls the alignment of the "=>" operator to the left or right hand side (e.g. "x=> 1", "x =>1").'
-        type:    'string'
-        enum:    ['left', 'right']
-        default: 'right'
+        type:        'string'
+        enum:        ['left', 'right']
+        default:     'right'

       padding:
-        title:    'Padding "=>"'
+        title:       'Padding "=>"'
         description: 'Controls the surrounding padding of the "=>" operator (e.g. "=> ", " =>", " => ", "=>").'
-        type:    'string'
-        enum:    ['left', 'right', 'both', 'none']
-        default: 'both'
+        type:        'string'
+        enum:        ['left', 'right', 'both', 'none']
+        default:     'both'


payment: call all factory methods consistently

now they're all a little long-winded, but it's very clear what they do.  Maybe we ought to replace them with constructors at some point.

--- node_modules_koding/koding-payment/lib/main.coffee
+++ node_modules_koding/koding-payment/lib/main.coffee
@@ -13,13 +13,13 @@

   # Recurly's error entry structure. I'll use the same structure
   # for socket/http errors.
-  errorObj = (short='', message='', field='')-> {short, message, field}
+  createErrorObject = (short='', message='', field='')-> {short, message, field}

   summarizeErrors = (errors)->
     billing = 0
     for e in errors
       if e.field.indexOf('subscription.account.billing_info') > -1 and ++billing > 2
-        return errorObj 'billing', 'Credit card information required.'
+        return createErrorObject 'billing', 'Credit card information required.'

   # If requested list of records are longer than 50 items, recurly
   # paginates them. This method gets the URL path of next page.
@@ -59,17 +59,17 @@
         # Parse XML and build JS object, or list of errors.
         xml2js.parseString body, (err, result)->
           if err
-            callback [errorObj 'xml', 'Invalid XML']
+            callback [createErrorObject 'xml', 'Invalid XML']
           else if result.error
-            callback [errorObj result.error.symbol[0], result.error.description[0]._]
+            callback [createErrorObject result.error.symbol[0], result.error.description[0]._]
           else if result.errors
             # See https://docs.recurly.com/api/basics/validation-errors for XML format
             errors = []
             for err1 in result.errors.error
               if err1.$
-                errors.push errorObj err1.$.symbol, err1._, err1.$.field
+                errors.push createErrorObject err1.$.symbol, err1._, err1.$.field
               else
-                errors.push errorObj err1, err1
+                errors.push createErrorObject err1, err1
             callback errors
           else
             # Find next page's URL if necessary
@@ -77,7 +77,7 @@
             callback null, result, nextUrl

     req.on 'error', (e)->
-      callback [errorObj 'connection', 'Unable to connect to payment backend.']
+      callback [createErrorObject 'connection', 'Unable to connect to payment backend.']

     req.end(data, 'utf8')

@@ -122,7 +122,7 @@
   # See https://docs.recurly.com/api/plans for documentation
   #

-  planObj = (plan)->
+  createPlanObject = (plan)->
     title       : plan.name[0]
     code        : plan.plan_code[0]
     desc        : ifString plan.description[0]
@@ -136,7 +136,7 @@

       plans = []
       for page in res when page.plans?.plan
-        plans.push planObj p  for p in page.plans.plan
+        plans.push createPlanObject p  for p in page.plans.plan

       callback null, plans

@@ -145,9 +145,9 @@
       return callback err  if err

       if res[0]?.plan
-        callback null, planObj res[0].plan
+        callback null, createPlanObject res[0].plan
       else
-        callback errorObj 'plan', 'Requested plan not found'
+        callback createErrorObject 'plan', 'Requested plan not found'

   @createPlan: (data, callback)->
     data.feeInitial  ?= 0
@@ -170,7 +170,7 @@
       """

     post 'plans', xml, (err, res)->
-      callback err, unless err then planObj res.plan
+      callback err, unless err then createPlanObject res.plan

   @updatePlan: (data, callback)->
     data.feeInitial  ?= 0
@@ -192,7 +192,7 @@
       """

     put "plans/#{data.code}", xml, (err, res)->
-      callback err, unless err then planObj res.plan
+      callback err, unless err then createPlanObject res.plan

   @deletePlan: (data, callback)->
     del "plans/#{data.code}", callback
@@ -202,7 +202,7 @@
   # See https://docs.recurly.com/api/subscriptions for documentation
   #

-  subObj = (sub)->
+  createSubscriptionObject = (sub)->
     sub =
       plan        : sub.plan[0].plan_code[0]
       quantity    : sub.quantity[0]._
@@ -227,7 +227,7 @@

       subs = []
       for page in res
-        subs.push subObj s  for s in page.subscriptions
+        subs.push createSubscriptionObject s  for s in page.subscriptions

       callback null, subs

@@ -236,9 +236,9 @@
       return callback err  if err

       if res[0]?.subscription
-        callback null, subObj res[0].subscription
+        callback null, createSubscriptionObject res[0].subscription
       else
-        callback errorObj 'subscription', 'Requested subscription not found'
+        callback createErrorObject 'subscription', 'Requested subscription not found'

   @createSubscription: (accountCode, data, callback)->
     data.quantity ?= 1
@@ -257,7 +257,7 @@
       """

     post 'subscriptions', xml, (err, res)->
-      callback err, unless err then subObj res.subscription
+      callback err, unless err then createSubscriptionObject res.subscription

   @updateSubscription: (accountCode, data, callback)->
     data.quantity ?= 1
@@ -274,28 +274,28 @@
       """

     put "subscriptions/#{data.uuid}", xml, (err, res)->
-      callback err, unless err then subObj res.subscription
+      callback err, unless err then createSubscriptionObject res.subscription

   @terminateSubscription: (accountCode, data, callback)->
     data.refund ?= 'none'

     put "subscriptions/#{data.uuid}/terminate?refund=#{data.refund}", '', (err, res)->
-      callback err, unless err then subObj res.subscription
+      callback err, unless err then createSubscriptionObject res.subscription

   @cancelSubscription: (accountCode, data, callback)->
     put "subscriptions/#{data.uuid}/cancel", '', (err, res)->
-      callback err, unless err then subObj res.subscription
+      callback err, unless err then createSubscriptionObject res.subscription

   @reactivateUserSubscription: (accountCode, data, callback)->
     put "subscriptions/#{data.uuid}/reactivate", '', (err, res)->
-      callback err, unless err then subObj res.subscription
+      callback err, unless err then createSubscriptionObject res.subscription

   #
   # TRANSACTIONS
   # See https://docs.recurly.com/api/transactions for documentation
   #

-  transObj = (t)->
+  createTransactionObject = (t)->
     trans =
       uuid       : t.uuid[0]
       status     : t.status[0]
@@ -322,7 +322,7 @@

       trans = []
       for page in res
-        trans.push transObj t  for t in page.transactions?.transaction
+        trans.push createTransactionObject t  for t in page.transactions?.transaction

       callback null, trans

@@ -340,7 +340,7 @@
       """

     post 'transactions', xml, (err, res)->
-      callback err, unless err then transObj res.transaction
+      callback err, unless err then createTransactionObject res.transaction

   @deleteTransaction: (accountCode, data, callback)->
     del "transactions/#{data.uuid}?amount_in_cents=##{data.amount}", callback
@@ -350,7 +350,7 @@
   # See https://docs.recurly.com/api/invoices for XML format
   #

-  invoiceObj = (i)->
+  createInvoiceObject = (i)->
     uuid     : i.uuid[0]
     amount   : i.total_in_cents[0]._
     currency : i.currency[0]
@@ -362,7 +362,7 @@

       invs = []
       for page in res
-        invs.push invoiceObj i  for i in page.invocies?.invoice
+        invs.push createInvoiceObject i  for i in page.invocies?.invoice

       callback null, invs

@@ -371,14 +371,14 @@
   # on subscription's renewal (typically monthly)
   @chargePending: (accountCode, callback)->
     post "accounts/#{accountCode}/invoices", '', (err, res)->
-      callback err, unless err then transObj res.transaction
+      callback err, unless err then createTransactionObject res.transaction

   #
   # ADJUSTMENTS (ONE-TIME CHARGE/CREDIT TO SUBSCRIPTION)
   # See https://docs.recurly.com/api/invoices for XML format
   #

-  adjObj = (a)->
+  createAdjustmentObject = (a)->
     uuid        : a.uuid[0]
     amount      : a.total_in_cents[0]._
     currency    : a.currency[0]
@@ -397,7 +397,7 @@

       adjs = []
       for page in res
-        adjs.push adjObj a  for a in page.adjustments?.adjustment
+        adjs.push createAdjustmentObject a  for a in page.adjustments?.adjustment

       callback null, adjs

@@ -426,7 +426,7 @@
       """

     post "accounts/#{accountCode}/adjustments", xml, (err, res)->
-      callback err, unless err then adjObj res.adjustment
+      callback err, unless err then createAdjustmentObject res.adjustment

   @createCharge: @createAdjustment

@@ -439,7 +439,7 @@
   # See https://docs.recurly.com/api/accounts for XML format
   #

-  accObj = (acc, billing) ->
+  createAccountObject = (acc, billing) ->
     firstName : acc.first_name[0]
     lastName  : acc.last_name[0]
     email     : acc.email[0]
@@ -452,7 +452,7 @@
       return callback err  if err
       @fetchBillingByAccountCode accountCode, (err, billing) ->
         return callback err  if err
-        callback null, accObj res[0]?.account, billing
+        callback null, createAccountObject res[0]?.account, billing

   @setAccountDetailsByAccountCode: (accountCode, data, callback) ->


prevent editor caret disappear

prevent editor caret disappear

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -173,8 +173,8 @@
 		$('body').delegate '#tooltip', 'mouseleave', (e)->
 			self.hideTooltip()

-		$('body').delegate '#editor', 'click', (e)->
-			$(@).focus()
+		# $('body').delegate '#editor', 'click', (e)->
+		# 	$('#editor').focus()

 		# toolpad
 		$('body').delegate '#editor', 'mouseup', (e)=>
@@ -333,8 +333,6 @@
 			@handleKeyUp(e)
 			@update()
 			@checkEmpty()
-		.on 'click', (e)->
-			$(@).focus()
 		.on 'paste', (e)=>
 			e.preventDefault()
 			raw = e.clipboardData.getData('text/html') || e.clipboardData.getData('text')
@@ -359,8 +357,11 @@
 			@updatePrompt(false)
 			@clearStatus()
 			# @hideInsertion()
-		.focus =>
-			@updatePrompt(true)
+		.focus (e)=>
+			# prevent caret being eaten
+			setTimeout =>
+				@updatePrompt(true)
+			, 0
 			@update()
 			@clearStatus()


Capsulate code in Pocket class

Capsulate code in Pocket class

--- app/scripts/pocket.coffee
+++ app/scripts/pocket.coffee
@@ -1,9 +1,11 @@
 'use strict'

-initialize = ->
-  element = document.createElement 'div'
-  element.classList.add 'pocket'
-  element.innerText = 'Pocket'
-  document.body.appendChild element
+class Pocket
+  constructor: ->
+    element = document.createElement 'div'
+    element.classList.add 'pocket'
+    element.innerText = 'Pocket'
+    document.body.appendChild element

-initialize()
+
+pocket = new Pocket()


Permissions: Deny requests when delegate is null

--- workers/social/lib/social/models/group/validators.coffee
+++ workers/social/lib/social/models/group/validators.coffee
@@ -47,6 +47,9 @@

   any:(client, group, permission, permissionSet, callback)->
     {delegate} = client.connection
+    unless delegate
+      console.warn "Delegate cannot be null for checking permissions, request is denied"
+      return callback null, no
     roleSelector = getRoleSelector delegate, group, permission, permissionSet
     return callback null, yes  if roleSelector is -1
     Relationship.count roleSelector, createExistenceCallback callback


If runKey was passed as URL parameter, pass it to document server when loading the document.

--- src/code/providers/document-store-provider.coffee
+++ src/code/providers/document-store-provider.coffee
@@ -194,6 +194,7 @@
       dataType: 'json'
       data:
         recordid: metadata.providerData?.id or metadata.sharedContentId
+        runKey: if @client.appOptions.hashParams.runKey then @client.appOptions.hashParams.runKey else undefined
       context: @
       xhrFields:
         {withCredentials}


Change the name of the group search option.

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -27,11 +27,11 @@
     @disable_search_threshold = @options.disable_search_threshold || 0
     @disable_search = @options.disable_search || false
     @enable_split_word_search = if @options.enable_split_word_search? then @options.enable_split_word_search else true
+    @group_search = if @options.group_search? then @options.group_search else true
     @search_contains = @options.search_contains || false
     @single_backstroke_delete = @options.single_backstroke_delete || false
     @max_selected_options = @options.max_selected_options || Infinity
     @inherit_select_classes = @options.inherit_select_classes || false
-    @search_groups = @options.search_groups || true

   set_default_text: ->
     if @form_field.getAttribute("data-placeholder")
@@ -123,7 +123,7 @@

         option.group_match = false if option.group

-        unless option.group and not @search_groups
+        unless option.group and not @group_search
           option.search_match = false

           search_string = if option.group then option.label else option.html


Fix a bug on cell saving

Fix a bug on cell saving

--- src/cell.coffee
+++ src/cell.coffee
@@ -95,6 +95,7 @@

 		value = @_.value
 		if value
+			value = value + ""
 			if value == value.trim()
 				obj.v = [value]
 			else


CustomScrolls: start listening on constructor

--- src/components/scrollview/customscrollview.coffee
+++ src/components/scrollview/customscrollview.coffee
@@ -36,6 +36,10 @@
     @wrapper.on 'ScrollTrackShown',  (type) => @setClass   "has-#{type}"
     @wrapper.on 'ScrollTrackHidden', (type) => @unsetClass "has-#{type}"

+    @wrapper.on 'MutationHappened', =>
+      @verticalTrack.thumb.reset()
+      @horizontalTrack.thumb.reset()
+
     @on 'mouseenter', @bound 'showTracks'
     @on 'mouseleave', @bound 'hideTracks'

@@ -72,9 +76,6 @@

     @wrapper.observeMutations()

-    @wrapper.on 'MutationHappened', =>
-      @verticalTrack.thumb.reset()
-      @horizontalTrack.thumb.reset()

   intent = null


EditorModal: set initial editor options

EditorModal: set initial editor options

--- client/Main/CommonViews/editormodal.coffee
+++ client/Main/CommonViews/editormodal.coffee
@@ -8,6 +8,7 @@
     options.height        = 400 # currently 400 for styling
     options.overlay      ?= yes
     options.overlayClick ?= no
+    options.editor       ?= {}

     super options, data


activity: use isReactivityEnabled util to add Reactivity cssClass

--- client/activity/lib/activityappview.coffee
+++ client/activity/lib/activityappview.coffee
@@ -16,6 +16,7 @@
 isChannelCollaborative = require 'app/util/isChannelCollaborative'
 isKoding               = require 'app/util/isKoding'
 isGroup                = require 'app/util/isGroup'
+isReactivityEnabled    = require 'app/util/isReactivityEnabled'
 ChatSearchModal        = require 'app/activity/sidebar/chatsearchmodal'
 isSuggestionEnabled    = require 'activity/util/isSuggestionEnabled'

@@ -33,6 +34,9 @@

     options.cssClass   = 'content-page activity clearfix'
     options.domId      = 'content-page-activity'
+
+    if isReactivityEnabled()
+      options.cssClass = kd.utils.curry 'Reactivity', options.cssClass

     super options, data


index the index.html

index the index.html

--- elasticsearch.coffee
+++ elasticsearch.coffee
@@ -32,9 +32,10 @@
     api: Object.keys(gist.api || {})
     colors: Object.keys(gist.colors || {})
     #tags: gist.tags || []
+    #files:
     readme: gist.readme || ""
+    code: gist.code || ""
     filenames: Object.keys(gist.files)
-    #files:
   }

   thumb = gist.files["thumbnail.png"]?.raw_url
@@ -110,8 +111,18 @@

     if gist.files["thumbnail.png"]
       gist.thumbnail = gist.files["thumbnail.png"].raw_url
+    if gist.files["preview.png"]
+      gist.thumbnail = gist.files["thumbnail.png"].raw_url
+
+    # TODO make this more robust
     if gist.files["README.md"]
       gist.readme = gist.files["README.md"].content
+    if gist.files["readme.md"]
+      gist.readme = gist.files["readme.md"].content
+
+    # for now we will just index
+    if gist.files["index.html"]
+      gist.code = gist.files["index.html"].content

     es = pruneES(gist)
     #console.log "ES", JSON.stringify(es)


Default is now really correct

Default is now really correct

--- lib/config.coffee
+++ lib/config.coffee
@@ -103,4 +103,4 @@
       title: 'Locale when running PROS CLI'
       description: 'If the default value isn\'t working, try changing this field to C.UTF-8 '+\
                    'or to \'inherit\' to possibly inherit from the system environment.'
-      default: 'en_us.utf-8'
+      default: 'en_US.UTF-8'


Tests: Collaboration: Start: Increase wait timers.

--- client/test/lib/collaboration/start.coffee
+++ client/test/lib/collaboration/start.coffee
@@ -24,8 +24,8 @@

       browser
         .waitForElementVisible  secondUserAvatar, 60000
-        .waitForElementVisible  secondUserOnlineAvatar, 20000 # Assertion
-        .waitForElementVisible  chatTextSelector, 20000
+        .waitForElementVisible  secondUserOnlineAvatar, 50000 # Assertion
+        .waitForElementVisible  chatTextSelector, 50000
         .assert.containsText    chatTextSelector, 'CHAT' # Assertion

@@ -55,22 +55,22 @@
     else
       browser
         .waitForElementVisible     shareModal, 200000 # wait for vm turn on for host
-        .waitForElementVisible     fullName, 20000
+        .waitForElementVisible     fullName, 50000
         .assert.containsText       shareModal, firstUserName
-        .waitForElementVisible     acceptButton, 20000
-        .waitForElementVisible     rejectButton, 20000
+        .waitForElementVisible     acceptButton, 50000
+        .waitForElementVisible     rejectButton, 50000
         .click                     acceptButton
-        .waitForElementVisible     loadingButton, 20000
-        .waitForElementNotPresent  shareModal, 20000
-        .waitForElementVisible     selectedMachine, 20000
-        .waitForElementVisible     chatBox, 20000
-        .waitForElementVisible     chatUsers, 20000
-        .waitForElementVisible     message, 20000
+        .waitForElementVisible     loadingButton, 50000
+        .waitForElementNotPresent  shareModal, 50000
+        .waitForElementVisible     selectedMachine, 50000
+        .waitForElementVisible     chatBox, 50000
+        .waitForElementVisible     chatUsers, 50000
+        .waitForElementVisible     message, 50000
         .assert.containsText       chatBox, firstUserName
         .assert.containsText       chatBox, secondUserName
         .assert.containsText       filetree, firstUserName
-        .waitForElementVisible     userAvatar, 20000 # Assertion
-        .waitForElementVisible     chatTextSelector, 20000
+        .waitForElementVisible     userAvatar, 50000 # Assertion
+        .waitForElementVisible     chatTextSelector, 50000
         .assert.containsText       chatTextSelector, 'CHAT' # Assertion


Matrix copy constructor

Matrix copy constructor

--- test/matrix.coffee
+++ test/matrix.coffee
@@ -20,9 +20,9 @@

    matrix2 = Matrix(matrix)

-   qmatrix
+   ok matrix != matrix2
    matrixEqual(matrix2, matrix)
-
+
   test "Matrix() (Identity)", ->
     matrix = Matrix()


Reduce overhead DIVs in layout

Reduce overhead DIVs in layout

--- src/client.cjsx
+++ src/client.cjsx
@@ -14,7 +14,7 @@

 Home = React.createClass
   render: ->
-

+

           <CodeEditor />
@@ -28,13 +28,7 @@
 Teacher = React.createClass
   render: ->

-

-

-

-            <Master />
-

-

-

+      <Master />

 About = React.createClass


Redirect op to file instead of writing it again.

--- lib/cscope.coffee
+++ lib/cscope.coffee
@@ -1,42 +1,32 @@
-{BufferedProcess} = require 'atom'
 ResultSetModel = require './models/result-set-model'
 fs = require 'fs'
 path = require 'path'
 spawn = require('child_process').spawn
+platform = require('os').platform()

 module.exports = CscopeCommands =
-  getSourceFiles: (project, exts) ->
-    args = ['.']
-    for ext,index in exts.split(/\s+/)
-      args.push '-o' if index > 0
-      args.push '-name'
-      args.push '*' + ext
-
-    return @runCommand 'find', args, {cwd: project}
+  getSourceFiles: (project, extStr) ->
+    exts = extStr.split(/\s+/)
+    out = fs.openSync(path.join(project, 'cscope.files'), 'w')
+    if platform == "win32"
+      cmd = 'dir'
+      args = ['/b/a/s'].concat(exts)
+    else
+      cmd = 'find'
+      args = [].concat.apply(['.', '-name', '*' + exts.shift()], ['-o', '-name', '*' + ext] for ext in exts)
+    return @runCommand cmd, args, {cwd: project, detached: true, stdio: ['ignore', out, 'pipe']}

   generateCscopeDB: (project) ->
     cscope_binary = atom.config.get('atom-cscope.cscopeBinaryLocation')
     return @runCommand cscope_binary, ['-qRbi', 'cscope.files'], {cwd: project}

-  writeToFile: (project, fileName, content) ->
-    filePath = path.join(project, fileName)
-    return new Promise (resolve, reject) ->
-      fs.writeFile filePath, content, (err) ->
-        reject {success: false, info: err.toString()} if err
-        resolve {success: true}
-
   setupCscopeForPath: (project, exts, force) ->
     cscopeExists = if force then Promise.reject force else @cscopeExists project
     cscopeExists.then (data) =>
       return Promise.resolve {success: true}
     .catch (data) =>
-      sourceFileGen = @getSourceFiles project, exts
-      writeCscopeFiles = sourceFileGen.then (data) =>
-        return @writeToFile project, 'cscope.files', data
-      dbGen = writeCscopeFiles.then (data) =>
+      return @getSourceFiles(project, exts).then (data) =>
         return @generateCscopeDB project
-
-      return Promise.all([sourceFileGen, writeCscopeFiles, dbGen])

   setupCscope: (projects, exts, force = false) ->
     promises = []
@@ -54,24 +44,19 @@

   runCommand: (command, args, options = {}) ->
     process = new Promise (resolve, reject) =>
-      complete = false
       output = ''
       child = spawn command, args, options
-      child.stdout.on 'data', (data) =>
+      if child.stdout != null then child.stdout.on 'data', (data) =>
         output += data.toString()
-      child.stderr.on 'data', (data) =>
-        if complete then return
-        complete = true
+      if child.stderr != null then child.stderr.on 'data', (data) =>
         reject data.toString()

       child.on 'error', (err) =>
-        if complete then return
-        complete = true
         reject err.toString()
       child.on 'close', (code) =>
-        if complete then return
-        complete = true
         if code != 0 then reject code else resolve output
+
+      if args.detached then child.unref()
     return process

   runCscopeCommand: (num, keyword, cwd) ->


fix namespace bug

fix namespace bug

--- coffeescript/love.js/graphics/image.coffee
+++ coffeescript/love.js/graphics/image.coffee
@@ -1,6 +1,6 @@
 class Love.Graphics.Image
   constructor: (data) ->
-    if data instanceof ImageData
+    if data instanceof Love.ImageModule.ImageData
       @element = document.createElement("img")
       @element.setAttribute("src", data.getString(data))
     else


testing 6 (travis.ci)

testing 6 (travis.ci)

--- test/server.coffee
+++ test/server.coffee
@@ -1,6 +1,7 @@
 require '../index.js'
 File  = require('pathfinder').File
 # require './secrets'
+# `npm install debug` for node 0.4.x

 global.chai   = require 'chai'
 global.assert = chai.assert


split artist test for only admins

split artist test for only admins

--- components/split_test/running_tests.coffee
+++ components/split_test/running_tests.coffee
@@ -34,6 +34,6 @@
   artist_page_format:
     key: 'artist_page_format'
     outcomes:
-      old: 0.5
-      new: 0.5
+      old: 1
+      new: 0
     edge: 'new'


fix #179

fix #179

--- lib/ensime.coffee
+++ lib/ensime.coffee
@@ -98,6 +98,11 @@
       default: 'right'
       enum: ['right', 'external-browser']
       order: 120
+    enableAutoInstallOfDependencies:
+      description: "Enable auto install of dependencies"
+      type: boolean
+      default: true
+      order: 130

   addCommandsForStoppedState: ->
     @stoppedCommands = new CompositeDisposable
@@ -141,8 +146,9 @@
     log.setLevel(logLevel)

     # Install deps if not there
-    (require 'atom-package-deps').install('Ensime').then ->
-      log.trace('Ensime dependencies installed, good to go!')
+    if(atom.config.get('Ensime.eenableAutoInstallOfDependencies')
+      (require 'atom-package-deps').install('Ensime').then ->
+        log.trace('Ensime dependencies installed, good to go!')

     @subscriptions = new CompositeDisposable


More typos around memberOfGroupOrReadable

--- lib/groups.coffee
+++ lib/groups.coffee
@@ -55,12 +55,13 @@
    (role in (user?.rolesPartial?[escapeGroup(group?.name ? group)]?[message2root message] ? [])))

 @groupPartialMessagesWithRole = (group, role, user = Meteor.user()) ->
+  group = group.name if group.name?
   message \
   for own message, roles of user?.rolesPartial?[escapeGroup group] ? [] \
   when role in roles

 @memberOfGroup = (group, user = Meteor.user()) ->
-  escaped = escapeGroup group
+  escaped = escapeGroup group.name ? group
   not _.isEmpty(user?.roles?[escaped]) or
   not _.isEmpty(user?.rolesPartial?[escaped])

@@ -68,7 +69,7 @@
 ## anonymous groups) and all groups of which you are a full or partial member.
 @memberOfGroupOrReadable = (group, user = Meteor.user()) ->
   memberOfGroup(group, user) or
-  groupRoleCheck groupData, 'read', user
+  groupRoleCheck group, 'read', user

 ## List all groups that the user is a member of.
 ## (Mimicking memberOfGroup above.)
@@ -167,7 +168,7 @@
       user = findUser @userId
       ## Publish members of all readable groups (including anonymous groups)
       ## and all groups of which you are a full or partial member.
-      if memberOfGroupOrReadable group, user
+      if memberOfGroupOrReadable groupData, user
         Meteor.users.find
           username: $in: groupMembers groupData
         ,


Speed: Reimplement group members computation

Instead of computing the group members every time a group gets displayed
(via a complex query to the users database), precompute and store the
members list in group's 'members' field, via live query on user roles.

--- lib/groups.coffee
+++ lib/groups.coffee
@@ -21,6 +21,7 @@
 @Groups = new Mongo.Collection 'groups'

 @findGroup = (group) ->
+  return group if group.name?
   Groups.findOne
     name: group

@@ -42,6 +43,14 @@
 @memberOfGroup = (group, user = Meteor.user()) ->
   roles = user?.roles?[escapeGroup group]
   roles and roles.length > 0
+
+## List all groups that the user is a member of.
+## (Mimicking memberOfGroup above.)
+@memberOfGroups = (user = Meteor.user()) ->
+  for group, roles of user?.roles ? {}
+    continue unless roles.length > 0
+    continue if group == wildGroup
+    unescapeGroup group

 if Meteor.isServer
   @readableGroups = (userId) ->
@@ -66,45 +75,79 @@
     @autorun ->
       readableGroups @userId

-  @groupMembers = (group, options) ->
-    ## Mimic memberOfGroup above
-    Meteor.users.find
-      "roles.#{escapeGroup group}":
-        $exists: true
-        $ne: []
-    , options
+  #@groupMembers = (group, options) ->
+  #  ## Mimic memberOfGroup above
+  #  Meteor.users.find
+  #    "roles.#{escapeGroup group}":
+  #      $exists: true
+  #      $ne: []
+  #  , options
+
+  ## Give all groups a 'members' array field, automatically updated to
+  ## contain all users that match memberOfGroup defined above.
+  ## The initial live query will get all memberships, so reset to empty.
+  ## (Also needed because $addToSet only works with fields containing arrays.)
+  Groups.update {}
+    #members: null
+  ,
+    $set: members: []
+  ,
+    multi: true
+
+  membersAddUsername = (username, groups) ->
+    if groups.length > 0
+      console.log 'adding', username, 'to', groups
+      Groups.update
+        name: $in: groups
+      ,
+        $addToSet: members: username
+      ,
+        multi: true
+
+  membersRemoveUsername = (username, groups) ->
+    if groups.length > 0
+      Groups.update
+        name: $in: groups
+      ,
+        $pull: members: username
+      ,
+        multi: true
+
+  Meteor.users.find
+    roles: $exists: true
+  ,
+    fields:
+      roles: true
+      username: true
+  .observe
+    added: (user) ->
+      membersAddUsername user.username, memberOfGroups user
+    removed: (user) ->
+      membersRemoveUsername user.username, memberOfGroups user
+    changed: (userNew, userOld) ->
+      groupsNew = memberOfGroups userNew
+      groupsOld = memberOfGroups userOld
+      membersRemoveUsername userOld.username, _.difference groupsOld, groupsNew
+      membersAddUsername userNew.username, _.difference groupsNew, groupsOld

   Meteor.publish 'groups.members', (group) ->
     check group, String
-    if groupRoleCheck group, 'read', findUser @userId
-      id = findGroup(group)._id
-      @autorun ->
-        groupMembers group,
+    @autorun ->
+      if groupRoleCheck group, 'read', findUser @userId
+        Meteor.users.find
+          username: $in: groupMembers group
+        ,
           fields:
             username: 1
             profile: 1
-      init = true
-      @autorun ->
-        members =
-          members: groupMembers(group, fields: username: 1).map (user) -> user.username
-        if init
-          init = false
-          members.group = group
-          @added 'groups.members', id, members
-        else
-          @changed 'groups.members', id, members
-    @ready()
-
-if Meteor.isClient
-  @GroupsMembers = new Mongo.Collection 'groups.members'
-
-  @groupMembers = (group) ->
-    GroupsMembers.findOne
-      group: group
-    ?.members ? []
-
-  @sortedGroupMembers = (group) ->
-    _.sortBy groupMembers(group), userSortKey
+      else
+        @ready()
+
+@groupMembers = (group) ->
+  findGroup(group).members
+
+@sortedGroupMembers = (group) ->
+  _.sortBy groupMembers(group), userSortKey

 Meteor.methods
   setRole: (group, user, role, yesno) ->