mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-14 01:31:15 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
1cfdee1679
14 changed files with 389 additions and 472 deletions
Binary file not shown.
|
@ -4,7 +4,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
no_ie: "抱歉!Internet Explorer 8 等旧式浏览器无法使用本网站。" # Warning that only shows up in IE8 and older
|
||||
no_mobile: "CodeCombat不是针对移动设备设计的,所以可能无法达到最佳体验!" # Warning that shows up on mobile devices
|
||||
play: "开始游戏" # The big play button that opens up the campaign view.
|
||||
# play_campaign_version: "Play Campaign Version" # Shows up under big play button if you only play /courses
|
||||
play_campaign_version: "玩战役模式" # Shows up under big play button if you only play /courses
|
||||
old_browser: "噢, 您的浏览器版本太旧了, 不能运行CodeCombat。抱歉!" # Warning that shows up on really old Firefox/Chrome/Safari
|
||||
old_browser_suffix: "您可以继续重试下去,但很可能不起作用,更新浏览器吧亲~"
|
||||
ipad_browser: "坏消息:CodeCombat无法在iPad的浏览器中运行。好消息:我们的iPad应用正在等待苹果公司审核通过。"
|
||||
|
@ -17,7 +17,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
nav:
|
||||
play: "关卡选择" # The top nav bar entry where players choose which levels to play
|
||||
community: "社区"
|
||||
# courses: "Courses"
|
||||
courses: "课程"
|
||||
editor: "编辑器"
|
||||
blog: "博客"
|
||||
forum: "论坛"
|
||||
|
@ -52,7 +52,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
|
||||
play:
|
||||
play_as: "扮演" # Ladder page
|
||||
# compete: "Compete!" # Course details page
|
||||
compete: "开战!" # Course details page
|
||||
spectate: "旁观他人的游戏" # Ladder page
|
||||
players: "玩家" # Hover over a level on /play
|
||||
hours_played: "游戏时长" # Hover over a level on /play
|
||||
|
@ -75,7 +75,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
subscription_required: "需订阅"
|
||||
anonymous: "匿名玩家"
|
||||
level_difficulty: "难度:"
|
||||
# play_classroom_version: "Play Classroom Version" # Choose a level in campaign version that you also can play in one of your courses
|
||||
play_classroom_version: "玩课堂模式" # Choose a level in campaign version that you also can play in one of your courses
|
||||
campaign_beginner: "新手作战"
|
||||
awaiting_levels_adventurer_prefix: "我们每周都会开放新关卡"
|
||||
awaiting_levels_adventurer: "注册成为冒险家"
|
||||
|
@ -560,14 +560,14 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
cat_blurb: "气宗"
|
||||
scott_title: "共同创始人" # {change}
|
||||
scott_blurb: "理性至上"
|
||||
# maka_title: "Customer Advocate"
|
||||
# maka_blurb: "Storyteller"
|
||||
maka_title: "客户律师"
|
||||
maka_blurb: "故事作者"
|
||||
rob_title: "编译器工程师" # {change}
|
||||
rob_blurb: "编代码之类的"
|
||||
josh_c_title: "游戏设计师"
|
||||
josh_c_blurb: "设计游戏"
|
||||
# robin_title: "UX Design & Research"
|
||||
# robin_blurb: "Scaffolding"
|
||||
robin_title: "用户体验设计和研究"
|
||||
robin_blurb: "基架"
|
||||
josh_title: "游戏设计师"
|
||||
josh_blurb: "地面是熔岩"
|
||||
retrostyle_title: "插画师"
|
||||
|
@ -594,15 +594,15 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
being_reviewed_2: "审核。"
|
||||
approved_1: "您的免费订阅试用申请已被" # {change}
|
||||
approved_2: "批准。" # {change}
|
||||
# approved_4: "You can now enroll your students on the"
|
||||
# approved_5: "courses"
|
||||
# approved_6: "page."
|
||||
approved_4: "现在你可以招收学生在"
|
||||
approved_5: "课程"
|
||||
approved_6: "页面。"
|
||||
denied_1: "您的免费订阅试用申请已被" # {change}
|
||||
denied_2: "拒绝。"
|
||||
contact_1: "如果你有进一步的问题, 请联系"
|
||||
contact_2: "。"
|
||||
description_1: "我们可为教师提供用于评估的免费订阅。你可以在我们的"
|
||||
# description_1b: "You can find more information on our"
|
||||
description_1b: "你可以找到更多信息在我们的"
|
||||
description_2: "教师"
|
||||
description_3: "页面找到更多的信息。"
|
||||
description_4: "请填写此简单问卷,我们将会向您的电子邮件发送设置说明。"
|
||||
|
@ -736,7 +736,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
subs_only: "只限订阅"
|
||||
create_clan: "创建新的部落"
|
||||
private_preview: "预览"
|
||||
# private_clans: "Private Clans"
|
||||
private_clans: "私人部落"
|
||||
public_clans: "公开部落"
|
||||
my_clans: "我的部落"
|
||||
clan_name: "部落名字"
|
||||
|
@ -773,7 +773,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
playtime: "游戏时间"
|
||||
last_played: "最后玩了"
|
||||
leagues_explanation: "在部落里与其他成员组成联盟一起参加下面的多人竞技场。"
|
||||
# track_concepts1: "Track concepts"
|
||||
track_concepts1: "跟踪概念"
|
||||
track_concepts2a: "由每位学生学习"
|
||||
track_concepts2b: "由每位成员学习"
|
||||
track_concepts3a: "查看每位同学达到的等级"
|
||||
|
@ -785,7 +785,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
track_concepts6b: "按姓名或进度排序成员"
|
||||
track_concepts7: "需要邀请"
|
||||
track_concepts8: "来加入"
|
||||
# private_require_sub: "Private clans require a subscription to create or join."
|
||||
private_require_sub: "创建或加入私人部落时需要具体描述。"
|
||||
|
||||
courses:
|
||||
course: "课程"
|
||||
|
@ -818,7 +818,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
play_time: "游戏时间:"
|
||||
completed: "完成:"
|
||||
invite_students: "邀请学生加入此班级。"
|
||||
invite_link_header: "参与班级的縺结"
|
||||
invite_link_header: "参与班级的链接"
|
||||
invite_link_p_1: "分享给您想分享的其他人以加入课程。"
|
||||
invite_link_p_2: "或让我们代你直接发送电邮:"
|
||||
capacity_used: "课程插槽已用:"
|
||||
|
@ -865,152 +865,152 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
topics: "题目"
|
||||
hours_content: "内容时间:"
|
||||
get_free: "取得免费课程!"
|
||||
# enroll_paid: "Enroll Students in Paid Courses"
|
||||
# you_have1: "You have"
|
||||
# you_have2: "unused paid enrollments"
|
||||
# use_one: "Use 1 paid enrollment for"
|
||||
# use_multiple: "Use paid enrollments for the following students:"
|
||||
# already_enrolled: "already enrolled"
|
||||
# licenses_remaining: "licenses remaining:"
|
||||
# insufficient_enrollments: "insufficient paid enrollments"
|
||||
# enroll_students: "Enroll Students"
|
||||
# get_enrollments: "Get More Enrollments"
|
||||
# change_language: "Change Course Language"
|
||||
# keep_using: "Keep Using"
|
||||
# switch_to: "Switch To"
|
||||
# greetings: "Greetings!"
|
||||
# learn_p: "Learn Python"
|
||||
# learn_j: "Learn JavaScript"
|
||||
# language_cannot_change: "Language cannot be changed once students join a class."
|
||||
# back_classrooms: "Back to my classrooms"
|
||||
# back_courses: "Back to my courses"
|
||||
# edit_details: "Edit class details"
|
||||
# enrolled_courses: "enrolled in paid courses:"
|
||||
# purchase_enrollments: "Purchase Enrollments"
|
||||
# remove_student: "remove student"
|
||||
# assign: "Assign"
|
||||
# to_assign: "to assign paid courses."
|
||||
# teacher: "Teacher"
|
||||
# complete: "Complete"
|
||||
# none: "None"
|
||||
# save: "Save"
|
||||
# play_campaign_title: "Play the Campaign"
|
||||
# play_campaign_description: "You’re ready to take the next step! Explore hundreds of challenging levels, learn advanced programming skills, and compete in multiplayer arenas!"
|
||||
# create_account_title: "Create an Account"
|
||||
# create_account_description: "Sign up for a FREE CodeCombat account and gain access to more levels, more programming skills, and more fun!"
|
||||
# preview_campaign_title: "Preview Campaign"
|
||||
# preview_campaign_description: "Take a sneak peek at all that CodeCombat has to offer before signing up for your FREE account."
|
||||
# arena: "Arena"
|
||||
# arena_soon_title: "Arena Coming Soon"
|
||||
# arena_soon_description: "We are working on a multiplayer arena for classrooms at the end of"
|
||||
# not_enrolled1: "Not enrolled"
|
||||
# not_enrolled2: "Ask your teacher to enroll you in the next course."
|
||||
# next_course: "Next Course"
|
||||
# coming_soon1: "Coming soon"
|
||||
# coming_soon2: "We are hard at work making more courses for you!"
|
||||
# available_levels: "Available Levels"
|
||||
# welcome_to_courses: "Adventurers, welcome to Courses!"
|
||||
# ready_to_play: "Ready to play?"
|
||||
# start_new_game: "Start New Game"
|
||||
# play_now_learn_header: "Play now to learn"
|
||||
# play_now_learn_1: "basic syntax to control your character"
|
||||
# play_now_learn_2: "while loops to solve pesky puzzles"
|
||||
# play_now_learn_3: "strings & variables to customize actions"
|
||||
# play_now_learn_4: "how to defeat an ogre (important life skills!)"
|
||||
# welcome_to_page: "Welcome to your Courses page!"
|
||||
# completed_hoc: "Amazing! You've completed the Hour of Code course!"
|
||||
# ready_for_more_header: "Ready for more? Play the campaign mode!"
|
||||
# ready_for_more_1: "Use gems to unlock new items!"
|
||||
# ready_for_more_2: "Play through brand new worlds and challenges"
|
||||
# ready_for_more_3: "Learn even more programming!"
|
||||
# saved_games: "Saved Games"
|
||||
# hoc: "Hour of Code"
|
||||
# my_classes: "My Classes"
|
||||
# class_added: "Class successfully added!"
|
||||
# view_class: "view class"
|
||||
# view_levels: "view levels"
|
||||
# join_class: "Join A Class"
|
||||
# ask_teacher_for_code: "Ask your teacher if you have a CodeCombat class code! If so, enter it below:"
|
||||
# enter_c_code: "<Enter Class Code>"
|
||||
# join: "Join"
|
||||
# joining: "Joining class"
|
||||
# course_complete: "Course Complete"
|
||||
# play_arena: "Play Arena"
|
||||
# start: "Start"
|
||||
# last_level: "Last Level"
|
||||
# welcome_to_hoc: "Adventurers, welcome to our Hour of Code!"
|
||||
# logged_in_as: "Logged in as:"
|
||||
# not_you: "Not you?"
|
||||
# welcome_back: "Hi adventurer, welcome back!"
|
||||
# continue_playing: "Continue Playing"
|
||||
# more_options: "More options:"
|
||||
# option1_header: "Option 1: Invite students via email"
|
||||
# option1_body: "Students will automatically be sent an invitation to join this class, and will need to create an account with a username and password."
|
||||
# option2_header: "Option 2: Send URL to your students"
|
||||
# option2_body: "Students will be asked to enter an email address, username and password to create an account."
|
||||
# option3_header: "Option 3: Direct students to codecombat.com/courses"
|
||||
# option3_body: "Give students the following passcode to enter along with an email address, username and password when they create an account."
|
||||
# thank_you_pref: "Thank you for your purchase! You can now assign"
|
||||
# thank_you_suff: "more students to paid courses."
|
||||
# return_to_class: "Return to classroom"
|
||||
# return_to_course_man: "Return to course management."
|
||||
# students_not_enrolled: "students not enrolled"
|
||||
# total_all_classes: "Total Across All Classes"
|
||||
# how_many_enrollments: "How many additional paid enrollments do you need?"
|
||||
# each_student_access: "Each student in a class will get access to Courses 2-4 once they are enrolled in paid courses. You may assign each course to each student individually."
|
||||
# purchase_now: "Purchase Now"
|
||||
# enrollments: "enrollments"
|
||||
# remove_student1: "Remove Student"
|
||||
# are_you_sure: "Are you sure you want to remove this student from this class?"
|
||||
# remove_description1: "Student will lose access to this classroom and assigned classes. Progress and gameplay is NOT lost, and the student can be added back to the classroom at any time."
|
||||
# remove_description2: "The activated paid license will not be returned."
|
||||
# keep_student: "Keep Student"
|
||||
# removing_user: "Removing user"
|
||||
# to_join_ask: "To join a class, ask your teacher for an unlock code."
|
||||
# join_this_class: "Join Class"
|
||||
# enter_here: "<enter unlock code here>"
|
||||
# successfully_joined: "Successfully joined"
|
||||
# click_to_start: "Click here to start taking"
|
||||
# my_courses: "My Courses"
|
||||
# classroom: "Classroom"
|
||||
# use_school_email: "use your school email if you have one"
|
||||
# unique_name: "a unique name no one has chosen"
|
||||
# pick_something: "pick something you can remember"
|
||||
# class_code: "Class Code"
|
||||
# optional_ask: "optional - ask your teacher to give you one!"
|
||||
# optional_school: "optional - what school do you go to?"
|
||||
# start_playing: "Start Playing"
|
||||
# skip_this: "Skip this, I'll create an account later!"
|
||||
# welcome: "Welcome"
|
||||
# getting_started: "Getting Started with Courses"
|
||||
# download_getting_started: "Download Getting Started Guide [PDF]"
|
||||
# getting_started_1: "Create a new class by clicking the green 'Create New Class' button below."
|
||||
# getting_started_2: "Once you've created a class, click the blue 'Add Students' button."
|
||||
# getting_started_3: "You'll see student's progress below as they sign up and join your class."
|
||||
# additional_resources: "Additional Resources"
|
||||
# additional_resources_1_pref: "Download/print our"
|
||||
# additional_resources_1_mid: "Course 1 Teacher's Guide"
|
||||
# additional_resources_1_suff: "explanations and solutions to each level."
|
||||
# additional_resources_2_pref: "Complete our"
|
||||
# additional_resources_2_suff: "to get two free enrollments for the rest of our paid courses."
|
||||
# additional_resources_3_pref: "Visit our"
|
||||
# additional_resources_3_mid: "Teacher Forums"
|
||||
# additional_resources_3_suff: "to connect to fellow educators who are using CodeCombat."
|
||||
# additional_resources_4_pref: "Check out our"
|
||||
# additional_resources_4_mid: "Schools Page"
|
||||
# additional_resources_4_suff: "to learn more about CodeCombat's classroom offerings."
|
||||
# your_classes: "Your Classes"
|
||||
# no_classes: "No classes yet!"
|
||||
# create_new_class1: "create new class"
|
||||
# available_courses: "Available Courses"
|
||||
# unused_enrollments: "Unused enrollments available:"
|
||||
# students_access: "All students get access to Introduction to Computer Science for free. One enrollment per student is required to assign them to paid CodeCombat courses. A single student does not need multiple enrollments to access all paid courses."
|
||||
# active_courses: "active courses"
|
||||
# no_students: "No students yet!"
|
||||
# add_students1: "add students"
|
||||
# view_edit: "view/edit"
|
||||
# students_enrolled: "students enrolled"
|
||||
# length: "Length:"
|
||||
enroll_paid: "招收学生到已付费课程"
|
||||
you_have1: "你有"
|
||||
you_have2: "未使用的已付费课程。"
|
||||
use_one: "使用一个付费名额于"
|
||||
use_multiple: "为下列学生使用付费名额:"
|
||||
already_enrolled: "已注册"
|
||||
licenses_remaining: "证书剩余:"
|
||||
insufficient_enrollments: "付费名额不足"
|
||||
enroll_students: "招收学生"
|
||||
get_enrollments: "获取更多招收名额"
|
||||
change_language: "修改课程预言"
|
||||
keep_using: "继续使用"
|
||||
switch_to: "切换到"
|
||||
greetings: "欢迎!"
|
||||
learn_p: "学习Python"
|
||||
learn_j: "学习JavaScript"
|
||||
language_cannot_change: "一旦学生加入课程后则不可修改课程语言。"
|
||||
back_classrooms: "回到我的教室"
|
||||
back_courses: "回到我的课程"
|
||||
edit_details: "编辑课程详情"
|
||||
enrolled_courses: "报名付费课程:"
|
||||
purchase_enrollments: "购买名额"
|
||||
remove_student: "删除学生"
|
||||
assign: "分派"
|
||||
to_assign: "去分派付费课程。"
|
||||
teacher: "老师"
|
||||
complete: "完成"
|
||||
none: "无"
|
||||
save: "保存"
|
||||
play_campaign_title: "玩战役模式"
|
||||
play_campaign_description: "你已经准备开始下一步了!探索数以百计的挑战关卡、学习高级编程技巧,以及在多人竞技场中战斗!"
|
||||
create_account_title: "创建账号"
|
||||
create_account_description: "注册免费的CodeCombat账号以获取更多关卡、更多的编程技巧和更多乐趣!"
|
||||
preview_campaign_title: "试玩战役"
|
||||
preview_campaign_description: "在注册你的免费账号前,简单看一下CodeCombat将要提供的所有内容。"
|
||||
arena: "竞技场"
|
||||
arena_soon_title: "竞技场马上就来"
|
||||
arena_soon_description: "我们正在开发一个多人竞技场"
|
||||
not_enrolled1: "未报名"
|
||||
not_enrolled2: "通知你的老师把你加入下一课程。"
|
||||
next_course: "下个课程"
|
||||
coming_soon1: "马上就来"
|
||||
coming_soon2: "我们正在努力地为您准备更多课程!"
|
||||
available_levels: "可用关卡"
|
||||
welcome_to_courses: "探险者,欢迎来到课程中!"
|
||||
ready_to_play: "准备玩了吗?"
|
||||
start_new_game: "开始新游戏"
|
||||
play_now_learn_header: "开始学习"
|
||||
play_now_learn_1: "控制你角色的基本语法"
|
||||
play_now_learn_2: "解决麻烦谜题的while循环"
|
||||
play_now_learn_3: "自定义动作的字符串和变量"
|
||||
play_now_learn_4: "如何打败食人魔(重要生存技巧!)"
|
||||
welcome_to_page: "欢迎来到你的课程页面!"
|
||||
completed_hoc: "太棒了!你已经完成了编码之时课程!"
|
||||
ready_for_more_header: "准备玩更多东西了?玩竞技场模式!"
|
||||
ready_for_more_1: "使用宝石解锁更多物品!"
|
||||
ready_for_more_2: "玩品牌新世界和挑战"
|
||||
ready_for_more_3: "学习更多编程!"
|
||||
saved_games: "已保存游戏"
|
||||
hoc: "编码之时"
|
||||
my_classes: "我的课程"
|
||||
class_added: "成功添加课程!"
|
||||
view_class: "浏览课程"
|
||||
view_levels: "浏览关卡"
|
||||
join_class: "加入课程"
|
||||
ask_teacher_for_code: "问你的老师如果你有CodeCombat课程码!如果是的话,在下方输入:"
|
||||
enter_c_code: "<输入课程码>"
|
||||
join: "加入"
|
||||
joining: "加入课程中"
|
||||
course_complete: "课程完成"
|
||||
play_arena: "玩竞技场"
|
||||
start: "开始"
|
||||
last_level: "上一关卡"
|
||||
welcome_to_hoc: "探险者,欢迎来到我们的编码之时!"
|
||||
logged_in_as: "登录为:"
|
||||
not_you: "不是你?"
|
||||
welcome_back: "探险者,欢迎回来!"
|
||||
continue_playing: "继续玩"
|
||||
more_options: "更多选项:"
|
||||
option1_header: "选项1:通过电子邮件邀请学生"
|
||||
option1_body: "学生会被自动发送一个加入该课程的邀请,他们需要提供用户名和密码以创建账号。"
|
||||
option2_header: "选项2:发送链接给你的学生"
|
||||
option2_body: "学生会被要求输入一个电子邮箱地址、用户名和密码以创建账号。"
|
||||
option3_header: "选项3:通知学生到codecombat.com/courses"
|
||||
option3_body: "给学生下列密码以便他们在使用电子邮件地址、用户名和密码创建账号时输入。"
|
||||
thank_you_pref: "感谢你的购买!现在你可以分派"
|
||||
thank_you_suff: "更多学生到付费课程中。"
|
||||
return_to_class: "回到教室"
|
||||
return_to_course_man: "回到课程管理。"
|
||||
students_not_enrolled: "未注册的学生"
|
||||
total_all_classes: "所有课程总数"
|
||||
how_many_enrollments: "你需要多少额外的付费名额?"
|
||||
each_student_access: "课程中的学生可以访问课程2-4,一旦他们注册了付费课程。你可以为每个学生单独分配任何课程。"
|
||||
purchase_now: "现在购买"
|
||||
enrollments: "名额"
|
||||
remove_student1: "删除学生"
|
||||
are_you_sure: "你确定要从该课程中删除该学生吗?"
|
||||
remove_description1: "学生将失去访问该课堂和被分派课程的权利。游戏进度不会丢失,该学生可以在任何时间被添加回该教室。"
|
||||
remove_description2: "被激活的付费证书将无法返还。"
|
||||
keep_student: "保留学生"
|
||||
removing_user: "删除用户"
|
||||
to_join_ask: "为了加入课程,问你的老师得到一个解锁码。"
|
||||
join_this_class: "加入课程"
|
||||
enter_here: "<在这里输入解锁码>"
|
||||
successfully_joined: "成功加入"
|
||||
click_to_start: "点击这里开始"
|
||||
my_courses: "我的课程"
|
||||
classroom: "教室"
|
||||
use_school_email: "用你学校的电子邮箱如果你有"
|
||||
unique_name: "没人使用的唯一名称"
|
||||
pick_something: "选用你能记住的某些东西"
|
||||
class_code: "课程码"
|
||||
optional_ask: "可选 - 让你的老师给你一个!"
|
||||
optional_school: "可选 - 你想去什么学校?"
|
||||
start_playing: "开始玩"
|
||||
skip_this: "跳过,以后我会创建一个账号!"
|
||||
welcome: "欢迎"
|
||||
getting_started: "开始课程"
|
||||
download_getting_started: "下载新手教程[PDF]"
|
||||
getting_started_1: "点击下面绿色的'创建新课程'按钮来创建新课程。"
|
||||
getting_started_2: "当你已经创建好了课程,点击蓝色的'添加学生'按钮。"
|
||||
getting_started_3: "你将会在下面看到学生的进度,当他们注册和加入你的课程。"
|
||||
additional_resources: "额外资源"
|
||||
additional_resources_1_pref: "下载/打印我们的"
|
||||
additional_resources_1_mid: "课程1老师的讲义"
|
||||
additional_resources_1_suff: "到各个关卡的解释和攻略。"
|
||||
additional_resources_2_pref: "完成我们的"
|
||||
additional_resources_2_suff: "以获得两个额外的我们后续付费课程的免费名额。"
|
||||
additional_resources_3_pref: "访问我们的"
|
||||
additional_resources_3_mid: "教室论坛"
|
||||
additional_resources_3_suff: "以连接那些使用CodeCombat的伙伴教育者。"
|
||||
additional_resources_4_pref: "检出我们的"
|
||||
additional_resources_4_mid: "学校页面"
|
||||
additional_resources_4_suff: "以学得更多关于CodeCombat的教室供应。"
|
||||
your_classes: "你的课程"
|
||||
no_classes: "还没有任何课程!"
|
||||
create_new_class1: "创建新课程"
|
||||
available_courses: "可用课程"
|
||||
unused_enrollments: "未使用的可用名额:"
|
||||
students_access: "所有的学生都可以免费访问Introduction to Computer Science。每个学生需要一个名额以被分派到付费的CodeCombat课程。每个学生并不需要多个名额来访问所有的付费课程。"
|
||||
active_courses: "已激活课程"
|
||||
no_students: "还没有学生!"
|
||||
add_students1: "添加学生"
|
||||
view_edit: "查看/编辑"
|
||||
students_enrolled: "学生已注册"
|
||||
length: "长度:"
|
||||
|
||||
classes:
|
||||
archmage_title: "大法师"
|
||||
|
@ -1322,11 +1322,11 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
|
||||
account_prepaid:
|
||||
purchase_code: "购买订阅码"
|
||||
# purchase_code1: "Subscription Codes can be redeemed to add premium subscription time to one or more CodeCombat accounts."
|
||||
# purchase_code2: "Each CodeCombat account can only redeem a particular Subscription Code once."
|
||||
# purchase_code3: "Subscription Code months will be added to the end of any existing subscription on the account."
|
||||
purchase_code1: "订阅码可以为一个或多个CodeCombat账号兑换额外的订阅时间。"
|
||||
purchase_code2: "每个CodeCombat账号每次只能兑换一个订阅码。"
|
||||
purchase_code3: "订阅码时间会在账号现有的订阅时间基础上延长。"
|
||||
users: "玩家"
|
||||
# months: "Months"
|
||||
months: "月份"
|
||||
purchase_total: "总共"
|
||||
purchase_button: "提交购买"
|
||||
your_codes: "你的订阅码:" # {change}
|
||||
|
@ -1354,7 +1354,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
bad_input: "错误输入。"
|
||||
server_error: "服务器错误。"
|
||||
unknown: "未知错误。"
|
||||
# error: "ERROR"
|
||||
error: "错误"
|
||||
|
||||
resources:
|
||||
sessions: "session"
|
||||
|
@ -1406,16 +1406,16 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
campaigns: "任务"
|
||||
poll: "调查"
|
||||
user_polls_record: "投票结果"
|
||||
# course: "Course"
|
||||
# courses: "Courses"
|
||||
# course_instance: "Course Instance"
|
||||
# course_instances: "Course Instances"
|
||||
# classroom: "Classroom"
|
||||
# classrooms: "Classrooms"
|
||||
# clan: "Clan"
|
||||
# clans: "Clans"
|
||||
# members: "Members"
|
||||
# users: "Users"
|
||||
course: "课程"
|
||||
courses: "课程"
|
||||
course_instance: "课程示例"
|
||||
course_instances: "课程示例"
|
||||
classroom: "教室"
|
||||
classrooms: "教室"
|
||||
clan: "部落"
|
||||
clans: "部落"
|
||||
members: "成员"
|
||||
users: "用户"
|
||||
|
||||
concepts:
|
||||
advanced_strings: "高级字符串"
|
||||
|
@ -1481,7 +1481,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
email_settings_url: "您的电子邮件设置"
|
||||
email_description_suffix: "或者我们发送的邮件中的链接,您可以随时更改您的偏好设置或者随时取消订阅。"
|
||||
cost_title: "花费"
|
||||
# cost_description: "CodeCombat is free to play for all of its core levels, with a ${{price}} USD/mo subscription for access to extra level branches and {{gems}} bonus gems per month. You can cancel with a click, and we offer a 100% money-back guarantee."
|
||||
cost_description: "CodeCombat的所有核心关卡都是免费的,需要${{price}}美元/月的订阅费来访问额外关卡分支并获得{{gems}}宝石奖励每月。你可以通过勾选来取消,我们提供100%退款保证。"
|
||||
copyrights_title: "版权与许可"
|
||||
contributor_title: "贡献者许可协议"
|
||||
contributor_description_prefix: "所有对本网站或是GitHub代码库的贡献都依照我们的"
|
||||
|
@ -1516,8 +1516,8 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
nutshell_title: "简而言之"
|
||||
nutshell_description: "我们在关卡编辑器里公开的任何资源,您都可以在制作关卡时随意使用,但我们保留限制在CodeCombat.com 之上创建的关卡本身传播的权利,因为我们以后可能决定为它们收费。"
|
||||
canonical: "这篇说明的英文版本是权威版本。如果各个翻译版本之间有任何冲突,请以英文版为准。"
|
||||
# third_party_title: "Third Party Services"
|
||||
# third_party_description: "CodeCombat uses the following third party services (among others):"
|
||||
third_party_title: "第三方服务"
|
||||
third_party_description: "CodeCombat使用了下列第三方服务(除了别的以外):"
|
||||
|
||||
ladder_prizes:
|
||||
title: "竞标赛奖项" # This section was for an old tournament and doesn't need new translations now.
|
||||
|
|
|
@ -27,6 +27,12 @@ block content
|
|||
.kpi-chart.line-chart-container
|
||||
|
||||
h3 Active Classes 90 days
|
||||
.small Active class: 12+ students in a classroom, with 6+ who played in last 30 days.
|
||||
.small Paid student: user.coursePrepaidID set and prepaid.properties.trialRequestID NOT set
|
||||
.small Trial student: user.coursePrepaidID set and prepaid.properties.trialRequestID set
|
||||
.small Paid class: at least one paid student in the classroom
|
||||
.small Trial class: not paid, at least one trial student in classroom
|
||||
.small Free class: not paid, not trial
|
||||
.active-classes-chart.line-chart-container
|
||||
|
||||
h3 Recurring Revenue 90 days
|
||||
|
@ -56,6 +62,22 @@ block content
|
|||
else
|
||||
div Loading ...
|
||||
|
||||
h3 School Counts
|
||||
.small Only including schools with #{view.minSchoolCount}+ counts
|
||||
if view.schoolCounts
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
th
|
||||
th School Name
|
||||
th User Count
|
||||
each val, i in view.schoolCounts
|
||||
tr
|
||||
td= i + 1
|
||||
td= val.schoolName
|
||||
td= val.count
|
||||
else
|
||||
div Loading ...
|
||||
|
||||
h1 Active Classes
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
|
|
|
@ -183,9 +183,16 @@ nav.navbar.navbar-default
|
|||
.request-demo-row.text-center
|
||||
h3 Curious? Request a demo and we'll show you the ropes
|
||||
h4 Or create a class and see it for yourself!
|
||||
p
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/freetrial") Request a Demo
|
||||
a.btn.btn-primary-alt.btn-lg(href="/courses/teachers") Create a Class
|
||||
div
|
||||
if me.isAnonymous()
|
||||
span.spr Already have an account?
|
||||
a.login-button Login
|
||||
else
|
||||
span.spr You are currently logged in as #{me.broadName()}.
|
||||
a#logout-button Logout?
|
||||
|
||||
h3.text-center Computer science courses for all ages
|
||||
h4.text-center
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
button.btn.btn-lg.btn-illustrated.cast-button(title=castVerbose)
|
||||
button.btn.btn-lg.btn-illustrated.cast-button(title=view.castVerbose())
|
||||
span(data-i18n="play_level.tome_run_button_ran") Ran
|
||||
|
||||
if !observing
|
||||
if mirror
|
||||
if !view.observing
|
||||
if view.mirror
|
||||
.ladder-submission-view
|
||||
else
|
||||
button.btn.btn-lg.btn-illustrated.submit-button(title=castRealTimeVerbose)
|
||||
button.btn.btn-lg.btn-illustrated.submit-button(title=view.castRealTimeVerbose())
|
||||
span(data-i18n="play_level.tome_submit_button") Submit
|
||||
span.spl.secret.submit-again-time
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
button.close(type="button") ×
|
||||
h3.problem-alert-title(data-i18n="play_level.problem_alert_title") Fix Your Code
|
||||
if hint
|
||||
span.problem-title!= hint
|
||||
if view.hint
|
||||
span.problem-title!= view.hint
|
||||
br
|
||||
span.problem-subtitle!= message
|
||||
span.problem-subtitle!= view.message
|
||||
else
|
||||
span.problem-title!= message
|
||||
span.problem-title!= view.message
|
||||
|
|
|
@ -196,7 +196,7 @@ block content
|
|||
.well
|
||||
p.text-center.blurb-subtitle Resources for Teachers
|
||||
p
|
||||
a(href='http://codecombat.com/docs/CodeCombatHourofCodeGettingStartedGuide.pdf')
|
||||
a(href='http://codecombat.com/docs/CodeCombatCoursesGettingStartedGuide.pdf')
|
||||
img(src='/images/Adobe_PDF_file_icon_32x32.png')
|
||||
span Getting Started Guide
|
||||
p
|
||||
|
|
|
@ -43,6 +43,8 @@ module.exports = class RequestQuoteView extends RootView
|
|||
if @trialRequests.size()
|
||||
@trialRequest = @trialRequests.first()
|
||||
me.setRole 'teacher'
|
||||
if @trialRequest and @trialRequest.get('status') isnt 'submitted' and @trialRequest.get('status') isnt 'approved'
|
||||
window.tracker?.trackEvent 'View Trial Request', category: 'Teachers', label: 'View Trial Request', ['Mixpanel']
|
||||
super()
|
||||
|
||||
onSubmitForm: (e) ->
|
||||
|
@ -80,6 +82,7 @@ module.exports = class RequestQuoteView extends RootView
|
|||
|
||||
onTrialRequestSubmit: ->
|
||||
@$('form, #form-submit-success').toggleClass('hide')
|
||||
window.tracker?.trackEvent 'Submit Trial Request', category: 'Teachers', label: 'Trial Request', ['Mixpanel']
|
||||
|
||||
onClickLoginButton: ->
|
||||
modal = new AuthModal({
|
||||
|
|
|
@ -12,6 +12,7 @@ module.exports = class AnalyticsView extends RootView
|
|||
template: template
|
||||
furthestCourseDayRange: 30
|
||||
lineColors: ['red', 'blue', 'green', 'purple', 'goldenrod', 'brown', 'darkcyan']
|
||||
minSchoolCount: 20
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
|
@ -119,6 +120,18 @@ module.exports = class AnalyticsView extends RootView
|
|||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource('school_counts', {
|
||||
url: '/db/user/-/school_counts'
|
||||
method: 'POST'
|
||||
data: {minCount: @minSchoolCount}
|
||||
success: (@schoolCounts) =>
|
||||
@schoolCounts?.sort (a, b) ->
|
||||
return -1 if a.count > b.count
|
||||
return 0 if a.count is b.count
|
||||
1
|
||||
@render?()
|
||||
}, 0).load()
|
||||
|
||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||
@courses.comparator = "_id"
|
||||
@listenToOnce @courses, 'sync', @onCoursesSync
|
||||
|
|
|
@ -32,24 +32,13 @@ module.exports = class CastButtonView extends CocoView
|
|||
@updateReplayabilityInterval = setInterval @updateReplayability, 1000
|
||||
@observing = options.session.get('creator') isnt me.id
|
||||
@loadMirrorSession() if @options.level.get('slug') in ['ace-of-coders', 'elemental-wars']
|
||||
@mirror = @mirrorSession?
|
||||
@autoSubmitsToLadder = @options.level.get('slug') in ['wakka-maul']
|
||||
|
||||
destroy: ->
|
||||
clearInterval @updateReplayabilityInterval
|
||||
super()
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
context = super context
|
||||
shift = $.i18n.t 'keyboard_shortcuts.shift'
|
||||
enter = $.i18n.t 'keyboard_shortcuts.enter'
|
||||
castShortcutVerbose = "#{shift}+#{enter}"
|
||||
castRealTimeShortcutVerbose = (if @isMac() then 'Cmd' else 'Ctrl') + '+' + castShortcutVerbose
|
||||
context.castVerbose = castShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_code')
|
||||
context.castRealTimeVerbose = castRealTimeShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_real_time')
|
||||
context.observing = @observing
|
||||
context.mirror = @mirrorSession?
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@castButton = $('.cast-button', @$el)
|
||||
|
@ -66,6 +55,18 @@ module.exports = class CastButtonView extends CocoView
|
|||
attachTo: (spellView) ->
|
||||
@$el.detach().prependTo(spellView.toolbarView.$el).show()
|
||||
|
||||
castShortcutVerbose: ->
|
||||
shift = $.i18n.t 'keyboard_shortcuts.shift'
|
||||
enter = $.i18n.t 'keyboard_shortcuts.enter'
|
||||
"#{shift}+#{enter}"
|
||||
|
||||
castVerbose: ->
|
||||
@castShortcutVerbose() + ': ' + $.i18n.t('keyboard_shortcuts.run_code')
|
||||
|
||||
castRealTimeVerbose: ->
|
||||
castRealTimeShortcutVerbose = (if @isMac() then 'Cmd' else 'Ctrl') + '+' + @castShortcutVerbose()
|
||||
castRealTimeShortcutVerbose + ': ' + $.i18n.t('keyboard_shortcuts.run_real_time')
|
||||
|
||||
onCastButtonClick: (e) ->
|
||||
Backbone.Mediator.publish 'tome:manual-cast', {}
|
||||
|
||||
|
|
|
@ -36,8 +36,14 @@ module.exports = class ProblemAlertView extends CocoView
|
|||
$(window).off 'resize', @onWindowResize
|
||||
super()
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
context = super context
|
||||
afterRender: ->
|
||||
super()
|
||||
if @problem?
|
||||
@$el.addClass('alert').addClass("alert-#{@problem.aetherProblem.level}").hide().fadeIn('slow')
|
||||
@$el.addClass('no-hint') unless @problem.aetherProblem.hint
|
||||
@playSound 'error_appear'
|
||||
|
||||
setProblemMessage: ->
|
||||
if @problem?
|
||||
format = (s) -> marked(s.replace(/</g, '<').replace(/>/g, '>')) if s?
|
||||
message = @problem.aetherProblem.message
|
||||
|
@ -50,16 +56,8 @@ module.exports = class ProblemAlertView extends CocoView
|
|||
message = message.replace /^(Line \d+)/, "$1, time #{age.toFixed(1)}"
|
||||
else
|
||||
message = "Time #{age.toFixed(1)}: #{message}"
|
||||
context.message = format message
|
||||
context.hint = format @problem.aetherProblem.hint
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
if @problem?
|
||||
@$el.addClass('alert').addClass("alert-#{@problem.aetherProblem.level}").hide().fadeIn('slow')
|
||||
@$el.addClass('no-hint') unless @problem.aetherProblem.hint
|
||||
@playSound 'error_appear'
|
||||
@message = format message
|
||||
@hint = format @problem.aetherProblem.hint
|
||||
|
||||
onShowProblemAlert: (data) ->
|
||||
return unless $('#code-area').is(":visible")
|
||||
|
@ -72,6 +70,7 @@ module.exports = class ProblemAlertView extends CocoView
|
|||
@lineOffsetPx = data.lineOffsetPx or 0
|
||||
@$el.show()
|
||||
@onWindowResize()
|
||||
@setProblemMessage()
|
||||
@render()
|
||||
@onJiggleProblemAlert()
|
||||
application.tracker?.trackEvent 'Show problem alert', {levelID: @level.get('slug'), ls: @session?.get('_id')}
|
||||
|
|
|
@ -1,31 +1,42 @@
|
|||
/* global db */
|
||||
/* global Mongo */
|
||||
/* global ISODate */
|
||||
// Insert per-day active class counts into analytics.perdays collection
|
||||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||
|
||||
try {
|
||||
logDB = new Mongo("localhost").getDB("analytics")
|
||||
var logDB = new Mongo("localhost").getDB("analytics")
|
||||
var scriptStartTime = new Date();
|
||||
var analyticsStringCache = {};
|
||||
|
||||
var minClassSize = 12;
|
||||
var minActiveCount = 6;
|
||||
|
||||
var eventNamePaid = 'Active classes paid';
|
||||
var eventNameTrial = 'Active classes trial';
|
||||
var eventNameFree = 'Active classes free';
|
||||
|
||||
var numDays = 40;
|
||||
var daysInMonth = 30;
|
||||
|
||||
var startDay = new Date();
|
||||
today = startDay.toISOString().substr(0, 10);
|
||||
var today = startDay.toISOString().substr(0, 10);
|
||||
startDay.setUTCDate(startDay.getUTCDate() - numDays);
|
||||
startDay = startDay.toISOString().substr(0, 10);
|
||||
|
||||
log("Today is " + today);
|
||||
log("Start day is " + startDay);
|
||||
|
||||
log("Getting active class counts...");
|
||||
log("Getting active class counts..");
|
||||
var activeClassCounts = getActiveClassCounts(startDay);
|
||||
// printjson(activeClassCounts);
|
||||
log("Inserting active class counts...");
|
||||
log("Inserting active class counts..");
|
||||
for (var event in activeClassCounts) {
|
||||
for (var day in activeClassCounts[event]) {
|
||||
if (today === day) continue; // Never save data for today because it's incomplete
|
||||
// print(event, day, activeClassCounts[event][day]);
|
||||
insertEventCount(event, day, activeClassCounts[event][day]);
|
||||
}
|
||||
}
|
||||
|
@ -38,130 +49,73 @@ catch(err) {
|
|||
}
|
||||
|
||||
function getActiveClassCounts(startDay) {
|
||||
// Tally active classes per day
|
||||
// Tally active classes per day, for paid, trial, and free
|
||||
// TODO: does not handle class membership changes
|
||||
|
||||
if (!startDay) return {};
|
||||
|
||||
var minGroupSize = 12;
|
||||
var classes = {
|
||||
'Active classes private clan': [],
|
||||
'Active classes managed subscription': [],
|
||||
'Active classes bulk subscription': [],
|
||||
'Active classes prepaid': [],
|
||||
'Active classes course free': [],
|
||||
'Active classes course paid': []
|
||||
};
|
||||
var userPlayedMap = {};
|
||||
|
||||
// Private clans
|
||||
// TODO: does not handle clan membership changes over time
|
||||
var cursor = db.clans.find({$and: [{type: 'private'}, {$where: 'this.members.length >= ' + minGroupSize}]});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var members = doc.members.map(function(a) {
|
||||
userPlayedMap[a.valueOf()] = [];
|
||||
return a.valueOf();
|
||||
});
|
||||
classes['Active classes private clan'].push({
|
||||
owner: doc.ownerID.valueOf(),
|
||||
members: members,
|
||||
activeDayMap: {}
|
||||
});
|
||||
}
|
||||
|
||||
// Managed subscriptions
|
||||
// TODO: does not handle former recipients playing after sponsorship ends
|
||||
var bulkSubGroups = {};
|
||||
cursor = db.payments.find({$and: [{service: 'stripe'}, {$where: '!this.purchaser.equals(this.recipient)'}]});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var purchaser = doc.purchaser.valueOf();
|
||||
if (!bulkSubGroups[purchaser]) bulkSubGroups[purchaser] = {};
|
||||
bulkSubGroups[purchaser][doc.recipient.valueOf()] = true;
|
||||
}
|
||||
for (var purchaser in bulkSubGroups) {
|
||||
if (Object.keys(bulkSubGroups[purchaser]).length >= minGroupSize) {
|
||||
for (var member in bulkSubGroups[purchaser]) {
|
||||
userPlayedMap[member] = [];
|
||||
}
|
||||
classes['Active classes managed subscription'].push({
|
||||
owner: purchaser,
|
||||
members: Object.keys(bulkSubGroups[purchaser]),
|
||||
activeDayMap: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Bulk subscriptions
|
||||
bulkSubGroups = {};
|
||||
cursor = db.payments.find({$and: [{service: 'external'}, {$where: '!this.purchaser.equals(this.recipient)'}]});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var purchaser = doc.purchaser.valueOf();
|
||||
if (!bulkSubGroups[purchaser]) bulkSubGroups[purchaser] = {};
|
||||
bulkSubGroups[purchaser][doc.recipient.valueOf()] = true;
|
||||
}
|
||||
for (var purchaser in bulkSubGroups) {
|
||||
if (Object.keys(bulkSubGroups[purchaser]).length >= minGroupSize) {
|
||||
for (var member in bulkSubGroups[purchaser]) {
|
||||
userPlayedMap[member] = [];
|
||||
}
|
||||
classes['Active classes bulk subscription'].push({
|
||||
owner: purchaser,
|
||||
members: Object.keys(bulkSubGroups[purchaser]),
|
||||
activeDayMap: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Prepaids terminal_subscription
|
||||
bulkSubGroups = {};
|
||||
cursor = db.prepaids.find(
|
||||
{$and: [{type: 'terminal_subscription'}, {$where: 'this.redeemers && this.redeemers.length >= ' + minGroupSize}]},
|
||||
{creator: 1, type: 1, redeemers: 1}
|
||||
);
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var owner = doc.creator.valueOf();
|
||||
var members = [];
|
||||
for (var i = 0 ; i < doc.redeemers.length; i++) {
|
||||
userPlayedMap[doc.redeemers[i].userID.valueOf()] = [];
|
||||
members.push(doc.redeemers[i].userID.valueOf());
|
||||
}
|
||||
classes['Active classes prepaid'].push({
|
||||
owner: owner,
|
||||
members: members,
|
||||
activeDayMap: {}
|
||||
});
|
||||
}
|
||||
var cursor, doc;
|
||||
|
||||
// Classrooms
|
||||
var classroomCourseInstancesMap = {};
|
||||
cursor = db.course.instances.find(
|
||||
{$where: 'this.members && this.members.length >= ' + minGroupSize},
|
||||
{classroomID: 1, courseID: 1, members: 1, ownerID: 1}
|
||||
);
|
||||
// paid: at least one paid member
|
||||
// trial: not paid, at least one trial member
|
||||
// free: not paid, not free trial
|
||||
// user.coursePrepaidID set means access to paid courses
|
||||
// prepaid.properties.trialRequestID means access was via trial
|
||||
|
||||
// Find classroom users
|
||||
log("Finding classrooms..");
|
||||
var userClassroomsMap = {};
|
||||
var classroomUsersMap = {};
|
||||
var classroomUserIDs = [];
|
||||
var classroomUserObjectIds = [];
|
||||
cursor = db.classrooms.find({}, {members: 1});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
var owner = doc.ownerID.valueOf();
|
||||
var classroom = doc.classroomID ? doc.classroomID.valueOf() : doc._id.valueOf();
|
||||
var members = [];
|
||||
doc = cursor.next();
|
||||
if (doc.members) {
|
||||
var classroomID = doc._id.valueOf();
|
||||
for (var i = 0; i < doc.members.length; i++) {
|
||||
userPlayedMap[doc.members[i].valueOf()] = [];
|
||||
members.push(doc.members[i].valueOf());
|
||||
if (doc.members.length < minClassSize) continue;
|
||||
var userID = doc.members[i].valueOf();
|
||||
if (!userClassroomsMap[userID]) userClassroomsMap[userID] = [];
|
||||
userClassroomsMap[userID].push(classroomID);
|
||||
if (!classroomUsersMap[classroomID]) classroomUsersMap[classroomID] = [];
|
||||
classroomUsersMap[classroomID].push(userID)
|
||||
classroomUserIDs.push(doc.members[i].valueOf());
|
||||
classroomUserObjectIds.push(doc.members[i]);
|
||||
}
|
||||
}
|
||||
if (!classroomCourseInstancesMap[classroom]) classroomCourseInstancesMap[classroom] = [];
|
||||
classroomCourseInstancesMap[classroom].push({
|
||||
course: doc.courseID.valueOf(),
|
||||
owner: owner,
|
||||
members: members,
|
||||
});
|
||||
}
|
||||
|
||||
// printjson(classroomCourseInstancesMap);
|
||||
log("Find user types..");
|
||||
var userEventMap = {};
|
||||
var prepaidUsersMap = {};
|
||||
var prepaidIDs = [];
|
||||
cursor = db.users.find({_id: {$in: classroomUserObjectIds}}, {coursePrepaidID: 1});
|
||||
while (cursor.hasNext()) {
|
||||
doc = cursor.next();
|
||||
if (doc.coursePrepaidID) {
|
||||
userEventMap[doc._id.valueOf()] = eventNamePaid;
|
||||
if (!prepaidUsersMap[doc.coursePrepaidID.valueOf()]) prepaidUsersMap[doc.coursePrepaidID.valueOf()] = [];
|
||||
prepaidUsersMap[doc.coursePrepaidID.valueOf()].push(doc._id.valueOf());
|
||||
prepaidIDs.push(doc.coursePrepaidID);
|
||||
}
|
||||
else {
|
||||
userEventMap[doc._id.valueOf()] = eventNameFree;
|
||||
}
|
||||
}
|
||||
cursor = db.prepaids.find({_id: {$in: prepaidIDs}}, {properties: 1});
|
||||
while (cursor.hasNext()) {
|
||||
doc = cursor.next();
|
||||
if (doc.properties && doc.properties.trialRequestID) {
|
||||
for (var i = 0; i < prepaidUsersMap[doc._id.valueOf()].length; i++) {
|
||||
userEventMap[prepaidUsersMap[doc._id.valueOf()][i]] = eventNameTrial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all the started level events for our class members, for startDay - daysInMonth
|
||||
log("Find Started Level log events for all classroom members for last " + (numDays + daysInMonth) + " days..");
|
||||
var userPlayedMap = {};
|
||||
var startDate = ISODate(startDay + "T00:00:00.000Z");
|
||||
startDate.setUTCDate(startDate.getUTCDate() - daysInMonth);
|
||||
var endDate = ISODate(startDay + "T00:00:00.000Z");
|
||||
|
@ -169,162 +123,72 @@ function getActiveClassCounts(startDay) {
|
|||
var startObj = objectIdWithTimestamp(startDate);
|
||||
var queryParams = {$and: [
|
||||
{_id: {$gte: startObj}},
|
||||
{user: {$in: Object.keys(userPlayedMap)}},
|
||||
{user: {$in: classroomUserIDs}},
|
||||
{event: 'Started Level'}
|
||||
]};
|
||||
cursor = logDB['log'].find(queryParams, {user: 1});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
doc = cursor.next();
|
||||
if (!userPlayedMap[doc.user]) userPlayedMap[doc.user] = [];
|
||||
userPlayedMap[doc.user].push(doc._id.getTimestamp());
|
||||
}
|
||||
|
||||
// printjson(userPlayedMap);
|
||||
// print(startDate, endDate, todayDate);
|
||||
|
||||
// Now we have a set of classes, and when users played
|
||||
// For a given day, walk classes and find out how many members were active during the previous daysInMonth
|
||||
while (endDate < todayDate) {
|
||||
var endDay = endDate.toISOString().substring(0, 10);
|
||||
|
||||
// For each class
|
||||
for (var event in classes) {
|
||||
for (var i = 0; i < classes[event].length; i++) {
|
||||
|
||||
// For each member of current class
|
||||
var activeMemberCount = 0;
|
||||
for (var j = 0; j < classes[event][i].members.length; j++) {
|
||||
var member = classes[event][i].members[j];
|
||||
|
||||
// Was member active during current timeframe?
|
||||
if (userPlayedMap[member]) {
|
||||
for (var k = 0; k < userPlayedMap[member].length; k++) {
|
||||
if (userPlayedMap[member][k] > startDate && userPlayedMap[member][k] <= endDate) {
|
||||
activeMemberCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Classes active for a given day if has minGroupSize members, and at least 1/2 played in last daysInMonth days
|
||||
if (activeMemberCount >= Math.round(classes[event][i].members.length / 2)) {
|
||||
classes[event][i].activeDayMap[endDay] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
startDate.setUTCDate(startDate.getUTCDate() + 1);
|
||||
endDate.setUTCDate(endDate.getUTCDate() + 1);
|
||||
}
|
||||
|
||||
// Classrooms are processed differently because they could be free or paid active classes
|
||||
var courseNameMap = {};
|
||||
cursor = db.courses.find({}, {name: 1});
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
courseNameMap[doc._id.valueOf()] = doc.name;
|
||||
}
|
||||
|
||||
// For each classroom, check free and paid members separately
|
||||
for (var classroom in classroomCourseInstancesMap) {
|
||||
var freeMembers = {};
|
||||
var paidMembers = {};
|
||||
var owner = null;
|
||||
for (var i = 0; i < classroomCourseInstancesMap[classroom].length; i++) {
|
||||
var courseInstance = classroomCourseInstancesMap[classroom][i];
|
||||
if (!owner) owner = courseInstance.owner;
|
||||
for (var j = 0; j < courseInstance.members.length; j++) {
|
||||
if (courseNameMap[courseInstance.course] === 'Introduction to Computer Science') {
|
||||
freeMembers[courseInstance.members[j]] = true;
|
||||
}
|
||||
else {
|
||||
paidMembers[courseInstance.members[j]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var freeClass = {
|
||||
owner: owner,
|
||||
members: Object.keys(freeMembers),
|
||||
activeDayMap: {}
|
||||
};
|
||||
var paidClass = {
|
||||
owner: owner,
|
||||
members: Object.keys(paidMembers),
|
||||
activeDayMap: {}
|
||||
};
|
||||
|
||||
// print('Processing classroom', classroom, freeClass.members.length, paidClass.members.length);
|
||||
log("Calculate number of active members per classroom per day per event type..");
|
||||
var classDayTypeMap = {};
|
||||
for (var classroom in classroomUsersMap) {
|
||||
if (classroomUsersMap[classroom].length < minClassSize) continue;
|
||||
|
||||
// For each each day in our target date range
|
||||
classDayTypeMap[classroom] = {};
|
||||
startDate = ISODate(startDay + "T00:00:00.000Z");
|
||||
startDate.setUTCDate(startDate.getUTCDate() - daysInMonth);
|
||||
endDate = ISODate(startDay + "T00:00:00.000Z");
|
||||
while (endDate < todayDate) {
|
||||
var endDay = endDate.toISOString().substring(0, 10);
|
||||
classDayTypeMap[classroom][endDay] = {};
|
||||
classDayTypeMap[classroom][endDay][eventNamePaid] = 0;
|
||||
classDayTypeMap[classroom][endDay][eventNameTrial] = 0;
|
||||
classDayTypeMap[classroom][endDay][eventNameFree] = 0;
|
||||
|
||||
// For each paid member of current class
|
||||
var paidActiveMemberCount = 0;
|
||||
for (var j = 0; j < paidClass.members.length; j++) {
|
||||
var member = paidClass.members[j];
|
||||
// Count active users of each type for current day
|
||||
for (var j = 0; j < classroomUsersMap[classroom].length; j++) {
|
||||
var member = classroomUsersMap[classroom][j];
|
||||
|
||||
// Was member active during current timeframe?
|
||||
if (userPlayedMap[member]) {
|
||||
for (var k = 0; k < userPlayedMap[member].length; k++) {
|
||||
if (userPlayedMap[member][k] > startDate && userPlayedMap[member][k] <= endDate) {
|
||||
paidActiveMemberCount++;
|
||||
classDayTypeMap[classroom][endDay][userEventMap[member]]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Classes active for a given day if has minGroupSize members, and at least 1/2 played in last daysInMonth days
|
||||
if (paidClass.members.length > minGroupSize && paidActiveMemberCount >= Math.round(paidClass.members.length / 2)) {
|
||||
// print('paid classroom', classroom, endDay);
|
||||
paidClass.activeDayMap[endDay] = true;
|
||||
}
|
||||
else {
|
||||
// For each free member of current class
|
||||
var freeActiveMemberCount = 0;
|
||||
for (var j = 0; j < freeClass.members.length; j++) {
|
||||
var member = freeClass.members[j];
|
||||
|
||||
// Was member active during current timeframe?
|
||||
if (userPlayedMap[member]) {
|
||||
for (var k = 0; k < userPlayedMap[member].length; k++) {
|
||||
if (userPlayedMap[member][k] > startDate && userPlayedMap[member][k] <= endDate) {
|
||||
freeActiveMemberCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (freeClass.members.length > minGroupSize && freeActiveMemberCount >= Math.round(freeClass.members.length / 2)) {
|
||||
// print('free classroom', classroom, endDay);
|
||||
freeClass.activeDayMap[endDay] = true;
|
||||
}
|
||||
|
||||
}
|
||||
startDate.setUTCDate(startDate.getUTCDate() + 1);
|
||||
endDate.setUTCDate(endDate.getUTCDate() + 1);
|
||||
}
|
||||
|
||||
// printjson(freeClass);
|
||||
// printjson(paidClass);
|
||||
|
||||
classes['Active classes course free'].push(freeClass);
|
||||
classes['Active classes course paid'].push(paidClass);
|
||||
}
|
||||
|
||||
// printjson(classes['Active classes course paid']);
|
||||
|
||||
log("Aggregate class counts by day and type..");
|
||||
var activeClassCounts = {};
|
||||
for (var event in classes) {
|
||||
if (!activeClassCounts[event]) activeClassCounts[event] = {};
|
||||
for (var i = 0; i < classes[event].length; i++) {
|
||||
for (var endDay in classes[event][i].activeDayMap) {
|
||||
if (!activeClassCounts[event][endDay]) activeClassCounts[event][endDay] = 0;
|
||||
activeClassCounts[event][endDay]++;
|
||||
for (var classroom in classDayTypeMap) {
|
||||
for (var endDay in classDayTypeMap[classroom]) {
|
||||
var activeStudents = 0;
|
||||
var classEvent = eventNameFree;
|
||||
for (var event in classDayTypeMap[classroom][endDay]) {
|
||||
if (classDayTypeMap[classroom][endDay][event] > 1) {
|
||||
activeStudents += classDayTypeMap[classroom][endDay][event];
|
||||
if (event === eventNamePaid) classEvent = event;
|
||||
if (classEvent !== eventNamePaid && event === eventNameTrial) classEvent = event;
|
||||
}
|
||||
}
|
||||
if (activeStudents >= minActiveCount) {
|
||||
if (!activeClassCounts[classEvent]) activeClassCounts[classEvent] = {};
|
||||
if (!activeClassCounts[classEvent][endDay]) activeClassCounts[classEvent][endDay] = 0;
|
||||
activeClassCounts[classEvent][endDay]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -334,17 +198,6 @@ function getActiveClassCounts(startDay) {
|
|||
|
||||
// *** Helper functions ***
|
||||
|
||||
function slugify(text)
|
||||
// https://gist.github.com/mathewbyrne/1280286
|
||||
{
|
||||
return text.toString().toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
|
||||
.replace(/\-\-+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, ''); // Trim - from end of text
|
||||
}
|
||||
|
||||
function log(str) {
|
||||
print(new Date().toISOString() + " " + str);
|
||||
}
|
||||
|
|
|
@ -26,12 +26,9 @@ class AnalyticsPerDayHandler extends Handler
|
|||
|
||||
getActiveClasses: (req, res) ->
|
||||
events = [
|
||||
'Active classes private clan',
|
||||
'Active classes managed subscription',
|
||||
'Active classes bulk subscription',
|
||||
'Active classes prepaid',
|
||||
'Active classes course free',
|
||||
'Active classes course paid'
|
||||
'Active classes paid',
|
||||
'Active classes trial',
|
||||
'Active classes free'
|
||||
]
|
||||
|
||||
AnalyticsString.find({v: {$in: events}}).exec (err, documents) =>
|
||||
|
|
|
@ -309,6 +309,7 @@ UserHandler = class UserHandler extends Handler
|
|||
return @getByIDs(req, res) if args[1] is 'users'
|
||||
return @getNamesByIDs(req, res) if args[1] is 'names'
|
||||
return @getPrepaidCodes(req, res) if args[1] is 'prepaid_codes'
|
||||
return @getSchoolCounts(req, res) if args[1] is 'school_counts'
|
||||
return @nameToID(req, res, args[0]) if args[1] is 'nameToID'
|
||||
return @getLevelSessionsForEmployer(req, res, args[0]) if args[1] is 'level.sessions' and args[2] is 'employer'
|
||||
return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
|
||||
|
@ -464,6 +465,27 @@ UserHandler = class UserHandler extends Handler
|
|||
Prepaid.find({}).or(orQuery).exec (err, documents) =>
|
||||
@sendSuccess(res, documents)
|
||||
|
||||
getSchoolCounts: (req, res) ->
|
||||
return @sendSuccess(res, []) unless req.user?.isAdmin()
|
||||
minCount = req.body.minCount ? 20
|
||||
query = {$and: [
|
||||
{anonymous: false},
|
||||
{schoolName: {$exists: true}},
|
||||
{schoolName: {$ne: ''}}
|
||||
]}
|
||||
User.find(query, {schoolName: 1}).exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
schoolCountMap = {}
|
||||
for doc in documents
|
||||
schoolName = doc.get('schoolName')
|
||||
schoolCountMap[schoolName] ?= 0;
|
||||
schoolCountMap[schoolName]++;
|
||||
schoolCounts = []
|
||||
for schoolName, count of schoolCountMap
|
||||
continue unless count >= minCount
|
||||
schoolCounts.push schoolName: schoolName, count: count
|
||||
@sendSuccess(res, schoolCounts)
|
||||
|
||||
agreeToCLA: (req, res) ->
|
||||
return @sendForbiddenError(res) unless req.user
|
||||
doc =
|
||||
|
|
Loading…
Reference in a new issue