From cea04d27ad63b1f3232df51c58e19e730648b384 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Mon, 27 Oct 2014 17:11:48 -0700 Subject: [PATCH 01/16] Built diplomat-specific views for editing levels, components, achievements and thang types. --- app/Router.coffee | 6 + app/lib/deltas.coffee | 4 + app/locale/en.coffee | 1 + app/models/CocoModel.coffee | 9 +- app/schemas/models/achievement.coffee | 6 +- app/schemas/models/level_component.coffee | 8 + app/schemas/models/thang_type.coffee | 3 + app/schemas/schemas.coffee | 1 + app/styles/i18n/i18n-edit-model-view.sass | 12 ++ app/templates/editor/achievement/edit.jade | 1 + .../component/level-component-edit-view.jade | 4 +- .../editor/thang/thang-type-edit-view.jade | 2 + app/templates/i18n/i18n-edit-model-view.jade | 71 ++++++++ app/templates/i18n/i18n-home-view.jade | 20 +++ app/views/editor/DeltaView.coffee | 4 +- app/views/editor/PatchModal.coffee | 6 +- .../achievement/AchievementEditView.coffee | 4 +- .../components/LevelComponentEditView.coffee | 1 + .../editor/level/modals/SaveLevelModal.coffee | 3 +- .../editor/thang/ThangTypeEditView.coffee | 8 +- app/views/i18n/I18NEditAchievementView.coffee | 16 ++ app/views/i18n/I18NEditComponentView.coffee | 44 +++++ app/views/i18n/I18NEditLevelView.coffee | 48 ++++++ app/views/i18n/I18NEditModelView.coffee | 159 ++++++++++++++++++ app/views/i18n/I18NEditThangTypeView.coffee | 15 ++ app/views/i18n/I18NHomeView.coffee | 87 ++++++++++ app/views/kinds/RootView.coffee | 11 +- app/views/modal/VersionsModal.coffee | 3 +- package.json | 3 +- server/achievements/Achievement.coffee | 1 + .../achievements/achievement_handler.coffee | 28 ++- server/commons/Handler.coffee | 35 ++++ .../levels/components/LevelComponent.coffee | 1 + .../components/level_component_handler.coffee | 1 + server/levels/level_handler.coffee | 1 + .../levels/thangs/thang_type_handler.coffee | 27 ++- server_setup.coffee | 1 + 37 files changed, 627 insertions(+), 28 deletions(-) create mode 100644 app/styles/i18n/i18n-edit-model-view.sass create mode 100644 app/templates/i18n/i18n-edit-model-view.jade create mode 100644 app/templates/i18n/i18n-home-view.jade create mode 100644 app/views/i18n/I18NEditAchievementView.coffee create mode 100644 app/views/i18n/I18NEditComponentView.coffee create mode 100644 app/views/i18n/I18NEditLevelView.coffee create mode 100644 app/views/i18n/I18NEditModelView.coffee create mode 100644 app/views/i18n/I18NEditThangTypeView.coffee create mode 100644 app/views/i18n/I18NHomeView.coffee diff --git a/app/Router.coffee b/app/Router.coffee index dcdbfd492..30d3f9eb1 100644 --- a/app/Router.coffee +++ b/app/Router.coffee @@ -72,6 +72,12 @@ module.exports = class CocoRouter extends Backbone.Router 'github/*path': 'routeToServer' + 'i18n': go('i18n/I18NHomeView') + 'i18n/thang/:handle': go('i18n/I18NEditThangTypeView') + 'i18n/component/:handle': go('i18n/I18NEditComponentView') + 'i18n/level/:handle': go('i18n/I18NEditLevelView') + 'i18n/achievement/:handle': go('i18n/I18NEditAchievementView') + 'legal': go('LegalView') 'multiplayer': go('MultiplayerView') diff --git a/app/lib/deltas.coffee b/app/lib/deltas.coffee index 00d941a1e..8cff67902 100644 --- a/app/lib/deltas.coffee +++ b/app/lib/deltas.coffee @@ -175,3 +175,7 @@ prunePath = (delta, path) -> prunePath delta[path[0]], path.slice(1) unless delta[path[0]] is undefined keys = (k for k in _.keys(delta[path[0]]) when k isnt '_t') delete delta[path[0]] if keys.length is 0 + +module.exports.DOC_SKIP_PATHS = [ + '_id','version', 'commitMessage', 'parent', 'created', + 'slug', 'index', '__v', 'patches', 'creator', 'js', 'watchers'] \ No newline at end of file diff --git a/app/locale/en.coffee b/app/locale/en.coffee index b62b10bee..a09185eea 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -536,6 +536,7 @@ achievement_query_misc: "Key achievement off of miscellanea" achievement_query_goals: "Key achievement off of level goals" level_completion: "Level Completion" + pop_i18n: "Populate I18N" article: edit_btn_preview: "Preview" diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index 1e7678ad3..1f45bd0c6 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -233,7 +233,7 @@ class CocoModel extends Backbone.Model getDelta: -> differ = deltasLib.makeJSONDiffer() - differ.diff @_revertAttributes, @attributes + differ.diff(_.omit(@_revertAttributes, deltasLib.DOC_SKIP_PATHS), _.omit(@attributes, deltasLib.DOC_SKIP_PATHS)) getDeltaWith: (otherModel) -> differ = deltasLib.makeJSONDiffer() @@ -272,9 +272,11 @@ class CocoModel extends Backbone.Model sum = 0 data ?= $.extend true, {}, @attributes schema ?= @schema() or {} + addedI18N = false if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n? data.i18n = {'-':'-'} # mongoose doesn't work with empty objects sum += 1 + addedI18N = true if _.isPlainObject data for key, value of data @@ -287,6 +289,7 @@ class CocoModel extends Backbone.Model if schema.items and _.isArray data sum += @populateI18N(value, schema.items, path+'/'+index) for value, index in data + @set('i18n', data.i18n) if addedI18N and not path # need special case for root i18n @updateI18NCoverage() sum @@ -343,10 +346,8 @@ class CocoModel extends Backbone.Model updateI18NCoverage: -> i18nObjects = @findI18NObjects() - console.log 'i18n objects', i18nObjects + return unless i18nObjects.length langCodeArrays = (_.keys(i18n) for i18n in i18nObjects) - console.log 'lang code arrays', langCodeArrays - window.codes = langCodeArrays @set('i18nCoverage', _.intersection(langCodeArrays...)) findI18NObjects: (data, results) -> diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee index e8f64c7a3..289b5e2bd 100644 --- a/app/schemas/models/achievement.coffee +++ b/app/schemas/models/achievement.coffee @@ -78,10 +78,7 @@ _.extend AchievementSchema.properties, default: {kind: 'linear', parameters: {}} required: ['kind', 'parameters'] additionalProperties: false - i18n: c.object - format: 'i18n' - props: ['name', 'description'] - description: 'Help translate this achievement' + i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate this achievement'} rewards: c.RewardSchema 'awarded by this achievement' @@ -93,5 +90,6 @@ _.extend AchievementSchema, # Let's have these on the bottom AchievementSchema.definitions = {} AchievementSchema.definitions['mongoQueryOperator'] = MongoQueryOperatorSchema AchievementSchema.definitions['mongoFindQuery'] = MongoFindQuerySchema +c.extendTranslationCoverageProperties AchievementSchema module.exports = AchievementSchema diff --git a/app/schemas/models/level_component.coffee b/app/schemas/models/level_component.coffee index 6a4b8fa72..a89972b4d 100644 --- a/app/schemas/models/level_component.coffee +++ b/app/schemas/models/level_component.coffee @@ -23,6 +23,12 @@ PropertyDocumentationSchema = c.object { required: ['name', 'type', 'description'] }, name: {type: 'string', title: 'Name', description: 'Name of the property.'} + i18n: { type: 'object', format: 'i18n', props: ['description', 'context'], description: 'Help translate this property'} + context: { + type: 'object' + title: 'Example template context' + additionalProperties: { type: 'string' } + } codeLanguages: c.array {title: 'Specific Code Languages', description: 'If present, then only the languages specified will show this documentation. Leave unset for language-independent documentation.', format: 'code-languages-array'}, c.shortString(title: 'Code Language', description: 'A specific code language to show this documentation for.', format: 'code-language') # not actual JS types, just whatever they describe... type: c.shortString(title: 'Type', description: 'Intended type of the property.') @@ -84,6 +90,7 @@ PropertyDocumentationSchema = c.object { } {title: 'Description', type: 'string', description: 'Description of the return value.', maxLength: 1000} ] + i18n: { type: 'object', format: 'i18n', props: ['description'], description: 'Help translate this return value'} DependencySchema = c.object { title: 'Component Dependency' @@ -155,5 +162,6 @@ c.extendSearchableProperties LevelComponentSchema c.extendVersionedProperties LevelComponentSchema, 'level.component' c.extendPermissionsProperties LevelComponentSchema, 'level.component' c.extendPatchableProperties LevelComponentSchema +c.extendTranslationCoverageProperties LevelComponentSchema module.exports = LevelComponentSchema diff --git a/app/schemas/models/thang_type.coffee b/app/schemas/models/thang_type.coffee index 7b7c48ee1..e718a2db3 100644 --- a/app/schemas/models/thang_type.coffee +++ b/app/schemas/models/thang_type.coffee @@ -116,6 +116,7 @@ _.extend ThangTypeSchema.properties, rotationType: {title: 'Rotation', type: 'string', enum: ['isometric', 'fixed', 'free']} matchWorldDimensions: {title: 'Match World Dimensions', type: 'boolean'} shadow: {title: 'Shadow Diameter', type: 'number', format: 'meters', description: 'Shadow diameter in meters'} + description: { type:'string', format: 'markdown', title: 'Description' } layerPriority: title: 'Layer Priority' type: 'integer' @@ -144,6 +145,7 @@ _.extend ThangTypeSchema.properties, type: 'number' description: 'Snap to this many meters in the y-direction.' components: c.array {title: 'Components', description: 'Thangs are configured by changing the Components attached to them.', uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on 'original', not whole thing + i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate this ThangType\'s name and description.'} ThangTypeSchema.required = [] @@ -158,5 +160,6 @@ c.extendBasicProperties ThangTypeSchema, 'thang.type' c.extendSearchableProperties ThangTypeSchema c.extendVersionedProperties ThangTypeSchema, 'thang.type' c.extendPatchableProperties ThangTypeSchema +c.extendTranslationCoverageProperties ThangTypeSchema module.exports = ThangTypeSchema diff --git a/app/schemas/schemas.coffee b/app/schemas/schemas.coffee index 82978d592..d9c62f40e 100644 --- a/app/schemas/schemas.coffee +++ b/app/schemas/schemas.coffee @@ -166,6 +166,7 @@ me.FunctionArgumentSchema = me.object { required: ['name', 'type', 'example', 'description'] }, name: {type: 'string', pattern: me.identifierPattern, title: 'Name', description: 'Name of the function argument.'} + i18n: { type: 'object', format: 'i18n', props: ['description'], description: 'Help translate this argument'} # not actual JS types, just whatever they describe... type: me.shortString(title: 'Type', description: 'Intended type of the argument.') example: diff --git a/app/styles/i18n/i18n-edit-model-view.sass b/app/styles/i18n/i18n-edit-model-view.sass new file mode 100644 index 000000000..4438144b4 --- /dev/null +++ b/app/styles/i18n/i18n-edit-model-view.sass @@ -0,0 +1,12 @@ +.i18n-edit-model-view + #patch-submit + margin-top: 5px + + td + width: 40% + + .outer-content + padding: 10px + + select + margin-bottom: 10px \ No newline at end of file diff --git a/app/templates/editor/achievement/edit.jade b/app/templates/editor/achievement/edit.jade index 8e811b30c..5d33490de 100644 --- a/app/templates/editor/achievement/edit.jade +++ b/app/templates/editor/achievement/edit.jade @@ -14,6 +14,7 @@ block content button.achievement-tool-button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate button.achievement-tool-button(data-i18n="common.delete", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#delete-button Delete button.achievement-tool-button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save + button.achievement-tool-button(data-i18n="editor.pop_i18n").btn.btn-primary#populate-i18n-button Populate I18N h3(data-i18n="achievement.edit_achievement_title") Edit Achievement span diff --git a/app/templates/editor/level/component/level-component-edit-view.jade b/app/templates/editor/level/component/level-component-edit-view.jade index 86e11d051..422ba16b7 100644 --- a/app/templates/editor/level/component/level-component-edit-view.jade +++ b/app/templates/editor/level/component/level-component-edit-view.jade @@ -34,7 +34,9 @@ nav.navbar.navbar-default(role='navigation') if !me.get('anonymous') li#create-new-component-button a(data-i18n="editor.level_component_b_new") Create New Component - + li + a(data-i18n="editor.pop_i18n")#pop-component-i18n-button Populate i18n + li.divider li.dropdown-header Info diff --git a/app/templates/editor/thang/thang-type-edit-view.jade b/app/templates/editor/thang/thang-type-edit-view.jade index dc3fd3f83..742c41bd3 100644 --- a/app/templates/editor/thang/thang-type-edit-view.jade +++ b/app/templates/editor/thang/thang-type-edit-view.jade @@ -56,6 +56,8 @@ block header a(data-i18n="common.fork")#fork-start-button Fork li(class=anonymous ? "disabled": "") a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert + li(class=anonymous ? "disabled": "") + a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n li.divider li.dropdown-header Info li#history-button diff --git a/app/templates/i18n/i18n-edit-model-view.jade b/app/templates/i18n/i18n-edit-model-view.jade new file mode 100644 index 000000000..b5570bfe8 --- /dev/null +++ b/app/templates/i18n/i18n-edit-model-view.jade @@ -0,0 +1,71 @@ +extends /templates/base + +block header + if model.loading + nav.navbar.navbar-default(role='navigation') + .container-fluid + ul.nav.navbar-nav + li + a(href="/i18n") + span.glyphicon-home.glyphicon + + else + nav.navbar.navbar-default(role='navigation') + ul.nav.navbar-nav + li + a(href="/i18n") + span.glyphicon-home.glyphicon + + .navbar-header + span.navbar-brand #{model.get('name')} + + ul.nav.navbar-nav.navbar-right + li + button.btn.btn-info.btn-sm.pull-right#patch-submit(disabled=model.hasLocalChanges() ? null : 'disabled', value=model.id) Submit Changes + + li.dropdown + + a(data-toggle='dropdown') + span.glyphicon-chevron-down.glyphicon + + ul.dropdown-menu + li.dropdown-header Actions + + li(class=anonymous ? "disabled": "") + a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert + + li.divider + li.dropdown-header Info + li#history-button + a(href='#', data-i18n="general.version_history") Version History + li.divider + li.dropdown-header Help + li + a(href='https://github.com/codecombat/codecombat/wiki', data-i18n="editor.wiki", target="_blank") Wiki + li + a(href='http://www.hipchat.com/g3plnOKqa', data-i18n="editor.live_chat", target="_blank") Live Chat + li + a(href='http://discourse.codecombat.com/category/diplomat', data-i18n="nav.forum", target="_blank") Forum + li + a(data-toggle="coco-modal", data-target="modal/ContactModal", data-i18n="nav.contact") Email + +block outer_content + .outer-content + + select.form-control#language-select + + table.table + for row in translationList + tr(data-format=row.format || '') + th= row.title + td.english-value-row + div= row.enValue + td.to-value-row + if row.format === 'markdown' + div(data-index=row.index.toString())= row.toValue + else + input.input-sm.form-control.translation-input(data-index=row.index.toString(), value=row.toValue) + + div#error-view + .clearfix +block footer diff --git a/app/templates/i18n/i18n-home-view.jade b/app/templates/i18n/i18n-home-view.jade new file mode 100644 index 000000000..6feb08cec --- /dev/null +++ b/app/templates/i18n/i18n-home-view.jade @@ -0,0 +1,20 @@ +extends /templates/base + +block content + table.table.table-condensed + tr + th + select#language-select.form-control.input-sm + + th Type + th Specifically Covered + th Generally Covered + + for model in collection.models + tr + td + a(href=model.i18nURLBase+model.get('slug'))= model.get('name') + td= model.constructor.className + td(class=model.specificallyCovered ? 'success' : 'danger')= model.specificallyCovered ? 'Yes' : 'No' + td(class=model.generallyCovered ? 'success' : 'danger')= model.generallyCovered ? 'Yes' : 'No' + diff --git a/app/views/editor/DeltaView.coffee b/app/views/editor/DeltaView.coffee index c55aed360..15cef1e3f 100644 --- a/app/views/editor/DeltaView.coffee +++ b/app/views/editor/DeltaView.coffee @@ -93,14 +93,14 @@ module.exports = class DeltaView extends CocoView treemaOptions = { schema: deltaData.schema or {}, readOnly: true } if _.isObject(deltaData.left) and leftEl = deltaEl.find('.old-value') - options = _.defaults {data: deltaData.left}, treemaOptions + options = _.defaults {data: _.merge({}, deltaData.left)}, treemaOptions try TreemaNode.make(leftEl, options).build() catch error console.error "Couldn't show left details Treema for", deltaData.left, treemaOptions if _.isObject(deltaData.right) and rightEl = deltaEl.find('.new-value') - options = _.defaults {data: deltaData.right}, treemaOptions + options = _.defaults {data: _.merge({}, deltaData.right)}, treemaOptions try TreemaNode.make(rightEl, options).build() catch error diff --git a/app/views/editor/PatchModal.coffee b/app/views/editor/PatchModal.coffee index d22dc7ce3..7eddce2a2 100644 --- a/app/views/editor/PatchModal.coffee +++ b/app/views/editor/PatchModal.coffee @@ -2,15 +2,13 @@ ModalView = require 'views/kinds/ModalView' template = require 'templates/editor/patch_modal' DeltaView = require 'views/editor/DeltaView' auth = require 'lib/auth' +deltasLib = require 'lib/deltas' module.exports = class PatchModal extends ModalView id: 'patch-modal' template: template plain: true modalWidthPercent: 60 - @DOC_SKIP_PATHS = [ - '_id','version', 'commitMessage', 'parent', 'created', - 'slug', 'index', '__v', 'patches', 'creator', 'js', 'watchers'] events: 'click #withdraw-button': 'withdrawPatch' @@ -54,7 +52,7 @@ module.exports = class PatchModal extends ModalView afterRender: -> return super() unless @supermodel.finished() and @deltaWorked - @deltaView = new DeltaView({model:@pendingModel, headModel:@headModel, skipPaths: PatchModal.DOC_SKIP_PATHS}) + @deltaView = new DeltaView({model:@pendingModel, headModel:@headModel, skipPaths: deltasLib.DOC_SKIP_PATHS}) changeEl = @$el.find('.changes-stub') @insertSubView(@deltaView, changeEl) super() diff --git a/app/views/editor/achievement/AchievementEditView.coffee b/app/views/editor/achievement/AchievementEditView.coffee index 49115c7f0..323313a07 100644 --- a/app/views/editor/achievement/AchievementEditView.coffee +++ b/app/views/editor/achievement/AchievementEditView.coffee @@ -16,7 +16,9 @@ module.exports = class AchievementEditView extends RootView 'click #recalculate-button': 'confirmRecalculation' 'click #recalculate-all-button': 'confirmAllRecalculation' 'click #delete-button': 'confirmDeletion' - + 'click #populate-i18n-button': -> @achievement.populateI18N() + + constructor: (options, @achievementID) -> super options @achievement = new Achievement(_id: @achievementID) diff --git a/app/views/editor/level/components/LevelComponentEditView.coffee b/app/views/editor/level/components/LevelComponentEditView.coffee index db2779d73..fbea4a152 100644 --- a/app/views/editor/level/components/LevelComponentEditView.coffee +++ b/app/views/editor/level/components/LevelComponentEditView.coffee @@ -20,6 +20,7 @@ module.exports = class LevelComponentEditView extends CocoView 'click #component-history-button': 'showVersionHistory' 'click #patch-component-button': 'startPatchingComponent' 'click #component-watch-button': 'toggleWatchComponent' + 'click #pop-component-i18n-button': -> @levelComponent.populateI18N() constructor: (options) -> super options diff --git a/app/views/editor/level/modals/SaveLevelModal.coffee b/app/views/editor/level/modals/SaveLevelModal.coffee index 7d90d1e74..fd21f6aca 100644 --- a/app/views/editor/level/modals/SaveLevelModal.coffee +++ b/app/views/editor/level/modals/SaveLevelModal.coffee @@ -5,6 +5,7 @@ LevelComponent = require 'models/LevelComponent' LevelSystem = require 'models/LevelSystem' DeltaView = require 'views/editor/DeltaView' PatchModal = require 'views/editor/PatchModal' +deltasLib = require 'lib/deltas' module.exports = class SaveLevelModal extends SaveVersionModal template: template @@ -40,7 +41,7 @@ module.exports = class SaveLevelModal extends SaveVersionModal for changeEl, i in changeEls model = models[i] try - deltaView = new DeltaView({model: model, skipPaths: PatchModal.DOC_SKIP_PATHS}) + deltaView = new DeltaView({model: model, skipPaths: deltasLib.DOC_SKIP_PATHS}) @insertSubView(deltaView, $(changeEl)) catch e console.error 'Couldn\'t create delta view:', e diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index 5ee5c35d7..962f711c1 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -45,6 +45,7 @@ module.exports = class ThangTypeEditView extends RootView 'click .play-with-level-button': 'onPlayLevel' 'click .play-with-level-parent': 'onPlayLevelSelect' 'keyup .play-with-level-input': 'onPlayLevelKeyUp' + 'click #pop-level-i18n-button': 'onPopulateLevelI18N' subscriptions: 'editor:thang-type-color-groups-changed': 'onColorGroupsChanged' @@ -352,7 +353,8 @@ module.exports = class ThangTypeEditView extends RootView saveNewThangType: (e) -> newThangType = if e.major then @thangType.cloneNewMajorVersion() else @thangType.cloneNewMinorVersion() newThangType.set('commitMessage', e.commitMessage) - + newThangType.updateI18NCoverage() if newThangType.get('i18nCoverage') + res = newThangType.save() return unless res modal = $('#save-version-modal') @@ -455,6 +457,10 @@ module.exports = class ThangTypeEditView extends RootView showVersionHistory: (e) -> @openModalView new ThangTypeVersionsModal thangType: @thangType, @thangTypeID + onPopulateLevelI18N: -> + @thangType.populateI18N() + _.delay((-> document.location.reload()), 500) + openSaveModal: -> @openModalView new SaveVersionModal model: @thangType diff --git a/app/views/i18n/I18NEditAchievementView.coffee b/app/views/i18n/I18NEditAchievementView.coffee new file mode 100644 index 000000000..bddfbc3f2 --- /dev/null +++ b/app/views/i18n/I18NEditAchievementView.coffee @@ -0,0 +1,16 @@ +I18NEditModelView = require './I18NEditModelView' +Achievement = require 'models/Achievement' + +module.exports = class I18NEditAchievementView extends I18NEditModelView + id: "i18n-edit-component-view" + modelClass: Achievement + + buildTranslationList: -> + lang = @selectedLanguage + + # name, description + if i18n = @model.get('i18n') + if name = @model.get('name') + @wrapRow "Achievement name", ['name'], name, i18n[lang]?.name, [] + if description = @model.get('description') + @wrapRow "Achievement description", ['description'], description, i18n[lang]?.description, [] diff --git a/app/views/i18n/I18NEditComponentView.coffee b/app/views/i18n/I18NEditComponentView.coffee new file mode 100644 index 000000000..4750ea1af --- /dev/null +++ b/app/views/i18n/I18NEditComponentView.coffee @@ -0,0 +1,44 @@ +I18NEditModelView = require './I18NEditModelView' +LevelComponent = require 'models/LevelComponent' + +module.exports = class I18NEditComponentView extends I18NEditModelView + id: "i18n-edit-component-view" + modelClass: LevelComponent + + buildTranslationList: -> + lang = @selectedLanguage + + propDocs = @model.get('propertyDocumentation') + + for propDoc, propDocIndex in propDocs + + #- Component property descriptions + if i18n = propDoc.i18n + path = ["propertyDocumentation", propDocIndex] + if _.isObject propDoc.description + for progLang, description of propDoc + @wrapRow "#{propDoc.name} description (#{progLang})", [progLang, 'description'], description, i18n[lang]?[progLang]?.description, path, 'markdown' + else if _.isString propDoc.description + @wrapRow "#{propDoc.name} description", ['description'], propDoc.description, i18n[lang]?.description, path, 'markdown' + + #- Component return value descriptions + if i18n = propDoc.returns?.i18n + path = ["propertyDocumentation", propDocIndex, "returns"] + d = propDoc.returns.description + if _.isObject d + for progLang, description of d + @wrapRow "#{propDoc.name} return val (#{progLang})", [progLang, 'description'], description, i18n[lang]?[progLang]?.description, path, 'markdown' + else if _.isString d + @wrapRow "#{propDoc.name} return val", ['description'], d, i18n[lang]?.description, path, 'markdown' + + #- Component argument descriptions + if propDoc.args + for argDoc, argIndex in propDoc.args + if i18n = argDoc.i18n + path = ["propertyDocumentation", propDocIndex, 'args', argIndex] + if _.isObject argDoc.description + for progLang, description of argDoc + @wrapRow "#{propDoc.name} arg description #{argDoc.name} (#{progLang})", [progLang, 'description'], description, i18n[lang]?[progLang]?.description, path, 'markdown' + else if _.isString argDoc.description + @wrapRow "#{propDoc.name} arg description #{argDoc.name}", ['description'], argDoc.description, i18n[lang]?.description, path, 'markdown' + \ No newline at end of file diff --git a/app/views/i18n/I18NEditLevelView.coffee b/app/views/i18n/I18NEditLevelView.coffee new file mode 100644 index 000000000..20dc275a4 --- /dev/null +++ b/app/views/i18n/I18NEditLevelView.coffee @@ -0,0 +1,48 @@ +I18NEditModelView = require './I18NEditModelView' +Level = require 'models/Level' + +module.exports = class I18NEditLevelView extends I18NEditModelView + id: "i18n-edit-level-view" + modelClass: Level + + buildTranslationList: -> + lang = @selectedLanguage + + # name, description + if i18n = @model.get('i18n') + if name = @model.get('name') + @wrapRow "Level name", ['name'], name, i18n[lang]?.name, [] + if description = @model.get('description') + @wrapRow "Level description", ['description'], description, i18n[lang]?.description, [] + + # goals + for goal, index in @model.get('goals') ? [] + if i18n = goal.i18n + @wrapRow "Goal name", ['name'], goal.name, i18n[lang]?.name, ['goals', index] + + # documentation + for doc, index in @model.get('documentation')?.specificArticles ? [] + if i18n = doc.i18n + @wrapRow "Guide article name", ['name'], doc.name, i18n[lang]?.name, ['documentation', 'specificArticles', index] + @wrapRow "'#{doc.name}' description", ['description'], doc.description, i18n[lang]?.description, ['documentation', 'specificArticles', index], 'markdown' + + # sprite dialogues + for script, scriptIndex in @model.get('scripts') ? [] + for noteGroup, noteGroupIndex in script.noteChain ? [] + for spriteCommand, spriteCommandIndex in noteGroup.sprites ? [] + pathPrefix = ['scripts', scriptIndex, 'noteChain', noteGroupIndex, 'sprites', spriteCommandIndex, 'say'] + + if i18n = spriteCommand.say?.i18n + if spriteCommand.say.text + @wrapRow "Sprite text", ['text'], spriteCommand.say.text, i18n[lang]?.text, pathPrefix, 'markdown' + if spriteCommand.say.blurb + @wrapRow "Sprite blurb", ['blurb'], spriteCommand.say.blurb, i18n[lang]?.blurb, pathPrefix + + for response, responseIndex in spriteCommand.say?.responses ? [] + if i18n = response.i18n + @wrapRow "Response button", ['text'], response.text, i18n[lang]?.text, pathPrefix.concat(['responses', responseIndex]) + + # victory modal + if i18n = @model.get('victory')?.i18n + @wrapRow "Victory text", ['body'], @model.get('victory').body, i18n[lang]?.body, ['victory'], 'markdown' + \ No newline at end of file diff --git a/app/views/i18n/I18NEditModelView.coffee b/app/views/i18n/I18NEditModelView.coffee new file mode 100644 index 000000000..b01a0ecf7 --- /dev/null +++ b/app/views/i18n/I18NEditModelView.coffee @@ -0,0 +1,159 @@ +RootView = require 'views/kinds/RootView' +locale = require 'locale/locale' +Patch = require 'models/Patch' +template = require 'templates/i18n/i18n-edit-model-view' +deltasLib = require 'lib/deltas' + +module.exports = class I18NEditModelView extends RootView + className: 'editor i18n-edit-model-view' + template: template + + events: + 'change .translation-input': 'onInputChanged' + 'change #language-select': 'onLanguageSelectChanged' + 'click #patch-submit': 'onSubmitPatch' + + constructor: (options, @modelHandle) -> + super(options) + @model = new @modelClass(_id: @modelHandle) + @model = @supermodel.loadModel(@model, 'model').model + @model.saveBackups = true + @selectedLanguage = me.get('preferredLanguage', true) + + showLoading: ($el) -> + $el ?= @$el.find('.outer-content') + super($el) + + onLoaded: -> + super() + @model.markToRevert() unless @model.hasLocalChanges() + + getRenderData: -> + c = super() + + c.model = @model + c.selectedLanguage = @selectedLanguage + + @translationList = [] + if @supermodel.finished() then @buildTranslationList() else [] + result.index = index for result, index in @translationList + c.translationList = @translationList + + c + + afterRender: -> + super() + + @hush = true + $select = @$el.find('#language-select').empty() + @addLanguagesToSelect($select, @selectedLanguage) + @hush = false + editors = [] + + @$el.find('tr[data-format="markdown"]').each((index, el) => + englishEditor = ace.edit(enEl=$(el).find('.english-value-row div')[0]) + englishEditor.el = enEl + englishEditor.setReadOnly(true) + toEditor = ace.edit(toEl=$(el).find('.to-value-row div')[0]) + toEditor.el = toEl + toEditor.on 'change', @onEditorChange + editors = editors.concat([englishEditor, toEditor]) + ) + + for editor in editors + session = editor.getSession() + session.setTabSize 2 + session.setMode 'ace/mode/markdown' + session.setNewLineMode = 'unix' + session.setUseSoftTabs true + editor.setOptions({ maxLines: Infinity }) + + onEditorChange: (event, editor) => + return if @destroyed + index = $(editor.el).data('index') + rowInfo = @translationList[index] + value = editor.getValue() + @onTranslationChanged(rowInfo, value) + + wrapRow: (title, key, enValue, toValue, path, format) -> + @translationList.push { + title: title, + key: key, + enValue: enValue, + toValue: toValue or '', + path: path + format: format + } + + buildTranslationList: -> [] # overwrite + + onInputChanged: (e) -> + index = $(e.target).data('index') + rowInfo = @translationList[index] + value = $(e.target).val() + @onTranslationChanged(rowInfo, value) + + onTranslationChanged: (rowInfo, value) -> + + #- Navigate down to where the translation will live + base = @model.attributes + + for seg in rowInfo.path + base = base[seg] + + base = base.i18n + + base[@selectedLanguage] ?= {} + base = base[@selectedLanguage] + + if rowInfo.key.length > 1 + for seg in rowInfo.key[..-2] + base[seg] ?= {} + base = base[seg] + + #- Set the data in a non-kosher way + + base[rowInfo.key[rowInfo.key.length-1]] = value + @model.saveBackup() + + #- Enable patch submit button + + @$el.find('#patch-submit').attr('disabled', null) + + onLanguageSelectChanged: (e) -> + return if @hush + @selectedLanguage = $(e.target).val() + @render() + + onSubmitPatch: (e) -> + + delta = @model.getDelta() + flattened = deltasLib.flattenDelta(delta) + save = _.all(flattened, (delta) -> + return _.isArray(delta.o) and delta.o.length is 1 and 'i18n' in delta.dataPath + ) + + if save + modelToSave = @model.cloneNewMinorVersion() + modelToSave.updateI18NCoverage() if modelToSave.get('i18nCoverage') + + else + modelToSave = new Patch() + modelToSave.set 'delta', @model.getDelta() + modelToSave.set 'target', { + 'collection': _.string.underscored @model.constructor.className + 'id': @model.id + } + + if @modelClass.schema.properties.commitMessage + commitMessage = "Diplomat submission for lang #{@selectedLanguage}: #{flattened.length} change(s)." + modelToSave.set 'commitMessage', commitMessage + + errors = modelToSave.validate() + button = $(e.target) + button.attr('disabled', 'disabled') + return button.text('Failed to Submit Changes') if errors + res = modelToSave.save() + return button.text('Failed to Submit Changes') unless res + res.error => button.text('Error Submitting Changes') + res.success => button.text('Submit Changes') diff --git a/app/views/i18n/I18NEditThangTypeView.coffee b/app/views/i18n/I18NEditThangTypeView.coffee new file mode 100644 index 000000000..5c4fcaf76 --- /dev/null +++ b/app/views/i18n/I18NEditThangTypeView.coffee @@ -0,0 +1,15 @@ +I18NEditModelView = require './I18NEditModelView' +ThangType = require 'models/ThangType' + +module.exports = class ThangTypeI18NView extends I18NEditModelView + id: "thang-type-i18n-view" + modelClass: ThangType + + buildTranslationList: -> + lang = @selectedLanguage + @model.markToRevert() unless @model.hasLocalChanges() + i18n = @model.get('i18n') + if i18n + name = @model.get('name') + @wrapRow('Name', ['name'], name, i18n[lang]?.name, []) + @wrapRow('Description', ['description'], @model.get('description'), i18n[lang]?.description, [], 'markdown') \ No newline at end of file diff --git a/app/views/i18n/I18NHomeView.coffee b/app/views/i18n/I18NHomeView.coffee new file mode 100644 index 000000000..4d8c86099 --- /dev/null +++ b/app/views/i18n/I18NHomeView.coffee @@ -0,0 +1,87 @@ +RootView = require 'views/kinds/RootView' +template = require 'templates/i18n/i18n-home-view' +CocoCollection = require 'collections/CocoCollection' + +LevelComponent = require 'models/LevelComponent' +ThangType = require 'models/ThangType' +Level = require 'models/Level' +Achievement = require 'models/Achievement' + +languages = _.keys(require 'locale/locale').sort() +PAGE_SIZE = 100 + +module.exports = class I18NHomeView extends RootView + id: "i18n-home-view" + template: template + + events: + 'change #language-select': 'onLanguageSelectChanged' + + constructor: (options) -> + super(options) + @selectedLanguage = me.get('preferredLanguage', true) + + #- + @aggregateModels = new Backbone.Collection() + project = ['name', 'components.original', 'i18nCoverage', 'slug'] + + @thangTypes = new CocoCollection([], { url: '/db/thang.type?view=i18n-coverage', project: project, model: ThangType }) + @components = new CocoCollection([], { url: '/db/level.component?view=i18n-coverage', project: project, model: LevelComponent }) + @levels = new CocoCollection([], { url: '/db/level?view=i18n-coverage', project: project, model: Level }) + @achievements = new CocoCollection([], { url: '/db/achievement?view=i18n-coverage', project: project, model: Achievement }) + + for c in [@thangTypes, @components, @levels, @achievements] + c.skip = 0 + c.fetch({data: {skip: 0, limit: PAGE_SIZE}, cache:false}) + @supermodel.loadCollection(c, 'documents') + @listenTo c, 'sync', @onCollectionSynced + + + onCollectionSynced: (collection) -> + for model in collection.models + model.i18nURLBase = switch model.constructor.className + when "ThangType" then "/i18n/thang/" + when "LevelComponent" then "/i18n/component/" + when "Achievement" then "/i18n/achievement/" + when "Level" then "/i18n/level/" + getMore = collection.models.length is PAGE_SIZE + @aggregateModels.add(collection.models) + @render() + + if getMore + collection.skip += PAGE_SIZE + collection.fetch({data: {skip: collection.skip, limit: PAGE_SIZE}}) + + getRenderData: -> + c = super() + @updateCoverage() + c.languages = languages + c.selectedLanguage = @selectedLanguage + c.collection = @aggregateModels + c + + updateCoverage: -> + selectedBase = @selectedLanguage[..2] + relatedLanguages = (l for l in languages when l.startsWith(selectedBase) and l isnt @selectedLanguage) + for model in @aggregateModels.models + @updateCoverageForModel(model, relatedLanguages) + model.generallyCovered = true if @selectedLanguage.startsWith 'en' + + updateCoverageForModel: (model, relatedLanguages) -> + model.specificallyCovered = true + model.generallyCovered = true + coverage = model.get('i18nCoverage') + + if @selectedLanguage not in coverage + model.specificallyCovered = false + if not _.any((l in coverage for l in relatedLanguages)) + model.generallyCovered = false + return + + afterRender: -> + super() + @addLanguagesToSelect(@$el.find('#language-select'), @selectedLanguage) + + onLanguageSelectChanged: (e) -> + @selectedLanguage = $(e.target).val() + @render() diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee index 29487d69b..7e05a2369 100644 --- a/app/views/kinds/RootView.coffee +++ b/app/views/kinds/RootView.coffee @@ -106,15 +106,20 @@ module.exports = class RootView extends CocoView $select.parent().find('.options, .trigger').remove() $select.unwrap().removeClass('fancified') preferred = me.get('preferredLanguage', true) + @addLanguagesToSelect($select, preferred) + $select.fancySelect().parent().find('.trigger').addClass('header-font') + $('body').attr('lang', preferred) + + addLanguagesToSelect: ($select, initialVal) -> + initialVal ?= me.get('preferredLanguage', true) codes = _.keys(locale) genericCodes = _.filter codes, (code) -> _.find(codes, (code2) -> code2 isnt code and code2.split('-')[0] is code) - for code, localeInfo of locale when not (code in genericCodes) or code is preferred + for code, localeInfo of locale when not (code in genericCodes) or code is initialVal $select.append( $('').val(code).text(localeInfo.nativeDescription)) - $select.val(preferred).fancySelect().parent().find('.trigger').addClass('header-font') - $('body').attr('lang', preferred) + $select.val(initialVal) onLanguageChanged: -> newLang = $('.language-dropdown').val() diff --git a/app/views/modal/VersionsModal.coffee b/app/views/modal/VersionsModal.coffee index ab63fd569..2c9d76499 100755 --- a/app/views/modal/VersionsModal.coffee +++ b/app/views/modal/VersionsModal.coffee @@ -4,6 +4,7 @@ DeltaView = require 'views/editor/DeltaView' PatchModal = require 'views/editor/PatchModal' nameLoader = require 'lib/NameLoader' CocoCollection = require 'collections/CocoCollection' +deltasLib = require 'lib/deltas' class VersionsViewCollection extends CocoCollection url: '' @@ -55,7 +56,7 @@ module.exports = class VersionsModal extends ModalView @deltaView = new DeltaView({ model: earlierVersion comparisonModel: laterVersion - skipPaths: PatchModal.DOC_SKIP_PATHS + skipPaths: deltasLib.DOC_SKIP_PATHS loadModels: true }) @insertSubView(@deltaView, deltaEl) diff --git a/package.json b/package.json index 68e029742..80d7aac88 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,8 @@ "node-gyp": "~0.13.0", "aether": "~0.2.39", "JASON": "~0.1.3", - "JQDeferred": "~2.1.0" + "JQDeferred": "~2.1.0", + "jsondiffpatch": "0.1.17" }, "devDependencies": { "jade": "0.33.x", diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee index 73d0b69ac..a4a87803d 100644 --- a/server/achievements/Achievement.coffee +++ b/server/achievements/Achievement.coffee @@ -64,6 +64,7 @@ AchievementSchema.post 'save', -> @constructor.loadAchievements() AchievementSchema.plugin(plugins.NamedPlugin) AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']}) +AchievementSchema.plugin plugins.TranslationCoveragePlugin module.exports = Achievement = mongoose.model('Achievement', AchievementSchema, 'achievements') diff --git a/server/achievements/achievement_handler.coffee b/server/achievements/achievement_handler.coffee index d9256cb1a..50fd1b0a8 100644 --- a/server/achievements/achievement_handler.coffee +++ b/server/achievements/achievement_handler.coffee @@ -5,13 +5,37 @@ class AchievementHandler extends Handler modelClass: Achievement # Used to determine which properties requests may edit - editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function', 'related', 'difficulty', 'category', 'rewards'] + editableProperties: [ + 'name' + 'query' + 'worth' + 'collection' + 'description' + 'userField' + 'proportionalTo' + 'icon' + 'function' + 'related' + 'difficulty' + 'category' + 'rewards' + 'i18n' + 'i18nCoverage' + ] + allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] jsonSchema = require '../../app/schemas/models/achievement.coffee' hasAccess: (req) -> - req.method is 'GET' or req.user?.isAdmin() + req.method in ['GET', 'PUT'] or req.user?.isAdmin() + + hasAccessToDocument: (req, document, method=null) -> + method = (method or req.method).toLowerCase() + return true if method is 'get' + return true if req.user?.isAdmin() + return true if method is 'put' and @isJustFillingTranslations(req, document) + return get: (req, res) -> # /db/achievement?related= diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index f8c0ea4be..baa889fef 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -7,6 +7,7 @@ Patch = require '../patches/Patch' User = require '../users/User' sendwithus = require '../sendwithus' hipchat = require '../hipchat' +deltasLib = require '../../app/lib/deltas' PROJECT = {original: 1, name: 1, version: 1, description: 1, slug: 1, kind: 1, created: 1, permissions: 1} FETCH_LIMIT = 300 @@ -32,9 +33,22 @@ module.exports = class Handler hasAccess: (req) -> true hasAccessToDocument: (req, document, method=null) -> return true if req.user?.isAdmin() + + if @modelClass.schema.uses_coco_translation_coverage and (method or req.method).toLowerCase() is 'put' + return true if @isJustFillingTranslations(req, document) + if @modelClass.schema.uses_coco_permissions return document.hasPermissionsForMethod?(req.user, method or req.method) return true + + isJustFillingTranslations: (req, document) -> + differ = deltasLib.makeJSONDiffer() + omissions = ['original'].concat(deltasLib.DOC_SKIP_PATHS) + delta = differ.diff(_.omit(document.toObject(), omissions), _.omit(req.body, omissions)) + flattened = deltasLib.flattenDelta(delta) + _.all(flattened, (delta) -> + # sometimes coverage gets moved around... allow other changes to happen to i18nCoverage + return _.isArray(delta.o) and (('i18n' in delta.dataPath and delta.o.length is 1) or 'i18nCoverage' in delta.dataPath)) formatEntity: (req, document) -> document?.toObject() getEditableProperties: (req, document) -> @@ -87,6 +101,27 @@ module.exports = class Handler get: (req, res) -> @sendForbiddenError(res) if not @hasAccess(req) + if @modelClass.schema.uses_coco_translation_coverage and req.query.view is 'i18n-coverage' + # TODO: generalize view, project, limit and skip query parameters + projection = {} + if req.query.project + projection[field] = 1 for field in req.query.project.split(',') + query = {slug: {$exists: true}, i18nCoverage: {$exists: true}} + q = @modelClass.find(query, projection) + + skip = parseInt(req.query.skip) + if skip? and skip < 1000000 + q.skip(skip) + + limit = parseInt(req.query.limit) + if limit? and limit < 1000 + q.limit(limit) + + q.exec (err, documents) => + return @sendDatabaseError(res, err) if err + documents = (@formatEntity(req, doc) for doc in documents) + @sendSuccess(res, documents) + specialParameters = ['term', 'project', 'conditions'] # If the model uses coco search it's probably a text search diff --git a/server/levels/components/LevelComponent.coffee b/server/levels/components/LevelComponent.coffee index d5eb14672..a714129c0 100644 --- a/server/levels/components/LevelComponent.coffee +++ b/server/levels/components/LevelComponent.coffee @@ -12,6 +12,7 @@ LevelComponentSchema.plugin plugins.PermissionsPlugin LevelComponentSchema.plugin plugins.VersionedPlugin LevelComponentSchema.plugin plugins.SearchablePlugin, {searchable: ['name', 'searchStrings', 'description']} LevelComponentSchema.plugin plugins.PatchablePlugin +LevelComponentSchema.plugin plugins.TranslationCoveragePlugin LevelComponentSchema.pre('save', (next) -> name = @get('name') strings = _.str.humanize(name).toLowerCase().split(' ') diff --git a/server/levels/components/level_component_handler.coffee b/server/levels/components/level_component_handler.coffee index 0dd44ec4a..41207680a 100644 --- a/server/levels/components/level_component_handler.coffee +++ b/server/levels/components/level_component_handler.coffee @@ -14,6 +14,7 @@ LevelComponentHandler = class LevelComponentHandler extends Handler 'propertyDocumentation' 'configSchema' 'name' + 'i18nCoverage' ] getEditableProperties: (req, document) -> diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee index b14a71968..73bdf3211 100644 --- a/server/levels/level_handler.coffee +++ b/server/levels/level_handler.coffee @@ -28,6 +28,7 @@ LevelHandler = class LevelHandler extends Handler 'banner' 'employerDescription' 'terrain' + 'i18nCoverage' ] postEditableProperties: ['name'] diff --git a/server/levels/thangs/thang_type_handler.coffee b/server/levels/thangs/thang_type_handler.coffee index 126e43cf3..dc0315eab 100644 --- a/server/levels/thangs/thang_type_handler.coffee +++ b/server/levels/thangs/thang_type_handler.coffee @@ -36,13 +36,22 @@ ThangTypeHandler = class ThangTypeHandler extends Handler 'featureImage' 'spriteType' 'i18nCoverage' + 'i18n' + 'description' ] hasAccess: (req) -> - req.method is 'GET' or req.user?.isAdmin() + req.method in ['GET', 'PUT'] or req.user?.isAdmin() + + hasAccessToDocument: (req, document, method=null) -> + method = (method or req.method).toLowerCase() + return true if method is 'get' + return true if req.user?.isAdmin() + return true if method is 'put' and @isJustFillingTranslations(req, document) + return get: (req, res) -> - if req.query.view in ['items', 'heroes'] + if req.query.view in ['items', 'heroes', 'i18n-coverage'] projection = {} if req.query.project projection[field] = 1 for field in req.query.project.split(',') @@ -52,7 +61,19 @@ ThangTypeHandler = class ThangTypeHandler extends Handler else if req.query.view is 'heroes' query.kind = 'Unit' query.original = {$in: _.values heroes} # TODO: replace with some sort of ThangType property later - ThangType.find(query, projection).exec (err, documents) => + else if req.query.view is 'i18n-coverage' + query.i18nCoverage = {$exists: true} + + q = ThangType.find(query, projection) + skip = parseInt(req.query.skip) + if skip? and skip < 1000000 + q.skip(skip) + + limit = parseInt(req.query.limit) + if limit? and limit < 1000 + q.limit(limit) + + q.exec (err, documents) => return @sendDatabaseError(res, err) if err documents = (@formatEntity(req, doc) for doc in documents) @sendSuccess(res, documents) diff --git a/server_setup.coffee b/server_setup.coffee index e90acccc6..f3638e644 100644 --- a/server_setup.coffee +++ b/server_setup.coffee @@ -14,6 +14,7 @@ config = require './server_config' auth = require './server/routes/auth' UserHandler = require './server/users/user_handler' global.tv4 = require 'tv4' # required for TreemaUtils to work +global.jsondiffpatch = require 'jsondiffpatch' productionLogging = (tokens, req, res) -> status = res.statusCode From 68ac412f2b2fb11cbf041f41ca6484bc67ffb65b Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Mon, 27 Oct 2014 22:54:50 -0700 Subject: [PATCH 02/16] I think this return is needed to prevent sending the i18n query results twice and crashing the server. --- app/views/i18n/I18NHomeView.coffee | 22 +++++++++++----------- server/commons/Handler.coffee | 7 ++++--- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/views/i18n/I18NHomeView.coffee b/app/views/i18n/I18NHomeView.coffee index 4d8c86099..7f933e644 100644 --- a/app/views/i18n/I18NHomeView.coffee +++ b/app/views/i18n/I18NHomeView.coffee @@ -13,30 +13,30 @@ PAGE_SIZE = 100 module.exports = class I18NHomeView extends RootView id: "i18n-home-view" template: template - + events: 'change #language-select': 'onLanguageSelectChanged' constructor: (options) -> super(options) @selectedLanguage = me.get('preferredLanguage', true) - - #- + + #- @aggregateModels = new Backbone.Collection() project = ['name', 'components.original', 'i18nCoverage', 'slug'] - + @thangTypes = new CocoCollection([], { url: '/db/thang.type?view=i18n-coverage', project: project, model: ThangType }) @components = new CocoCollection([], { url: '/db/level.component?view=i18n-coverage', project: project, model: LevelComponent }) @levels = new CocoCollection([], { url: '/db/level?view=i18n-coverage', project: project, model: Level }) @achievements = new CocoCollection([], { url: '/db/achievement?view=i18n-coverage', project: project, model: Achievement }) - + for c in [@thangTypes, @components, @levels, @achievements] c.skip = 0 c.fetch({data: {skip: 0, limit: PAGE_SIZE}, cache:false}) @supermodel.loadCollection(c, 'documents') @listenTo c, 'sync', @onCollectionSynced - + onCollectionSynced: (collection) -> for model in collection.models model.i18nURLBase = switch model.constructor.className @@ -51,7 +51,7 @@ module.exports = class I18NHomeView extends RootView if getMore collection.skip += PAGE_SIZE collection.fetch({data: {skip: collection.skip, limit: PAGE_SIZE}}) - + getRenderData: -> c = super() @updateCoverage() @@ -59,25 +59,25 @@ module.exports = class I18NHomeView extends RootView c.selectedLanguage = @selectedLanguage c.collection = @aggregateModels c - + updateCoverage: -> selectedBase = @selectedLanguage[..2] relatedLanguages = (l for l in languages when l.startsWith(selectedBase) and l isnt @selectedLanguage) for model in @aggregateModels.models @updateCoverageForModel(model, relatedLanguages) model.generallyCovered = true if @selectedLanguage.startsWith 'en' - + updateCoverageForModel: (model, relatedLanguages) -> model.specificallyCovered = true model.generallyCovered = true coverage = model.get('i18nCoverage') - + if @selectedLanguage not in coverage model.specificallyCovered = false if not _.any((l in coverage for l in relatedLanguages)) model.generallyCovered = false return - + afterRender: -> super() @addLanguagesToSelect(@$el.find('#language-select'), @selectedLanguage) diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index baa889fef..bc5161465 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -33,14 +33,14 @@ module.exports = class Handler hasAccess: (req) -> true hasAccessToDocument: (req, document, method=null) -> return true if req.user?.isAdmin() - + if @modelClass.schema.uses_coco_translation_coverage and (method or req.method).toLowerCase() is 'put' return true if @isJustFillingTranslations(req, document) - + if @modelClass.schema.uses_coco_permissions return document.hasPermissionsForMethod?(req.user, method or req.method) return true - + isJustFillingTranslations: (req, document) -> differ = deltasLib.makeJSONDiffer() omissions = ['original'].concat(deltasLib.DOC_SKIP_PATHS) @@ -121,6 +121,7 @@ module.exports = class Handler return @sendDatabaseError(res, err) if err documents = (@formatEntity(req, doc) for doc in documents) @sendSuccess(res, documents) + return specialParameters = ['term', 'project', 'conditions'] From 71a4a883e5db1b5908b63ee46c61f15341727230 Mon Sep 17 00:00:00 2001 From: enricpc Date: Tue, 28 Oct 2014 09:29:46 +0100 Subject: [PATCH 03/16] Update ca.coffee --- app/locale/ca.coffee | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/app/locale/ca.coffee b/app/locale/ca.coffee index a4596795e..33fac64ce 100644 --- a/app/locale/ca.coffee +++ b/app/locale/ca.coffee @@ -28,7 +28,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr about: "Sobre Nosaltres" contact: "Contacta" twitter_follow: "Segueix-nos" -# teachers: "Teachers" + teachers: "Profesors" modal: close: "Tancar" @@ -56,7 +56,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr account: "Conta" # Tooltip on account button from /play settings: "Configuració" # Tooltip on settings button from /play next: "Següent" # Go from choose hero to choose inventory before playing a level -# change_hero: "Change Hero" # Go back from choose inventory to choose hero + change_hero: "Canviar heroi" # Go back from choose inventory to choose hero choose_inventory: "Equipar objectes" older_campaigns: "Campanyes antigues" anonymous: "Jugador anònim" @@ -132,7 +132,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr body: "Cos" version: "Versió" # commit_msg: "Commit Message" -# version_history: "Version History" + version_history: "Historial de versions" # version_history_for: "Version History for: " result: "Resultat" results: "Resultats" @@ -148,8 +148,8 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr # opponent: "Opponent" # rank: "Rank" score: "Puntuació" -# win: "Win" -# loss: "Loss" + win: "Guanyats" + loss: "Perduts" # tie: "Tie" easy: "Fàcil" medium: "Intermedi" @@ -175,7 +175,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr play_level: done: "Fet" home: "Inici" -# skip: "Skip" + skip: "Ometre" game_menu: "Menu de joc" guide: "Guia" # restart: "Restart" @@ -200,10 +200,10 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr # victory_play_skip: "Skip Ahead" victory_play_next_level: "Jugar el següent nivell" # victory_play_more_practice: "More Practice" -# victory_play_too_easy: "Too Easy" + victory_play_too_easy: "Massa fàcil" # victory_play_just_right: "Just Right" -# victory_play_too_hard: "Too Hard" -# victory_saving_progress: "Saving Progress" + victory_play_too_hard: "Massa difícil" + victory_saving_progress: "Desa progrés" victory_go_home: "Tornar a l'inici" # Only in old-style levels. victory_review: "Diguens més!" # Only in old-style levels. # victory_hour_of_code_done: "Are You Done?" @@ -302,7 +302,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr # attack: "Damage" # Can also translate as "Attack" health: "Salut" speed: "Velocitat" -# skills: "Skills" + skills: "Habilitats" save_load: granularity_saved_games: "Desats" @@ -482,7 +482,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr # revert: "Revert" # revert_models: "Revert Models" # pick_a_terrain: "Pick A Terrain" -# small: "Small" + small: "Petit" # grassy: "Grassy" # fork_title: "Fork New Version" # fork_creating: "Creating Fork..." @@ -492,11 +492,11 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr live_chat: "Xat en directe" # level_some_options: "Some Options?" # level_tab_thangs: "Thangs" -# level_tab_scripts: "Scripts" + level_tab_scripts: "Scripts" level_tab_settings: "Configuració" level_tab_components: "Components" level_tab_systems: "Sistemes" -# level_tab_docs: "Documentation" + level_tab_docs: "Documentació" # level_tab_thangs_title: "Current Thangs" level_tab_thangs_all: "Tot" # level_tab_thangs_conditions: "Starting Conditions" @@ -508,13 +508,13 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr level_component_tab_title: "Components actuals" # level_component_btn_new: "Create New Component" level_systems_tab_title: "Sistemes actuals" -# level_systems_btn_new: "Create New System" + level_systems_btn_new: "Crea un nou sistema" level_systems_btn_add: "Afegir sistema" # level_components_title: "Back to All Thangs" # level_components_type: "Type" # level_component_edit_title: "Edit Component" # level_component_config_schema: "Config Schema" -# level_component_settings: "Settings" + level_component_settings: "Configuració" # level_system_edit_title: "Edit System" create_system_title: "Crea un nou sistema" # new_component_title: "Create New Component" @@ -537,9 +537,9 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr # achievement_query_goals: "Key achievement off of level goals" # level_completion: "Level Completion" -# article: -# edit_btn_preview: "Preview" -# edit_article_title: "Edit Article" + article: + edit_btn_preview: "Vista previa" + edit_article_title: "Editar l'article" contribute: # page_title: "Contributing" @@ -783,8 +783,8 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr # merge_conflict_with: "MERGE CONFLICT WITH" no_changes: "Sense canvis" -# guide: -# temp: "Temp" + guide: + temp: "Temp" multiplayer: multiplayer_title: "Configuració multijugador" # We'll be changing this around significantly soon. Until then, it's not important to translate. @@ -808,13 +808,13 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr # practices_description: "These are our promises to you, the player, in slightly less legalese." privacy_title: "Privacitat" # privacy_description: "We will not sell any of your personal information. We intend to make money through recruitment eventually, but rest assured we will not distribute your personal information to interested companies without your explicit consent." -# security_title: "Security" + security_title: "Seguretat" # security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems." # email_title: "Email" # email_description_prefix: "We will not inundate you with spam. Through" # email_settings_url: "your email settings" # email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time." -# cost_title: "Cost" + cost_title: "Cost" # cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:" # recruitment_title: "Recruitment" # recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizard–not just in the game, but also in real life." @@ -900,7 +900,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr profile_for_prefix: "Perfil de " profile_for_suffix: "" featured: "Destacat" -# not_featured: "Not Featured" + not_featured: "Sense destacar" looking_for: "Buscant:" last_updated: "Ultima actualització:" contact: "Contacta" @@ -1059,7 +1059,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr # av_entities_user_code_problems_list_url: "User Code Problems List" av_other_sub_title: "Altres" # av_other_debug_base_url: "Base (for debugging base.jade)" -# u_title: "User List" + u_title: "Llista d'usuaris" # ucp_title: "User Code Problems" # lg_title: "Latest Games" # clas: "CLAs" From 346f3b33bf40f1cba4655015cafa8d7473234cfe Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 09:41:51 -0700 Subject: [PATCH 04/16] Added context to the i18n editor for components. --- app/views/i18n/I18NEditComponentView.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/i18n/I18NEditComponentView.coffee b/app/views/i18n/I18NEditComponentView.coffee index 4750ea1af..455b1a1aa 100644 --- a/app/views/i18n/I18NEditComponentView.coffee +++ b/app/views/i18n/I18NEditComponentView.coffee @@ -20,6 +20,9 @@ module.exports = class I18NEditComponentView extends I18NEditModelView @wrapRow "#{propDoc.name} description (#{progLang})", [progLang, 'description'], description, i18n[lang]?[progLang]?.description, path, 'markdown' else if _.isString propDoc.description @wrapRow "#{propDoc.name} description", ['description'], propDoc.description, i18n[lang]?.description, path, 'markdown' + if context = propDoc.context + for key, value of context + @wrapRow "#{propDoc.name} context value", ["context", key], value, i18n[lang]?.context[key], path #- Component return value descriptions if i18n = propDoc.returns?.i18n @@ -41,4 +44,4 @@ module.exports = class I18NEditComponentView extends I18NEditModelView @wrapRow "#{propDoc.name} arg description #{argDoc.name} (#{progLang})", [progLang, 'description'], description, i18n[lang]?[progLang]?.description, path, 'markdown' else if _.isString argDoc.description @wrapRow "#{propDoc.name} arg description #{argDoc.name}", ['description'], argDoc.description, i18n[lang]?.description, path, 'markdown' - \ No newline at end of file + From ee95e4e450444713796c22152149a8d9cd624baf Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 09:47:49 -0700 Subject: [PATCH 05/16] Got rid of a race condition. --- server/commons/Handler.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index bc5161465..e56111398 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -101,6 +101,8 @@ module.exports = class Handler get: (req, res) -> @sendForbiddenError(res) if not @hasAccess(req) + specialParameters = ['term', 'project', 'conditions'] + if @modelClass.schema.uses_coco_translation_coverage and req.query.view is 'i18n-coverage' # TODO: generalize view, project, limit and skip query parameters projection = {} @@ -123,10 +125,8 @@ module.exports = class Handler @sendSuccess(res, documents) return - specialParameters = ['term', 'project', 'conditions'] - # If the model uses coco search it's probably a text search - if @modelClass.schema.uses_coco_search + else if @modelClass.schema.uses_coco_search term = req.query.term matchedObjects = [] filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}] From 7477633909acc3a79ed06bd06e2e57db5cdf244b Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 11:18:36 -0700 Subject: [PATCH 06/16] Trying to better get around mongoose stripping out empty i18n objects. --- app/models/CocoModel.coffee | 2 +- app/treema-ext.coffee | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index 1f45bd0c6..4aeff828f 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -274,7 +274,7 @@ class CocoModel extends Backbone.Model schema ?= @schema() or {} addedI18N = false if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n? - data.i18n = {'-':'-'} # mongoose doesn't work with empty objects + data.i18n = {'-':{'-':'-'}} # mongoose doesn't work with empty objects sum += 1 addedI18N = true diff --git a/app/treema-ext.coffee b/app/treema-ext.coffee index 8fe5242cc..ac9a141d9 100644 --- a/app/treema-ext.coffee +++ b/app/treema-ext.coffee @@ -266,6 +266,11 @@ class InternationalizationNode extends TreemaNode.nodeMap.object res = super(arguments...) res = (r for r in res when r[0] isnt '-') res + + populateData: -> + super() + if Object.keys(@data).length is 0 + @data['-'] = {'-':'-'} # also to get around mongoose bug getChildSchema: (key) -> #construct the child schema here From e5ea4f70e983bc76b65f89ded8c3ac132d800d87 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 11:28:31 -0700 Subject: [PATCH 07/16] Added a progress bar to the i18n home view. Sorting models by completion so documents needing translation go to the top. --- app/templates/i18n/i18n-home-view.jade | 3 +++ app/views/i18n/I18NHomeView.coffee | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/templates/i18n/i18n-home-view.jade b/app/templates/i18n/i18n-home-view.jade index 6feb08cec..c0604e32b 100644 --- a/app/templates/i18n/i18n-home-view.jade +++ b/app/templates/i18n/i18n-home-view.jade @@ -1,6 +1,9 @@ extends /templates/base block content + .progress + .progress-bar.progress-bar-info(role="progressbar" aria-valuenow=progress aria-valuemin="0" aria-valuemax="100" style="width: "+progress+"%")= progress+"%" + table.table.table-condensed tr th diff --git a/app/views/i18n/I18NHomeView.coffee b/app/views/i18n/I18NHomeView.coffee index 7f933e644..66be39cc1 100644 --- a/app/views/i18n/I18NHomeView.coffee +++ b/app/views/i18n/I18NHomeView.coffee @@ -23,6 +23,11 @@ module.exports = class I18NHomeView extends RootView #- @aggregateModels = new Backbone.Collection() + @aggregateModels.comparator = (m) -> + return 2 if m.specificallyCovered + return 1 if m.generallyCovered + return 0 + project = ['name', 'components.original', 'i18nCoverage', 'slug'] @thangTypes = new CocoCollection([], { url: '/db/thang.type?view=i18n-coverage', project: project, model: ThangType }) @@ -58,6 +63,11 @@ module.exports = class I18NHomeView extends RootView c.languages = languages c.selectedLanguage = @selectedLanguage c.collection = @aggregateModels + + covered = (m for m in @aggregateModels.models when m.specificallyCovered).length + total = @aggregateModels.models.length + c.progress = if total then parseInt(100 * covered / total) else 100 + c updateCoverage: -> @@ -66,7 +76,8 @@ module.exports = class I18NHomeView extends RootView for model in @aggregateModels.models @updateCoverageForModel(model, relatedLanguages) model.generallyCovered = true if @selectedLanguage.startsWith 'en' - + @aggregateModels.sort() + updateCoverageForModel: (model, relatedLanguages) -> model.specificallyCovered = true model.generallyCovered = true From 9e6fd119a037a9d68942f5fecd3cec3476585353 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 13:09:07 -0700 Subject: [PATCH 08/16] Achievements populate i18n whenever they're edited now. --- app/models/Achievement.coffee | 4 ++++ app/templates/editor/achievement/edit.jade | 1 - app/views/editor/achievement/AchievementEditView.coffee | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/Achievement.coffee b/app/models/Achievement.coffee index b4dc4c0e8..1142919c1 100644 --- a/app/models/Achievement.coffee +++ b/app/models/Achievement.coffee @@ -12,6 +12,10 @@ module.exports = class Achievement extends CocoModel getExpFunction: -> func = @get('function', true) return utils.functionCreators[func.kind](func.parameters) if func.kind of utils.functionCreators + + save: -> + @populateI18N() + super(arguments...) @styleMapping: 1: 'achievement-wood' diff --git a/app/templates/editor/achievement/edit.jade b/app/templates/editor/achievement/edit.jade index 5d33490de..8e811b30c 100644 --- a/app/templates/editor/achievement/edit.jade +++ b/app/templates/editor/achievement/edit.jade @@ -14,7 +14,6 @@ block content button.achievement-tool-button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate button.achievement-tool-button(data-i18n="common.delete", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#delete-button Delete button.achievement-tool-button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save - button.achievement-tool-button(data-i18n="editor.pop_i18n").btn.btn-primary#populate-i18n-button Populate I18N h3(data-i18n="achievement.edit_achievement_title") Edit Achievement span diff --git a/app/views/editor/achievement/AchievementEditView.coffee b/app/views/editor/achievement/AchievementEditView.coffee index 323313a07..db1a6ca29 100644 --- a/app/views/editor/achievement/AchievementEditView.coffee +++ b/app/views/editor/achievement/AchievementEditView.coffee @@ -16,8 +16,6 @@ module.exports = class AchievementEditView extends RootView 'click #recalculate-button': 'confirmRecalculation' 'click #recalculate-all-button': 'confirmAllRecalculation' 'click #delete-button': 'confirmDeletion' - 'click #populate-i18n-button': -> @achievement.populateI18N() - constructor: (options, @achievementID) -> super options From 895645ef6dcd7dd32a9a82eeb6608d897af15857 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 13:48:17 -0700 Subject: [PATCH 09/16] Levels and Components with i18nCoverage update that coverage from the level editor now. --- app/views/editor/level/modals/SaveLevelModal.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/editor/level/modals/SaveLevelModal.coffee b/app/views/editor/level/modals/SaveLevelModal.coffee index fd21f6aca..1f7d4f4ea 100644 --- a/app/views/editor/level/modals/SaveLevelModal.coffee +++ b/app/views/editor/level/modals/SaveLevelModal.coffee @@ -96,6 +96,7 @@ module.exports = class SaveLevelModal extends SaveVersionModal @showLoading() tuples = _.zip(modelsToSave, formsToSave) for [newModel, form] in tuples + newModel.updateI18NCoverage() if newModel.get('i18nCoverage') res = newModel.save() do (newModel, form) => res.error => From 67684b89d518d958bcb7b4c2db97995bf90aea95 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 14:21:22 -0700 Subject: [PATCH 10/16] Fixed #1687, forcing reloads when populating i18n in documents. --- app/views/editor/level/LevelEditView.coffee | 7 ++++++- .../editor/level/components/LevelComponentEditView.coffee | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index 578b681b7..2869c9b01 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -43,7 +43,7 @@ module.exports = class LevelEditView extends RootView 'click #components-tab': -> @subviews.editor_level_components_tab_view.refreshLevelThangsTreema @level.get('thangs') 'click #level-patch-button': 'startPatchingLevel' 'click #level-watch-button': 'toggleWatchLevel' - 'click #pop-level-i18n-button': -> @level.populateI18N() + 'click #pop-level-i18n-button': 'onPopulateI18N' 'click a[href="#editor-level-documentation"]': 'onClickDocumentationTab' 'mouseup .nav-tabs > li a': 'toggleTab' @@ -163,6 +163,11 @@ module.exports = class LevelEditView extends RootView button = @$el.find('#level-watch-button') @level.watch(button.find('.watch').is(':visible')) button.find('> span').toggleClass('secret') + + onPopulateI18N: -> + @level.populateI18N() + f = -> document.location.reload() + setTimeout(f, 200) toggleTab: (e) -> @renderScrollbar() diff --git a/app/views/editor/level/components/LevelComponentEditView.coffee b/app/views/editor/level/components/LevelComponentEditView.coffee index fbea4a152..10be2e7de 100644 --- a/app/views/editor/level/components/LevelComponentEditView.coffee +++ b/app/views/editor/level/components/LevelComponentEditView.coffee @@ -20,7 +20,7 @@ module.exports = class LevelComponentEditView extends CocoView 'click #component-history-button': 'showVersionHistory' 'click #patch-component-button': 'startPatchingComponent' 'click #component-watch-button': 'toggleWatchComponent' - 'click #pop-component-i18n-button': -> @levelComponent.populateI18N() + 'click #pop-component-i18n-button': 'onPopulateI18N' constructor: (options) -> super options @@ -134,6 +134,10 @@ module.exports = class LevelComponentEditView extends CocoView @levelComponent.watch(button.find('.watch').is(':visible')) button.find('> span').toggleClass('secret') + onPopulateI18N: -> + @levelComponent.populateI18N() + @render() + destroy: -> @destroyAceEditor(@editor) @componentSettingsTreema?.destroy() From e6c0d53d50337c621017c1aa304d89c87226e941 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 14:22:29 -0700 Subject: [PATCH 11/16] ThangType editor now deletes properties properly. --- app/views/editor/thang/ThangTypeEditView.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index 962f711c1..141a79dfe 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -407,8 +407,9 @@ module.exports = class ThangTypeEditView extends RootView pushChangesToPreview: => return if @temporarilyIgnoringChanges # TODO: This doesn't delete old Treema keys you deleted - for key, value of @treema.data - @thangType.set(key, value) + for key of @thangType.attributes + continue if key is 'components' + @thangType.set(key, @treema.data[key]) @updateSelectBox() @refreshAnimation() @updateDots() From dcb7aaf9a417fb9f30c0b7becd40e063f3954a72 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 14:23:39 -0700 Subject: [PATCH 12/16] Removed a TODO. --- app/views/editor/thang/ThangTypeEditView.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index 141a79dfe..775d5d77c 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -406,7 +406,6 @@ module.exports = class ThangTypeEditView extends RootView pushChangesToPreview: => return if @temporarilyIgnoringChanges - # TODO: This doesn't delete old Treema keys you deleted for key of @thangType.attributes continue if key is 'components' @thangType.set(key, @treema.data[key]) From a4729d9d6bba75a75c0c6560ff834dd03c06e409 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 28 Oct 2014 16:30:40 -0700 Subject: [PATCH 13/16] Finished migrating the guide into game menu. --- app/styles/game-menu/guide-view.sass | 30 +++++++++- app/styles/play/level/modal/docs.sass | 25 -------- app/templates/game-menu/game-menu-modal.jade | 6 +- app/templates/game-menu/guide-view.jade | 14 ++--- app/templates/play/level/control_bar.jade | 3 - app/templates/play/level/modal/docs.jade | 16 ----- app/views/game-menu/GameMenuModal.coffee | 4 +- app/views/game-menu/GuideView.coffee | 57 +++++++++++++++--- app/views/play/level/ControlBarView.coffee | 30 +--------- app/views/play/level/PlayLevelView.coffee | 31 ---------- .../play/level/modal/LevelGuideModal.coffee | 60 ------------------- 11 files changed, 91 insertions(+), 185 deletions(-) delete mode 100644 app/styles/play/level/modal/docs.sass delete mode 100644 app/templates/play/level/modal/docs.jade delete mode 100644 app/views/play/level/modal/LevelGuideModal.coffee diff --git a/app/styles/game-menu/guide-view.sass b/app/styles/game-menu/guide-view.sass index 186c61f81..a25a38fa5 100644 --- a/app/styles/game-menu/guide-view.sass +++ b/app/styles/game-menu/guide-view.sass @@ -1,3 +1,27 @@ -#guide-view - h3 - text-decoration: underline +#guide-view, #settings-treema .treema-markdown + .nav-tabs + height: 41px + + .tab-content + padding-top: 20px + margin-bottom: 50px + + li:not(.active) a[data-toggle="tab"] + cursor: pointer + + img + display: block + margin: 0 auto + + img + em + display: block + margin: 0 auto + text-align: center + + hr + border-color: #5c5c5c + width: 80% + + table + width: 80% + margin: 20px 10% \ No newline at end of file diff --git a/app/styles/play/level/modal/docs.sass b/app/styles/play/level/modal/docs.sass deleted file mode 100644 index f126a182d..000000000 --- a/app/styles/play/level/modal/docs.sass +++ /dev/null @@ -1,25 +0,0 @@ -#docs-modal .modal-dialog, #settings-treema .treema-markdown - width: 800px - - .tab-content - padding-top: 20px - - li:not(.active) a[data-toggle="tab"] - cursor: pointer - - img - display: block - margin: 0 auto - - img + em - display: block - margin: 0 auto - text-align: center - - hr - border-color: #5c5c5c - width: 80% - - table - width: 80% - margin: 20px 10% \ No newline at end of file diff --git a/app/templates/game-menu/game-menu-modal.jade b/app/templates/game-menu/game-menu-modal.jade index 53db4cbf4..81c55f7c8 100644 --- a/app/templates/game-menu/game-menu-modal.jade +++ b/app/templates/game-menu/game-menu-modal.jade @@ -6,13 +6,15 @@ block modal-body-content .button.close(type="button", data-dismiss="modal", aria-hidden="true") × .tabbable.tabs-left - var submenus = ["inventory", "choose-hero", "save-load", "options", "guide", "multiplayer"] - - if (!showDevBits) { // Not done yet. + - if (!showsGuide) { - submenus.splice(4, 1); + - } + - if (!showDevBits) { // Not done yet. - submenus.splice(2, 1); - } - if (!showInventory) - submenus.splice(0, 1); - ul.nav.nav-tabs + ul.nav.nav-tabs#game-menu-nav for submenu, index in submenus li(class=index ? "" : "active") a(href='#' + submenu + '-view', data-toggle='tab') diff --git a/app/templates/game-menu/guide-view.jade b/app/templates/game-menu/guide-view.jade index ff1ed3920..81c2613e5 100644 --- a/app/templates/game-menu/guide-view.jade +++ b/app/templates/game-menu/guide-view.jade @@ -1,7 +1,7 @@ -h3 Guide, and stuff - -div(data-i18n="guide.temp") Temp - -p ... and more stuff - -p Just put the existing guide in there pretty much as it is. Also add tab for keyboard shortcuts. +ul.nav.nav-tabs + for doc in docs + li + a(data-target="#docs_tab_#{doc.slug}", data-toggle="tab") #{doc.name} +div.tab-content + for doc in docs + div.tab-pane(id="docs_tab_#{doc.slug}")!= doc.html diff --git a/app/templates/play/level/control_bar.jade b/app/templates/play/level/control_bar.jade index 2a1c1716d..9d4100fc6 100644 --- a/app/templates/play/level/control_bar.jade +++ b/app/templates/play/level/control_bar.jade @@ -21,9 +21,6 @@ h4.title button.btn.btn-xs.btn-inverse.banner#game-menu-button(title="Show game menu", data-i18n="play_level.game_menu") Game Menu -if showsGuide - button.btn.btn-xs.btn-success.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide - if spectateGame button.btn.btn-xs.btn-inverse.banner#next-game-button(title="Next Game", data-i18n="play_level.next-game") Next game! diff --git a/app/templates/play/level/modal/docs.jade b/app/templates/play/level/modal/docs.jade deleted file mode 100644 index 62a8247c5..000000000 --- a/app/templates/play/level/modal/docs.jade +++ /dev/null @@ -1,16 +0,0 @@ -extends /templates/modal/modal_base - -block modal-header-content - h3(data-i18n="play_level.guide_title") Guide - -block modal-body-content - ul.nav.nav-tabs - for doc in docs - li - a(data-target="#docs_tab_#{doc.slug}", data-toggle="tab") #{doc.name} - div.tab-content - for doc in docs - div.tab-pane(id="docs_tab_#{doc.slug}")!= doc.html - -block modal-footer-content - a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close diff --git a/app/views/game-menu/GameMenuModal.coffee b/app/views/game-menu/GameMenuModal.coffee index f7488f505..af7f94668 100644 --- a/app/views/game-menu/GameMenuModal.coffee +++ b/app/views/game-menu/GameMenuModal.coffee @@ -16,7 +16,7 @@ module.exports = class GameMenuModal extends ModalView events: 'change input.select': 'onSelectionChanged' - 'shown.bs.tab .nav-tabs a': 'onTabShown' + 'shown.bs.tab #game-menu-nav a': 'onTabShown' constructor: (options) -> super options @@ -30,6 +30,8 @@ module.exports = class GameMenuModal extends ModalView context = super(context) context.showDevBits = @options.showDevBits context.showInventory = @options.showInventory + docs = @options.level.get('documentation') ? {} + context.showsGuide = docs.specificArticles?.length or docs.generalArticles?.length context afterRender: -> diff --git a/app/views/game-menu/GuideView.coffee b/app/views/game-menu/GuideView.coffee index 39daf588f..40a7f944d 100644 --- a/app/views/game-menu/GuideView.coffee +++ b/app/views/game-menu/GuideView.coffee @@ -1,17 +1,58 @@ CocoView = require 'views/kinds/CocoView' template = require 'templates/game-menu/guide-view' -{me} = require 'lib/auth' -ThangType = require 'models/ThangType' +Article = require 'models/Article' +utils = require 'lib/utils' -module.exports = class GuideView extends CocoView +# let's implement this once we have the docs database schema set up + +module.exports = class LevelGuideView extends CocoView + template: template id: 'guide-view' className: 'tab-pane' - template: template - getRenderData: (context={}) -> - context = super(context) - context + constructor: (options) -> + @firstOnly = options.firstOnly + @docs = options?.docs ? options.level.get('documentation') ? {} + general = @docs.generalArticles or [] + specific = @docs.specificArticles or [] + + articles = options.supermodel.getModels(Article) + articleMap = {} + articleMap[article.get('original')] = article for article in articles + general = (articleMap[ref.original] for ref in general) + general = (article.attributes for article in general when article) + + @docs = specific.concat(general) + @docs = $.extend(true, [], @docs) + @docs = [@docs[0]] if @firstOnly and @docs[0] + doc.html = marked(utils.i18n doc, 'body') for doc in @docs + doc.name = (utils.i18n doc, 'name') for doc in @docs + doc.slug = _.string.slugify(doc.name) for doc in @docs + super() + + getRenderData: -> + c = super() + c.docs = @docs + c afterRender: -> super() - #Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-open', volume: 1 # no, only when the tab is activated + if @docs.length is 1 + @$el.html(@docs[0].html) + else + # incredible hackiness. Getting bootstrap tabs to work shouldn't be this complex + @$el.find('.nav-tabs li:first').addClass('active') + @$el.find('.tab-content .tab-pane:first').addClass('active') + @$el.find('.nav-tabs a').click(@clickTab) + Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-open', volume: 1 + + clickTab: (e) => + @$el.find('li.active').removeClass('active') + Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-tab-switch', volume: 1 + + afterInsert: -> + super() + Backbone.Mediator.publish 'level:docs-shown', {} + + onHidden: -> + Backbone.Mediator.publish 'level:docs-hidden', {} diff --git a/app/views/play/level/ControlBarView.coffee b/app/views/play/level/ControlBarView.coffee index 722dec599..cf4839781 100644 --- a/app/views/play/level/ControlBarView.coffee +++ b/app/views/play/level/ControlBarView.coffee @@ -2,7 +2,6 @@ CocoView = require 'views/kinds/CocoView' template = require 'templates/play/level/control_bar' {me} = require 'lib/auth' -LevelGuideModal = require './modal/LevelGuideModal' GameMenuModal = require 'views/game-menu/GameMenuModal' RealTimeCollection = require 'collections/RealTimeCollection' @@ -16,16 +15,9 @@ module.exports = class ControlBarView extends CocoView 'real-time-multiplayer:left-game': 'onLeftRealTimeMultiplayerGame' events: - 'click #docs-button': -> - window.tracker?.trackEvent 'Clicked Docs', level: @level.get('name'), label: @level.get('name') - @showGuideModal() - 'click #next-game-button': -> Backbone.Mediator.publish 'level:next-game-pressed', {} - 'click #game-menu-button': 'showGameMenuModal' - 'click': -> Backbone.Mediator.publish 'tome:focus-editor', {} - 'click .home a': 'onClickHome' constructor: (options) -> @@ -69,30 +61,10 @@ module.exports = class ControlBarView extends CocoView c.multiplayerSession = @multiplayerSession if @multiplayerSession c.multiplayerPlayers = @multiplayerPlayers if @multiplayerPlayers c.meID = me.id - docs = @level.get('documentation') ? {} - c.showsGuide = docs.specificArticles?.length or docs.generalArticles?.length c - afterRender: -> - super() - @guideHighlightInterval ?= setInterval @onGuideHighlight, 5 * 60 * 1000 - - destroy: -> - clearInterval @guideHighlightInterval if @guideHighlightInterval - super() - - onGuideHighlight: => - return if @destroyed or @guideShownOnce - @$el.find('#docs-button').hide().show('highlight', 4000) - - showGuideModal: -> - options = {docs: @level.get('documentation'), supermodel: @supermodel} - @openModalView(new LevelGuideModal(options)) - clearInterval @guideHighlightInterval - @guideHighlightInterval = null - showGameMenuModal: -> - @openModalView new GameMenuModal level: @level, session: @session + @openModalView new GameMenuModal level: @level, session: @session, supermodel: @supermodel onClickHome: (e) -> e.preventDefault() diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index 4b6b4237f..b9bce5502 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -142,37 +142,6 @@ module.exports = class PlayLevelView extends RootView # CocoView overridden methods ############################################### - updateProgress: (progress) -> - super(progress) - return if @seenDocs - return if @isIPadApp() - return unless @levelLoader.session.loaded and @levelLoader.level.loaded - return unless showFrequency = @levelLoader.level.get('showsGuide') - session = @levelLoader.session - diff = new Date().getTime() - new Date(session.get('created')).getTime() - return if showFrequency is 'first-time' and diff > (5 * 60 * 1000) - articles = @levelLoader.supermodel.getModels Article - for article in articles - return unless article.loaded - @showGuide() - - showGuide: -> - @seenDocs = true - LevelGuideModal = require './modal/LevelGuideModal' - options = - docs: @levelLoader.level.get('documentation') - supermodel: @supermodel - firstOnly: true - @openModalView(new LevelGuideModal(options), true) - onGuideOpened = (e) -> - @guideOpenTime = new Date() - onGuideClosed = (e) -> - application.tracker?.trackTiming new Date() - @guideOpenTime, 'Intro Guide Time', @levelID, @levelID, 100 - @onLevelStarted() - Backbone.Mediator.subscribeOnce 'modal:opened', onGuideOpened, @ - Backbone.Mediator.subscribeOnce 'modal:closed', onGuideClosed, @ - return true - getRenderData: -> c = super() c.world = @world diff --git a/app/views/play/level/modal/LevelGuideModal.coffee b/app/views/play/level/modal/LevelGuideModal.coffee deleted file mode 100644 index d2b3c4b41..000000000 --- a/app/views/play/level/modal/LevelGuideModal.coffee +++ /dev/null @@ -1,60 +0,0 @@ -ModalView = require 'views/kinds/ModalView' -template = require 'templates/play/level/modal/docs' -Article = require 'models/Article' -utils = require 'lib/utils' - -# let's implement this once we have the docs database schema set up - -module.exports = class LevelGuideModal extends ModalView - template: template - id: 'docs-modal' - - shortcuts: - 'enter': 'hide' - - constructor: (options) -> - @firstOnly = options.firstOnly - @docs = options?.docs ? {} - general = @docs.generalArticles or [] - specific = @docs.specificArticles or [] - - articles = options.supermodel.getModels(Article) - articleMap = {} - articleMap[article.get('original')] = article for article in articles - general = (articleMap[ref.original] for ref in general) - general = (article.attributes for article in general when article) - - @docs = specific.concat(general) - @docs = $.extend(true, [], @docs) - @docs = [@docs[0]] if @firstOnly and @docs[0] - doc.html = marked(utils.i18n doc, 'body') for doc in @docs - doc.name = (utils.i18n doc, 'name') for doc in @docs - doc.slug = _.string.slugify(doc.name) for doc in @docs - super() - - getRenderData: -> - c = super() - c.docs = @docs - c - - afterRender: -> - super() - if @docs.length is 1 - @$el.find('.modal-body').html(@docs[0].html) - else - # incredible hackiness. Getting bootstrap tabs to work shouldn't be this complex - @$el.find('.nav-tabs li:first').addClass('active') - @$el.find('.tab-content .tab-pane:first').addClass('active') - @$el.find('.nav-tabs a').click(@clickTab) - Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-open', volume: 1 - - clickTab: (e) => - @$el.find('li.active').removeClass('active') - Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-tab-switch', volume: 1 - - afterInsert: -> - super() - Backbone.Mediator.publish 'level:docs-shown', {} - - onHidden: -> - Backbone.Mediator.publish 'level:docs-hidden', {} From c7713b41050b9d2c34fc36b8a25f5e08f1d81989 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Tue, 28 Oct 2014 13:44:49 -0700 Subject: [PATCH 14/16] Showing i18n in property docs. --- app/views/play/level/tome/DocFormatter.coffee | 16 ++++++++++++++++ server/commons/Handler.coffee | 1 - server/queues/scoring.coffee | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/views/play/level/tome/DocFormatter.coffee b/app/views/play/level/tome/DocFormatter.coffee index 220989f09..259998c75 100644 --- a/app/views/play/level/tome/DocFormatter.coffee +++ b/app/views/play/level/tome/DocFormatter.coffee @@ -1,6 +1,7 @@ popoverTemplate = require 'templates/play/level/tome/spell_palette_entry_popover' {downTheChain} = require 'lib/world/world_utils' window.Vector = require 'lib/world/vector' # So we can document it +utils = require 'lib/utils' safeJSONStringify = (input, maxDepth) -> recursion = (input, path, depth) -> @@ -89,11 +90,26 @@ module.exports = class DocFormatter if @doc.returns toTranslate.push {obj: @doc.returns, prop: 'example'}, {obj: @doc.returns, prop: 'description'} for {obj, prop} in toTranslate + # Translate into chosen code language. if val = obj[prop]?[@options.language] obj[prop] = val else unless _.isString obj[prop] obj[prop] = null + # Translate into chosen spoken language. + if val = obj[prop] + context = @doc.context + obj[prop] = val = utils.i18n obj, prop + if @doc.i18n + spokenLanguage = me.get 'preferredLanguage' + while spokenLanguage + spokenLanguage = spokenLanguage.substr 0, spokenLanguage.lastIndexOf('-') if fallingBack? + if spokenLanguageContext = @doc.i18n[spokenLanguage]?.context + context = spokenLanguageContext + break + fallingBack = true + obj[prop] = _.template val, context if context + formatPopover: -> content = popoverTemplate doc: @doc, language: @options.language, value: @formatValue(), marked: marked, argumentExamples: (arg.example or arg.default or arg.name for arg in @doc.args ? []), writable: @options.writable, selectedMethod: @options.selectedMethod, cooldowns: @inferCooldowns(), item: @options.item owner = if @doc.owner is 'this' then @options.thang else window[@doc.owner] diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index e56111398..d62eaac5a 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -123,7 +123,6 @@ module.exports = class Handler return @sendDatabaseError(res, err) if err documents = (@formatEntity(req, doc) for doc in documents) @sendSuccess(res, documents) - return # If the model uses coco search it's probably a text search else if @modelClass.schema.uses_coco_search diff --git a/server/queues/scoring.coffee b/server/queues/scoring.coffee index fd7dbc984..d3fa48a54 100644 --- a/server/queues/scoring.coffee +++ b/server/queues/scoring.coffee @@ -124,7 +124,7 @@ module.exports.getTwoGames = (req, res) -> #if userIsAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.') humansGameID = req.body.humansGameID ogresGameID = req.body.ogresGameID - ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span', 'dueling-grounds', 'cavern-survival'] + ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span', 'dueling-grounds', 'cavern-survival', 'intuit-tf2014'] levelID = _.sample ladderGameIDs unless ogresGameID and humansGameID #fetch random games here From 5ba1f61abe897fe013b8610c83ecf5637498db22 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Tue, 28 Oct 2014 21:15:41 -0700 Subject: [PATCH 15/16] Added i18n support for level code comments. --- app/models/LevelComponent.coffee | 1 + .../play/level/tome/spell_palette_entry.jade | 2 +- app/treema-ext.coffee | 9 +++++++-- app/views/game-menu/InventoryView.coffee | 7 +++++-- app/views/i18n/I18NEditLevelView.coffee | 20 ++++++++++++++----- app/views/play/level/tome/DocFormatter.coffee | 2 +- app/views/play/level/tome/Spell.coffee | 14 +++++++++++++ 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/app/models/LevelComponent.coffee b/app/models/LevelComponent.coffee index d8a9bc4f1..100ced453 100644 --- a/app/models/LevelComponent.coffee +++ b/app/models/LevelComponent.coffee @@ -12,6 +12,7 @@ module.exports = class LevelComponent extends CocoModel @LandID: '524b7aff7fc0f6d519000006' @CollidesID: '524b7b857fc0f6d519000012' @PlansID: '524b7b517fc0f6d51900000d' + @ProgrammableID: '524b7b5a7fc0f6d51900000e' urlRoot: '/db/level.component' set: (key, val, options) -> diff --git a/app/templates/play/level/tome/spell_palette_entry.jade b/app/templates/play/level/tome/spell_palette_entry.jade index 2d1a94f4d..5dfb2ae4f 100644 --- a/app/templates/play/level/tome/spell_palette_entry.jade +++ b/app/templates/play/level/tome/spell_palette_entry.jade @@ -1 +1 @@ -span.doc-title= doc.title \ No newline at end of file +span.doc-title(data-property-name=doc.name)= doc.title \ No newline at end of file diff --git a/app/treema-ext.coffee b/app/treema-ext.coffee index ac9a141d9..a12630d3b 100644 --- a/app/treema-ext.coffee +++ b/app/treema-ext.coffee @@ -266,7 +266,7 @@ class InternationalizationNode extends TreemaNode.nodeMap.object res = super(arguments...) res = (r for r in res when r[0] isnt '-') res - + populateData: -> super() if Object.keys(@data).length is 0 @@ -286,7 +286,12 @@ class InternationalizationNode extends TreemaNode.nodeMap.object @workingSchema.props = (prop for prop,_ of @parent.schema.properties when prop isnt 'i18n') for i18nProperty in @workingSchema.props - i18nChildSchema.properties[i18nProperty] = @parent.schema.properties[i18nProperty] + parentSchemaProperties = @parent.schema.properties ? {} + for extraSchemas in [@parent.schema.oneOf, @parent.schema.anyOf] + for extraSchema in extraSchemas ? [] + for prop, schema of extraSchema?.properties ? {} + parentSchemaProperties[prop] ?= schema + i18nChildSchema.properties[i18nProperty] = parentSchemaProperties[i18nProperty] return i18nChildSchema #this must be filled out in order for the i18n node to work diff --git a/app/views/game-menu/InventoryView.coffee b/app/views/game-menu/InventoryView.coffee index 0c710deb2..dfc45a4ea 100644 --- a/app/views/game-menu/InventoryView.coffee +++ b/app/views/game-menu/InventoryView.coffee @@ -336,6 +336,7 @@ module.exports = class InventoryView extends CocoView gearByLevel = 'dungeons-of-kithgard': {feet: 'simple-boots'} 'gems-in-the-deep': {feet: 'simple-boots'} + 'forgetful-gemsmith': {feet: 'simple-boots'} 'shadow-guard': {feet: 'simple-boots'} 'true-names': {feet: 'simple-boots', 'right-hand': 'longsword'} 'the-raised-sword': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic'} @@ -345,9 +346,11 @@ module.exports = class InventoryView extends CocoView 'lowly-kithmen': {feet: 'simple-boots', 'right-hand': 'longsword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} 'closing-the-distance': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic', eyes: 'crude-glasses'} 'the-final-kithmaze': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} - 'kithgard-gates': {feet: 'simple-boots', 'right-hand': 'builders-hammer'} + 'kithgard-gates': {feet: 'simple-boots', 'right-hand': 'builders-hammer', torso: 'leather-tunic'} 'defense-of-plainswood': {feet: 'simple-boots', 'right-hand': 'builders-hammer'} - # TODO: figure out leather boots for plainswood (or next one?) + 'winding-trail': {feet: 'leather-boots', 'right-hand': 'builders-hammer'} + 'thornbush-farm': {feet: 'leather-boots', 'right-hand': 'builders-hammer', eyes: 'crude-glasses'} + 'a-fiery-trap': {feet: 'leather-boots', 'right-hand': 'builders-hammer', eyes: 'crude-glasses'} return unless necessaryGear = gearByLevel[@options.levelID] if @inserted if @supermodel.finished() diff --git a/app/views/i18n/I18NEditLevelView.coffee b/app/views/i18n/I18NEditLevelView.coffee index 20dc275a4..475aef524 100644 --- a/app/views/i18n/I18NEditLevelView.coffee +++ b/app/views/i18n/I18NEditLevelView.coffee @@ -1,5 +1,6 @@ I18NEditModelView = require './I18NEditModelView' Level = require 'models/Level' +LevelComponent = require 'models/LevelComponent' module.exports = class I18NEditLevelView extends I18NEditModelView id: "i18n-edit-level-view" @@ -25,24 +26,33 @@ module.exports = class I18NEditLevelView extends I18NEditModelView if i18n = doc.i18n @wrapRow "Guide article name", ['name'], doc.name, i18n[lang]?.name, ['documentation', 'specificArticles', index] @wrapRow "'#{doc.name}' description", ['description'], doc.description, i18n[lang]?.description, ['documentation', 'specificArticles', index], 'markdown' - + # sprite dialogues for script, scriptIndex in @model.get('scripts') ? [] for noteGroup, noteGroupIndex in script.noteChain ? [] for spriteCommand, spriteCommandIndex in noteGroup.sprites ? [] pathPrefix = ['scripts', scriptIndex, 'noteChain', noteGroupIndex, 'sprites', spriteCommandIndex, 'say'] - + if i18n = spriteCommand.say?.i18n if spriteCommand.say.text @wrapRow "Sprite text", ['text'], spriteCommand.say.text, i18n[lang]?.text, pathPrefix, 'markdown' if spriteCommand.say.blurb @wrapRow "Sprite blurb", ['blurb'], spriteCommand.say.blurb, i18n[lang]?.blurb, pathPrefix - + for response, responseIndex in spriteCommand.say?.responses ? [] if i18n = response.i18n @wrapRow "Response button", ['text'], response.text, i18n[lang]?.text, pathPrefix.concat(['responses', responseIndex]) - + # victory modal if i18n = @model.get('victory')?.i18n @wrapRow "Victory text", ['body'], @model.get('victory').body, i18n[lang]?.body, ['victory'], 'markdown' - \ No newline at end of file + + # code comments + for thang, thangIndex in @model.get('thangs') ? [] + for component, componentIndex in thang.components ? [] + continue unless component.original is LevelComponent.ProgrammableID + for methodName, method of component.config?.programmableMethods ? {} + if (i18n = method.i18n) and (context = method.context) + for key, value of context + path = ['thangs', thangIndex, 'components', componentIndex, 'config', 'programmableMethods', methodName] + @wrapRow "Code comment", ["context", key], value, i18n[lang]?.context[key], path diff --git a/app/views/play/level/tome/DocFormatter.coffee b/app/views/play/level/tome/DocFormatter.coffee index 259998c75..147702eef 100644 --- a/app/views/play/level/tome/DocFormatter.coffee +++ b/app/views/play/level/tome/DocFormatter.coffee @@ -105,7 +105,7 @@ module.exports = class DocFormatter while spokenLanguage spokenLanguage = spokenLanguage.substr 0, spokenLanguage.lastIndexOf('-') if fallingBack? if spokenLanguageContext = @doc.i18n[spokenLanguage]?.context - context = spokenLanguageContext + context = _.merge context, spokenLanguageContext break fallingBack = true obj[prop] = _.template val, context if context diff --git a/app/views/play/level/tome/Spell.coffee b/app/views/play/level/tome/Spell.coffee index 0c0e50e8d..847f79681 100644 --- a/app/views/play/level/tome/Spell.coffee +++ b/app/views/play/level/tome/Spell.coffee @@ -22,6 +22,8 @@ module.exports = class Spell @levelType = options.level.get('type', true) p = options.programmableMethod + @commentI18N = p.i18n + @commentContext = p.context @languages = p.languages ? {} @languages.javascript ?= p.source @name = p.name @@ -61,6 +63,18 @@ module.exports = class Spell setLanguage: (@language) -> #console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript @originalSource = @languages[language] ? @languages.javascript + # Translate comments chosen spoken language. + return unless @commentContext + context = $.extend true, {}, @commentContext + if @commentI18N + spokenLanguage = me.get 'preferredLanguage' + while spokenLanguage + spokenLanguage = spokenLanguage.substr 0, spokenLanguage.lastIndexOf('-') if fallingBack? + if spokenLanguageContext = @commentI18N[spokenLanguage]?.context + context = _.merge context, spokenLanguageContext + break + fallingBack = true + @originalSource = _.template @originalSource, context addThang: (thang) -> if @thangs[thang.id] From def4108913ac7d6822ac7bb48ab0656944b3fb3b Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Tue, 28 Oct 2014 21:17:54 -0700 Subject: [PATCH 16/16] Fixed a test to expect PUT instead of PATCH. --- test/app/lib/FacebookHandler.spec.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/app/lib/FacebookHandler.spec.coffee b/test/app/lib/FacebookHandler.spec.coffee index f808ea1e5..5084800c1 100644 --- a/test/app/lib/FacebookHandler.spec.coffee +++ b/test/app/lib/FacebookHandler.spec.coffee @@ -76,5 +76,6 @@ describe 'lib/FacebookHandler.coffee', -> expect(params.gender).toBe(mockMe.gender) expect(params.email).toBe(mockMe.email) expect(params.facebookID).toBe(mockMe.id) - expect(request.method).toBe('PATCH') + #expect(request.method).toBe('PATCH') # PATCHes are PUTs until more proxy servers recognize PATCH + expect(request.method).toBe('PUT') expect(_.string.startsWith(request.url, '/db/user/12345')).toBeTruthy()