Skip to content

Commit

Permalink
feat: add a translation tool
Browse files Browse the repository at this point in the history
Hopefully makes it (slightly) easier to translate all Hedy content.

Visit:

```
http://<hedy>/translate/en/nl
```

(Or whatever pair of languages you want to translate)

This PR uses content from the "adventure-mode" branch (without actually
having the code that uses the data yet).

Change Python YAML dependency from **PyYAML** to **ruamel.yaml** because
it has better support for leaving the original file mostly in place.

I am not proud of the code but it works and I wanted to get it out
quickly.
  • Loading branch information
rix0rrr committed May 1, 2021
1 parent 6a16480 commit 860f4da
Show file tree
Hide file tree
Showing 15 changed files with 1,977 additions and 14 deletions.
60 changes: 57 additions & 3 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@
import re
import requests
import uuid
import yaml
from ruamel import yaml
from flask_commonmark import Commonmark
from werkzeug.urls import url_encode
from config import config
from auth import auth_templates, current_user, requires_login, is_admin, is_teacher
from utils import db_get, db_get_many, db_set, timems, type_check, object_check, db_del
from utils import db_get, db_get_many, db_set, timems, type_check, object_check, db_del, load_yaml, load_yaml_rt, dump_yaml_rt
import hashlib

# app.py
from flask import Flask, request, jsonify, render_template, session, abort, g, redirect, make_response
from flask import Flask, request, jsonify, render_template, session, abort, g, redirect, make_response, Response
from flask_compress import Compress

# Hedy-specific modules
import courses
import hedyweb
import translating

# Define and load all available language data
ALL_LANGUAGES = {
Expand Down Expand Up @@ -580,6 +581,59 @@ def save_program (user):

return jsonify({})

@app.route('/translate/<source>/<target>')
def translate_fromto(source, target):
# FIXME: right now loading source file on demand. We might need to cache this...
source_adventures = load_yaml(f'coursedata/adventures/{source}.yaml')
source_levels = load_yaml(f'coursedata/level-defaults/{source}.yaml')
source_texts = load_yaml(f'coursedata/texts/{source}.yaml')

target_adventures = load_yaml(f'coursedata/adventures/{target}.yaml')
target_levels = load_yaml(f'coursedata/level-defaults/{target}.yaml')
target_texts = load_yaml(f'coursedata/texts/{target}.yaml')

files = []

files.append(translating.TranslatableFile(
'Adventures',
f'adventures/{target}.yaml',
translating.struct_to_sections(source_adventures, target_adventures)))

files.append(translating.TranslatableFile(
'Levels',
f'level-defaults/{target}.yaml',
translating.struct_to_sections(source_levels, target_levels)))

files.append(translating.TranslatableFile(
'Messages',
f'texts/{target}.yaml',
translating.struct_to_sections(source_texts, target_texts)))

return render_template('translate-fromto.html',
source_lang=source,
target_lang=target,
files=files)

@app.route('/update_yaml', methods=['POST'])
def update_yaml():
filename = path.join('coursedata', request.form['file'])
# The file MUST point to something inside our 'coursedata' directory
# (no exploiting bullshit here)
filepath = path.abspath(filename)
expected_path = path.abspath('coursedata')
if not filepath.startswith(expected_path):
raise RuntimeError('Are you trying to trick me?')

data = load_yaml_rt(filepath)
for key, value in request.form.items():
if key.startswith('c:'):
translating.apply_form_change(data, key[2:], value)

return Response(dump_yaml_rt(data),
mimetype='application/x-yaml',
headers={'Content-disposition': 'attachment; filename=' + path.basename(filename)})


# *** AUTH ***

import auth
Expand Down
57 changes: 57 additions & 0 deletions coursedata/adventures/adventures.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"title": "JSON Schema for Hedy Adventures",
"type": "object",
"additionalProperties": false,
"properties": {
"title": {
"type": "string",
"description": "Short title of the adventure"
},
"subtitle": {
"type": "string",
"description": "Slightly longer introductory description of the adventure"
},
"adventures": {
"type": "object",
"description": "Individual adventures, key/value map",
"additionalProperties": { "$ref": "#/definitions/Adventure" }
}
},
"definitions": {
"Adventure": {
"type": "object",
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"image": { "type": "string" },
"levels": {
"type": "object",
"additionalProperties": { "$ref": "#/definitions/Story" }
}
},
"required": ["name", "description", "levels"],
"additionalProperties": false
},
"Story": {
"type": "object",
"properties": {
"intro_text": { "type": "string" },
"story_text": { "type": "string" },
"start_code": { "type": "string" }
},
"required": ["start_code"],
"additionalProperties": false
},
"StoryCommand": {
"type": "object",
"properties": {
"name": { "type": "string" },
"example": { "type": "string" },
"explanation": { "type": "string" },
"demo_code": { "type": "string" }
},
"required": ["demo_code"],
"additionalProperties": false
}
}
}
Loading

0 comments on commit 860f4da

Please sign in to comment.