Commit 98c2aa1e by Tim Krones

Merge pull request #10 from asadiqbal08/asadiqbal08/WL-347

WL-347 Translations Poll and Survey XBlock
parents bd93e485 a41edff6
......@@ -5,7 +5,7 @@ before_install:
- "export DISPLAY=:99"
- "sh -e /etc/init.d/xvfb start"
install:
- "pip install -e git://github.com/edx/xblock-sdk.git#egg=xblock-sdk"
- "pip install -e git://github.com/edx/xblock-sdk.git@v0.1.2#egg=xblock-sdk==v0.1.2"
- "cd $VIRTUAL_ENV/src/xblock-sdk/"
- "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/base.txt"
- "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/test.txt"
......
......@@ -237,3 +237,43 @@ If you want to grant members of other groups ability to view the results, you ca
names in the django settings using the `XBLOCK_POLL_EXTRA_VIEW_GROUPS` setting, for example:
XBLOCK_POLL_EXTRA_VIEW_GROUPS = ['poll_staff']
## Working with Translations
The following sections provide basic information about common use cases for working with translations. You can find additional information in [this section](http://edx.readthedocs.io/projects/xblock-tutorial/en/latest/edx_platform/edx_lms.html#adding-translated-strings-to-your-xblock) of the [Open edX XBlock Tutorial](http://edx.readthedocs.io/projects/xblock-tutorial/en/latest/index.html).
### Viewing the XBlock in a Different Language
To view this XBlock in a different language you first need to compile the `text.po` file for the language to a file called `text.mo`. Translation files for a given language are located in the `poll/translations/<locale>/LC_MESSAGES` directory. From the top-level directory of this repository, use the following commands to access the directory and compile the translation file (replacing `<lang_code>` with the appropriate language code):
```sh
cd poll/translations/<lang_code>/LC_MESSAGES/
msgfmt text.po -o text.mo
```
You can then navigate to a poll or survey in the LMS and view a translated version of it by adding `/?preview-lang=<lang_code>` to the URL. Note that user-editable content (such as questions and answers belonging to a poll) is not translated by default. To revert to the default language, add `/?clear-lang` to the URL.
### Adding Translations for Other Languages
To add translations for other languages, create a set of language directories for each of your locales within the `poll/translations` directory. You may specify either a general language code or a specific language locale code for the name of each directory. Include an `LC_MESSAGES` directory with each language directory. For example:
* `../poll/translations/ar/LC_MESSAGES/`
* `../poll/translations/es-es/LC_MESSAGES/`
For each language, create a domain file named `text.po` and compile it: You can use edX's [i18n-tools](https://github.com/edx/i18n-tools) to extract translations for multiple languages as follows:
```sh
i18n_tool extract
```
Open each `text.po` domain file and, for each `msgid` string, add a corresponding `msgstr` translation. You can then use edX's [i18n-tools](https://github.com/edx/i18n-tools) to compile the translation files as follows:
```sh
i18n_tool generate
```
Before running the `i18n_tool extract` or `i18n_tool generate` commands, appropriate settings for xblock-poll need to be added to `edx-platform/conf/locale/config.yaml`.
You should have to mention `poll` as a third_party app and `poll.po` in `django.po` list under `generate_merge` section.
### Updating Existing Translation Files
After changing existing strings in the source code (or adding new ones), you'll need to update the translation files. To do this, extract and compile the translations as described in the previous section. (There is no need to create additional directories if you are not adding translations for additional languages.)
......@@ -36,6 +36,7 @@ from xblock.fragment import Fragment
from xblockutils.publish_event import PublishEventMixin
from xblockutils.resources import ResourceLoader
from xblockutils.settings import XBlockWithSettingsMixin, ThemableXBlockMixin
from .utils import _
try:
# pylint: disable=import-error
......@@ -83,17 +84,18 @@ class ResourceMixin(XBlockWithSettingsMixin, ThemableXBlockMixin):
@XBlock.wants('settings')
@XBlock.needs('i18n')
class PollBase(XBlock, ResourceMixin, PublishEventMixin):
"""
Base class for Poll-like XBlocks.
"""
event_namespace = 'xblock.pollbase'
private_results = Boolean(default=False, help="Whether or not to display results to the user.")
max_submissions = Integer(default=1, help="The maximum number of times a user may send a submission.")
private_results = Boolean(default=False, help=_("Whether or not to display results to the user."))
max_submissions = Integer(default=1, help=_("The maximum number of times a user may send a submission."))
submissions_count = Integer(
default=0, help="Number of times the user has sent a submission.", scope=Scope.user_state
default=0, help=_("Number of times the user has sent a submission."), scope=Scope.user_state
)
feedback = String(default='', help="Text to display after the user votes.")
feedback = String(default='', help=_("Text to display after the user votes."))
def send_vote_event(self, choice_data):
# Let the LMS know the user has answered the poll.
......@@ -139,8 +141,11 @@ class PollBase(XBlock, ResourceMixin, PublishEventMixin):
if field not in data or not isinstance(data[field], list):
source_items = []
result['success'] = False
result['errors'].append(
"'{0}' is not present, or not a JSON array.".format(field))
error_message = self.ugettext(
# Translators: {field} is either "answers" or "questions".
"'{field}' is not present, or not a JSON array."
).format(field=field)
result['errors'].append(error_message)
else:
source_items = data[field]
......@@ -148,45 +153,63 @@ class PollBase(XBlock, ResourceMixin, PublishEventMixin):
for item in source_items:
if not isinstance(item, dict):
result['success'] = False
result['errors'].append(
"{0} {1} not a javascript object!".format(noun, item))
error_message = self.ugettext(
# Translators: {noun} is either "Answer" or "Question". {item} identifies the answer or question.
"{noun} {item} not a javascript object!"
).format(noun=noun, item=item)
result['errors'].append(error_message)
continue
key = item.get('key', '').strip()
if not key:
result['success'] = False
result['errors'].append(
"{0} {1} contains no key.".format(noun, item))
error_message = self.ugettext(
# Translators: {noun} is either "Answer" or "Question". {item} identifies the answer or question.
"{noun} {item} contains no key."
).format(noun=noun, item=item)
result['errors'].append(error_message)
image_link = item.get('img', '').strip()
image_alt = item.get('img_alt', '').strip()
label = item.get('label', '').strip()
if not label:
if image and not image_link:
result['success'] = False
result['errors'].append(
"{0} has no text or img. Please make sure all {1}s "
"have one or the other, or both.".format(noun, noun.lower()))
error_message = self.ugettext(
# Translators: {noun} is either "Answer" or "Question".
# {noun_lower} is the lowercase version of {noun}.
"{noun} has no text or img. Please make sure all {noun_lower}s have one or the other, or both."
).format(noun=noun, noun_lower=noun.lower())
result['errors'].append(error_message)
elif not image:
result['success'] = False
# If there's a bug in the code or the user just forgot to relabel a question,
# votes could be accidentally lost if we assume the omission was an
# intended deletion.
result['errors'].append("{0} was added with no label. "
"All {1}s must have labels. Please check the form. "
"Check the form and explicitly delete {1}s "
"if not needed.".format(noun, noun.lower()))
error_message = self.ugettext(
# Translators: {noun} is either "Answer" or "Question".
# {noun_lower} is the lowercase version of {noun}.
"{noun} was added with no label. All {noun_lower}s must have labels. Please check the form. "
"Check the form and explicitly delete {noun_lower}s if not needed."
).format(noun=noun, noun_lower=noun.lower())
result['errors'].append(error_message)
if image_link and not image_alt and self.img_alt_mandatory():
result['success'] = False
result['errors'].append(
"All images must have an alternative text describing the image in a way that "
"would allow someone to answer the poll if the image did not load.")
self.ugettext(
"All images must have an alternative text describing the image in a way "
"that would allow someone to answer the poll if the image did not load."
)
)
if image:
items.append((key, {'label': label, 'img': image_link, 'img_alt': image_alt}))
else:
items.append([key, label])
if not items:
result['errors'].append(
"You must include at least one {0}.".format(noun.lower()))
error_message = self.ugettext(
# Translators: "{noun_lower} is either "answer" or "question".
"You must include at least one {noun_lower}."
).format(noun_lower=noun.lower())
result['errors'].append(error_message)
result['success'] = False
return items
......@@ -221,7 +244,7 @@ class PollBase(XBlock, ResourceMixin, PublishEventMixin):
return GroupProfile.objects.filter(group_id__in=group_ids, name__in=group_names).exists()
@staticmethod
def get_max_submissions(data, result, private_results):
def get_max_submissions(ugettext, data, result, private_results):
"""
Gets the value of 'max_submissions' from studio submitted AJAX data, and checks for conflicts
with private_results, which may not be False when max_submissions is not 1, since that would mean
......@@ -232,12 +255,12 @@ class PollBase(XBlock, ResourceMixin, PublishEventMixin):
except (ValueError, KeyError):
max_submissions = 1
result['success'] = False
result['errors'].append('Maximum Submissions missing or not an integer.')
result['errors'].append(ugettext('Maximum Submissions missing or not an integer.'))
# Better to send an error than to confuse the user by thinking this would work.
if (max_submissions != 1) and not private_results:
result['success'] = False
result['errors'].append("Private results may not be False when Maximum Submissions is not 1.")
result['errors'].append(ugettext("Private results may not be False when Maximum Submissions is not 1."))
return max_submissions
@classmethod
......@@ -272,23 +295,23 @@ class PollBlock(PollBase):
"""
# pylint: disable=too-many-instance-attributes
display_name = String(default='Poll')
question = String(default='What is your favorite color?')
display_name = String(default=_('Poll'))
question = String(default=_('What is your favorite color?'))
# This will be converted into an OrderedDict.
# Key, (Label, Image path)
answers = List(
default=[
('R', {'label': 'Red', 'img': None, 'img_alt': None}),
('B', {'label': 'Blue', 'img': None, 'img_alt': None}),
('G', {'label': 'Green', 'img': None, 'img_alt': None}),
('O', {'label': 'Other', 'img': None, 'img_alt': None}),
('R', {'label': _('Red'), 'img': None, 'img_alt': None}),
('B', {'label': _('Blue'), 'img': None, 'img_alt': None}),
('G', {'label': _('Green'), 'img': None, 'img_alt': None}),
('O', {'label': _('Other'), 'img': None, 'img_alt': None}),
],
scope=Scope.settings, help="The answer options on this poll."
scope=Scope.settings, help=_("The answer options on this poll.")
)
tally = Dict(default={'R': 0, 'B': 0, 'G': 0, 'O': 0},
scope=Scope.user_state_summary,
help="Total tally of answers from students.")
choice = String(scope=Scope.user_state, help="The student's answer")
help=_("Total tally of answers from students."))
choice = String(scope=Scope.user_state, help=_("The student's answer"))
event_namespace = 'xblock.poll'
def clean_tally(self):
......@@ -452,18 +475,22 @@ class PollBlock(PollBase):
result = {'success': False, 'errors': []}
old_choice = self.get_choice()
if (old_choice is not None) and not self.private_results:
result['errors'].append('You have already voted in this poll.')
result['errors'].append(self.ugettext('You have already voted in this poll.'))
return result
try:
choice = data['choice']
except KeyError:
result['errors'].append('Answer not included with request.')
result['errors'].append(self.ugettext('Answer not included with request.'))
return result
# Just to show data coming in...
try:
OrderedDict(self.answers)[choice]
except KeyError:
result['errors'].append('No key "{choice}" in answers table.'.format(choice=choice))
result['errors'].append(
self.ugettext(
# Translators: {choice} uniquely identifies a specific answer belonging to a poll or survey.
'No key "{choice}" in answers table.'
).format(choice=choice))
return result
if old_choice is None:
......@@ -471,7 +498,7 @@ class PollBlock(PollBase):
self.submissions_count = 0
if not self.can_vote():
result['errors'].append('You have already voted as many times as you are allowed.')
result['errors'].append(self.ugettext('You have already voted as many times as you are allowed.'))
return result
self.clean_tally()
......@@ -497,14 +524,14 @@ class PollBlock(PollBase):
feedback = data.get('feedback', '').strip()
private_results = bool(data.get('private_results', False))
max_submissions = self.get_max_submissions(data, result, private_results)
max_submissions = self.get_max_submissions(self.ugettext, data, result, private_results)
display_name = data.get('display_name', '').strip()
if not question:
result['errors'].append("You must specify a question.")
result['errors'].append(self.ugettext("You must specify a question."))
result['success'] = False
answers = self.gather_items(data, result, 'Answer', 'answers')
answers = self.gather_items(data, result, self.ugettext('Answer'), 'answers')
if not result['success']:
return result
......@@ -554,32 +581,36 @@ class PollBlock(PollBase):
class SurveyBlock(PollBase):
# pylint: disable=too-many-instance-attributes
display_name = String(default='Survey')
display_name = String(default=_('Survey'))
# The display name affects how the block is labeled in the studio,
# but either way we want it to say 'Poll' by default on the page.
block_name = String(default='Poll')
block_name = String(default=_('Poll'))
answers = List(
default=(
('Y', 'Yes'), ('N', 'No'),
('M', 'Maybe')),
scope=Scope.settings, help="Answer choices for this Survey"
('Y', _('Yes')), ('N', _('No')),
('M', _('Maybe'))),
scope=Scope.settings, help=_("Answer choices for this Survey")
)
questions = List(
default=[
('enjoy', {'label': 'Are you enjoying the course?', 'img': None, 'img_alt': None}),
('recommend', {'label': 'Would you recommend this course to your friends?', 'img': None, 'img_alt': None}),
('learn', {'label': 'Do you think you will learn a lot?', 'img': None, 'img_alt': None}),
('enjoy', {'label': _('Are you enjoying the course?'), 'img': None, 'img_alt': None}),
('recommend', {
'label': _('Would you recommend this course to your friends?'),
'img': None,
'img_alt': None
}),
('learn', {'label': _('Do you think you will learn a lot?'), 'img': None, 'img_alt': None}),
],
scope=Scope.settings, help="Questions for this Survey"
scope=Scope.settings, help=_("Questions for this Survey")
)
tally = Dict(
default={
'enjoy': {'Y': 0, 'N': 0, 'M': 0}, 'recommend': {'Y': 0, 'N': 0, 'M': 0},
'learn': {'Y': 0, 'N': 0, 'M': 0}},
scope=Scope.user_state_summary,
help="Total tally of answers from students."
help=_("Total tally of answers from students.")
)
choices = Dict(help="The user's answers", scope=Scope.user_state)
choices = Dict(help=_("The user's answers"), scope=Scope.user_state)
event_namespace = 'xblock.survey'
def student_view(self, context=None):
......@@ -811,7 +842,7 @@ class SurveyBlock(PollBase):
choices = self.get_choices()
if choices and not self.private_results:
result['success'] = False
result['errors'].append("You have already voted in this poll.")
result['errors'].append(self.ugettext("You have already voted in this poll."))
if not choices:
# Reset submissions count if choices are bogus.
......@@ -819,15 +850,17 @@ class SurveyBlock(PollBase):
if not self.can_vote():
result['success'] = False
result['errors'].append('You have already voted as many times as you are allowed.')
result['errors'].append(self.ugettext('You have already voted as many times as you are allowed.'))
# Make sure the user has included all questions, and hasn't included
# anything extra, which might indicate the questions have changed.
if not sorted(data.keys()) == sorted(questions.keys()):
result['success'] = False
result['errors'].append(
"Not all questions were included, or unknown questions were "
"included. Try refreshing and trying again."
self.ugettext(
"Not all questions were included, or unknown questions were included. "
"Try refreshing and trying again."
)
)
# Make sure the answer values are sane.
......@@ -835,7 +868,11 @@ class SurveyBlock(PollBase):
if value not in answers.keys():
result['success'] = False
result['errors'].append(
"Found unknown answer '%s' for question key '%s'" % (key, value))
self.ugettext(
# Translators: {answer_key} uniquely identifies a specific answer belonging to a poll or survey.
# {question_key} uniquely identifies a specific question belonging to a poll or survey.
"Found unknown answer '{answer_key}' for question key '{question_key}'"
).format(answer_key=key, question_key=value))
if not result['success']:
result['can_vote'] = self.can_vote()
......@@ -865,10 +902,10 @@ class SurveyBlock(PollBase):
feedback = data.get('feedback', '').strip()
block_name = data.get('display_name', '').strip()
private_results = bool(data.get('private_results', False))
max_submissions = self.get_max_submissions(data, result, private_results)
max_submissions = self.get_max_submissions(self.ugettext, data, result, private_results)
answers = self.gather_items(data, result, 'Answer', 'answers', image=False)
questions = self.gather_items(data, result, 'Question', 'questions')
answers = self.gather_items(data, result, self.ugettext('Answer'), 'answers', image=False)
questions = self.gather_items(data, result, self.ugettext('Question'), 'questions')
if not result['success']:
return result
......
......@@ -27,10 +27,12 @@
{{/each}}
</ul>
<input class="input-main" type="button" name="poll-submit" value="Submit" disabled>
<div class="poll-footnote">Results gathered from {{total}} respondent{{#if plural}}s{{/if}}.</div>
<div class="poll-footnote">
{{interpolate (i18n_ngettext "Results gathered from {total} respondent." "Results gathered from {total} respondents." total) total=total}}
</div>
{{#if feedback}}
<hr />
<h3 class="poll-header">Feedback</h3>
<h3 class="poll-header">{{i18n "Feedback" }}</h3>
<div class="poll-feedback">
{{{feedback}}}
</div>
......
......@@ -3,26 +3,25 @@
<li class="field comp-setting-entry is-set poll-{{noun}}-studio-item">
<div class="wrapper-comp-setting">
<div class="poll-move">
<button class="poll-move-up">&#9650;<span class="sr">&nbsp;move poll up</span></button>
<button class="poll-move-down">&#9660;<span class="sr">&nbsp;move poll down</span></button>
<button class="poll-move-up">&#9650;<span class="sr">&nbsp;{{i18n "move poll up"}}</span></button>
<button class="poll-move-down">&#9660;<span class="sr">&nbsp;{{i18n "move poll down"}}</span></button>
</div>
<button class="button action-button poll-delete-answer">Delete</button>
<button class="button action-button poll-delete-answer">{{i18n "Delete" }}</button>
<label class="label setting-label poll-setting-label" for="{{noun}}-label-{{key}}">{{noun}}</label>
<input class="input setting-input" name="{{noun}}-label-{{key}}" id="{{noun}}-label-{{key}}" value="{{text}}" type="text" /><br />
{{#if image}}
<label class="label setting-label" for="{{noun}}-img-{{key}}">Image URL</label>
<label class="label setting-label" for="{{noun}}-img-{{key}}">{{i18n "Image URL"}}</label>
<input class="input setting-input" name="{{noun}}-img-{{key}}" id="{{noun}}-img-{{key}}" value="{{img}}" type="text" /><br />
<label class="label setting-label" for="{{noun}}-img_alt-{{key}}">Image alternative text</label>
<label class="label setting-label" for="{{noun}}-img_alt-{{key}}">{{i18n "Image alternative text"}}</label>
<input class="input setting-input" name="{{noun}}-img_alt-{{key}}" id="{{noun}}-img_alt-{{key}}" value="{{img_alt}}" type="text" /><br />
{{/if}}
</div>
<span class="tip setting-help">
You can make limited use of Markdown in answer texts, preferably only bold and italics.
{{i18n "You can make limited use of Markdown in answer texts, preferably only bold and italics."}}
</span>
<span class="tip setting-help">
{{#if image}}
This must have an image URL or text, and can have both. If you add an image, you must also provide an alternative text
that describes the image in a way that would allow someone to answer the poll if the image did not load.
{{i18n "This must have an image URL or text, and can have both. If you add an image, you must also provide an alternative text that describes the image in a way that would allow someone to answer the poll if the image did not load."}}
{{/if}}
</span>
</li>
......
......@@ -26,10 +26,13 @@
{{/each}}
</table>
<input class="input-main" type="button" name="poll-submit" value="Submit" disabled>
<div class="poll-footnote">Results gathered from {{total}} respondent{{#if plural}}s{{/if}}.</div>
<div class="poll-footnote">
{{interpolate (i18n_ngettext "Results gathered from {total} respondent." "Results gathered from {total} respondents." total) total=total}}
</div>
{{#if feedback}}
<hr />
<h3 class="poll-header">Feedback</h3>
<h3 class="poll-header">{{i18n "Feedback" }}</h3>
<div class="poll-feedback">
{{{feedback}}}
</div>
......
......@@ -10,7 +10,35 @@ function PollUtil (runtime, element, pollType) {
this.votedUrl = runtime.handlerUrl(element, 'student_voted');
this.submit = $('input[type=button]', element);
this.answers = $('input[type=radio]', element);
// Set up gettext in case it isn't available in the client runtime:
if (typeof gettext == "undefined") {
window.gettext = function gettext_stub(string) { return string; };
window.ngettext = function ngettext_stub(strA, strB, n) { return n == 1 ? strA : strB; };
}
// Make gettext available in Handlebars templates
Handlebars.registerHelper('i18n', function(str) {
return gettext(str);
});
// Make ngettext available in Handlebars templates
Handlebars.registerHelper('i18n_ngettext', function(singular, plural, count) {
return ngettext(singular, plural, count);
});
// Add helper for interpolating values into strings in Handlebars templates
Handlebars.registerHelper('interpolate', function(formatString, parameters) {
parameters = parameters.hash;
return formatString.replace(/{\w+}/g,
function(parameter) {
var parameterName = parameter.slice(1, -1);
return String(parameters[parameterName]);
});
});
this.resultsTemplate = Handlebars.compile($("#" + pollType + "-results-template", element).html());
this.viewResultsButton = $('.view-results-button', element);
this.viewResultsButton.click(this.getResults);
......@@ -131,7 +159,7 @@ function PollUtil (runtime, element, pollType) {
thanks.fadeOut(0).fadeIn('slow', 'swing');
$('.poll-feedback-container', element).removeClass('poll-hidden');
if (can_vote) {
$('input[name="poll-submit"]', element).val('Resubmit');
$('input[name="poll-submit"]', element).val(gettext('Resubmit'));
} else {
$('input', element).attr('disabled', true)
}
......
......@@ -13,6 +13,31 @@ function PollEditUtil(runtime, element, pollType) {
this.init = function () {
// Set up the editing form for a Poll or Survey.
var temp = $('#poll-form-component', element).html();
// Set up gettext in case it isn't available in the client runtime:
if (typeof gettext == "undefined") {
window.gettext = function gettext_stub(string) { return string; };
window.ngettext = function ngettext_stub(strA, strB, n) { return n == 1 ? strA : strB; };
}
// Make gettext available in Handlebars templates
Handlebars.registerHelper('i18n', function(str) { return gettext(str); });
// Make ngettext available in Handlebars templates
Handlebars.registerHelper('i18n_ngettext', function(singular, plural, count) {
return ngettext(singular, plural, count);
});
// Add helper for interpolating values into strings in Handlebars templates
Handlebars.registerHelper('interpolate', function(formatString, parameters) {
parameters = parameters.hash;
return formatString.replace(/{\w+}/g,
function(parameter) {
var parameterName = parameter.slice(1, -1);
return String(parameters[parameterName]);
});
});
self.answerTemplate = Handlebars.compile(temp);
$(element).find('.cancel-button', element).bind('click', function() {
......@@ -231,7 +256,7 @@ function PollEditUtil(runtime, element, pollType) {
data['private_results'] = eval($('#poll-private-results', element).val());
if (notify) {
runtime.notify('save', {state: 'start', message: "Saving"});
runtime.notify('save', {state: 'start', message: gettext("Saving")});
}
$.ajax({
type: "POST",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2016-04-07 16:17+0500\n"
"PO-Revision-Date: 2016-04-07 16:17+0500\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.7\n"
"X-Poedit-Basepath: ../../..\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SearchPath-0: poll.py\n"
#: poll.py
msgid "Whether or not to display results to the user."
msgstr ""
#: poll.py
msgid "The maximum number of times a user may send a submission."
msgstr ""
#: poll.py
msgid "Number of times the user has sent a submission."
msgstr ""
#: poll.py
msgid "Text to display after the user votes."
msgstr ""
#: poll.py
#, python-brace-format
msgid "'{field}' is not present, or not a JSON array."
msgstr ""
#: poll.py
#, python-brace-format
msgid "{noun} {item} not a javascript object!"
msgstr ""
#: poll.py
#, python-brace-format
msgid "{noun} {item} contains no key."
msgstr ""
#: poll.py
#, python-brace-format
msgid ""
"{noun} has no text or img. Please make sure all {noun_lower}s have one or the other, or "
"both."
msgstr ""
#: poll.py
#, python-brace-format
msgid ""
"{noun} was added with no label. All {noun_lower}s must have labels. Please check the "
"form. Check the form and explicitly delete {noun_lower}s if not needed."
msgstr ""
#: poll.py
msgid ""
"All images must have an alternative text describing the image in a way that "
"would allow someone to answer the poll if the image did not load."
msgstr ""
#: poll.py
#, python-brace-format
msgid "You must include at least one {noun_lower}."
msgstr ""
#: poll.py
msgid "Maximum Submissions missing or not an integer."
msgstr ""
#: poll.py
msgid "Private results may not be False when Maximum Submissions is not 1."
msgstr ""
#: poll.py
msgid "Total tally of answers from students."
msgstr ""
#: poll.py
msgid "The student's answer"
msgstr ""
#: poll.py
msgid "You have already voted in this poll."
msgstr ""
#: poll.py
msgid "Answer not included with request."
msgstr ""
#: poll.py
#, python-brace-format
msgid "No key \"{choice}\" in answers table."
msgstr ""
#: poll.py
msgid "You have already voted as many times as you are allowed."
msgstr ""
#: poll.py
msgid "You must specify a question."
msgstr ""
#: poll.py
msgid "Answer choices for this Survey"
msgstr ""
#: poll.py
msgid "Are you enjoying the course?"
msgstr ""
#: poll.py
msgid "Would you recommend this course to your friends?"
msgstr ""
#: poll.py
msgid "Do you think you will learn a lot?"
msgstr ""
#: poll.py
msgid "Questions for this Survey"
msgstr ""
#: poll.py
msgid "Survey"
msgstr ""
#: poll.py
msgid "The user's answers"
msgstr ""
#: poll.py
msgid ""
"Not all questions were included, or unknown questions were included. Try "
"refreshing and trying again."
msgstr ""
#: poll.py
#, python-format
msgid "Found unknown answer '{answer_key}' for question key '{question_key}'"
msgstr ""
#: poll.py
msgid "Poll"
msgstr ""
#: poll.py
msgid "What is your favorite color?"
msgstr ""
#: poll.py
msgid "Red"
msgstr ""
#: poll.py
msgid "Blue"
msgstr ""
#: poll.py
msgid "Other"
msgstr ""
#: poll.py
msgid "The answer options on this poll."
msgstr ""
#: poll.py
msgid "Answer"
msgstr ""
#: poll.py
msgid "Question"
msgstr ""
#: poll.py
msgid "Yes"
msgstr ""
#: poll.py
msgid "No"
msgstr ""
#: poll.py
msgid "Maybe"
msgstr ""
#: public/handlebars/poll_results.handlebars
msgid "Results gathered from {total} respondents."
msgstr ""
#: public/handlebars/poll_results.handlebars
msgid "Results gathered from {total} respondent."
msgstr ""
#: public/handlebars/poll_studio.handlebars
msgid "move poll up"
msgstr ""
#: public/handlebars/poll_studio.handlebars
msgid "move poll down"
msgstr ""
#: public/handlebars/poll_studio.handlebars
msgid "Delete"
msgstr ""
#: public/handlebars/poll_studio.handlebars
msgid "Image URL"
msgstr ""
#: public/handlebars/poll_studio.handlebars
msgid "Image alternative text"
msgstr ""
#: public/handlebars/poll_studio.handlebars
msgid ""
"You can make limited use of Markdown in answer texts, preferably only bold "
"and italics."
msgstr ""
#: public/handlebars/poll_studio.handlebars
msgid ""
"This must have an image URL or text, and can have both. If you add an "
"image, you must also provide an alternative text that describes the image "
"in a way that would allow someone to answer the poll if the image did not "
"load."
msgstr ""
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2016-04-07 16:17+0500\n"
"PO-Revision-Date: 2016-04-07 16:17+0500\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: eo\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.8.7\n"
"X-Poedit-Basepath: ../../..\n"
"X-Poedit-SearchPath-0: poll.py\n"
#: poll.py
msgid "Whether or not to display results to the user."
msgstr ""
"Whéthér ör nöt tö dïspläý résülts tö thé üsér. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя α#"
#: poll.py
msgid "The maximum number of times a user may send a submission."
msgstr ""
"Thé mäxïmüm nümßér öf tïmés ä üsér mäý sénd ä süßmïssïön. Ⱡ'σяєм ιρѕυм ∂σłσя"
" ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: poll.py
msgid "Number of times the user has sent a submission."
msgstr ""
"Nümßér öf tïmés thé üsér häs sént ä süßmïssïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,"
" ¢σηѕє¢тєтυя α#"
#: poll.py
msgid "Text to display after the user votes."
msgstr ""
"Téxt tö dïspläý äftér thé üsér vötés. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυ#"
#: poll.py
#, python-brace-format
msgid "'{field}' is not present, or not a JSON array."
msgstr ""
"'{field}' ïs nöt présént, ör nöt ä JSÖN ärräý. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя #"
#: poll.py
#, python-brace-format
msgid "{noun} {item} not a javascript object!"
msgstr ""
"{noun} {item} nöt ä jäväsçrïpt ößjéçt! Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
#: poll.py
#, python-brace-format
msgid "{noun} {item} contains no key."
msgstr "{noun} {item} çöntäïns nö kéý. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢ση#"
#: poll.py
#, python-brace-format
msgid ""
"{noun} has no text or img. Please make sure all {noun_lower}s have one or the other, or "
"both."
msgstr ""
"{noun} häs nö téxt ör ïmg. Pléäsé mäké süré äll {noun_lower}s hävé öné ör thé öthér, ör "
"ßöth. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
#: poll.py
#, python-brace-format
msgid ""
"{noun} was added with no label. All {noun_lower}s must have labels. Please check the "
"form. Check the form and explicitly delete {noun_lower}s if not needed."
msgstr ""
"{noun} wäs äddéd wïth nö läßél. Àll {noun_lower}s müst hävé läßéls. Pléäsé çhéçk thé "
"förm. Çhéçk thé förm änd éxplïçïtlý délété {noun_lower}s ïf nöt néédéd. Ⱡ'σяєм ιρѕυм "
"∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυѕмσ∂ тємρσя "
"ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм νєηιαм, qυιѕ "
"ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ "
"¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт єѕѕє "
"¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт "
"ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт łαвσяυ#"
#: poll.py
msgid ""
"All images must have an alternative text describing the image in a way that "
"would allow someone to answer the poll if the image did not load."
msgstr ""
"Àll ïmägés müst hävé än ältérnätïvé téxt désçrïßïng thé ïmägé ïn ä wäý thät "
"wöüld ällöw söméöné tö änswér thé pöll ïf thé ïmägé dïd nöt löäd. Ⱡ'σяєм "
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυѕмσ∂ тємρσя "
"ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм νєηιαм, qυιѕ "
"ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ "
"¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт єѕѕє "
"¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт "
"ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт #"
#: poll.py
#, python-brace-format
msgid "You must include at least one {noun_lower}."
msgstr ""
"Ýöü müst ïnçlüdé ät léäst öné {noun_lower}. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєт#"
#: poll.py
msgid "Maximum Submissions missing or not an integer."
msgstr ""
"Mäxïmüm Süßmïssïöns mïssïng ör nöt än ïntégér. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя α#"
#: poll.py
msgid "Private results may not be False when Maximum Submissions is not 1."
msgstr ""
"Prïväté résülts mäý nöt ßé Fälsé whén Mäxïmüm Süßmïssïöns ïs nöt 1. Ⱡ'σяєм "
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя #"
#: poll.py
msgid "Total tally of answers from students."
msgstr ""
"Tötäl tällý öf änswérs fröm stüdénts. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυ#"
#: poll.py
msgid "The student's answer"
msgstr "Thé stüdént's änswér Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
#: poll.py
msgid "You have already voted in this poll."
msgstr ""
"Ýöü hävé älréädý vötéd ïn thïs pöll. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυ#"
#: poll.py
msgid "Answer not included with request."
msgstr ""
"Ànswér nöt ïnçlüdéd wïth réqüést. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
#: poll.py
#, python-brace-format
msgid "No key \"{choice}\" in answers table."
msgstr ""
"Nö kéý \"{choice}\" ïn änswérs täßlé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#"
#: poll.py
msgid "You have already voted as many times as you are allowed."
msgstr ""
"Ýöü hävé älréädý vötéd äs mäný tïmés äs ýöü äré ällöwéd. Ⱡ'σяєм ιρѕυм ∂σłσя "
"ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: poll.py
msgid "You must specify a question."
msgstr "Ýöü müst spéçïfý ä qüéstïön. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
#: poll.py
msgid "Answer choices for this Survey"
msgstr "Ànswér çhöïçés för thïs Sürvéý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#"
#: poll.py
msgid "Are you enjoying the course?"
msgstr "Àré ýöü énjöýïng thé çöürsé? Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
#: poll.py
msgid "Would you recommend this course to your friends?"
msgstr ""
"Wöüld ýöü réçömménd thïs çöürsé tö ýöür frïénds? Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт "
"αмєт, ¢σηѕє¢тєтυя α#"
#: poll.py
msgid "Do you think you will learn a lot?"
msgstr ""
"Dö ýöü thïnk ýöü wïll léärn ä löt? Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєт#"
#: poll.py
msgid "Questions for this Survey"
msgstr "Qüéstïöns för thïs Sürvéý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
#: poll.py
msgid "Survey"
msgstr "Sürvéý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
#: poll.py
msgid "The user's answers"
msgstr "Thé üsér's änswérs Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#"
#: poll.py
msgid ""
"Not all questions were included, or unknown questions were included. Try "
"refreshing and trying again."
msgstr ""
"Nöt äll qüéstïöns wéré ïnçlüdéd, ör ünknöwn qüéstïöns wéré ïnçlüdéd. Trý "
"réfréshïng änd trýïng ägäïn. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмє#"
#: poll.py
#, python-format
msgid "Found unknown answer '{answer_key}' for question key '{question_key}'"
msgstr ""
"Föünd ünknöwn änswér '{answer_key}' för qüéstïön kéý '{question_key}' Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,"
" ¢σηѕє¢тєтυя α#"
#: poll.py
msgid "Poll"
msgstr "Pöll Ⱡ'σяєм ι#"
#: poll.py
msgid "What is your favorite color?"
msgstr "Whät ïs ýöür fävörïté çölör? Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
#: poll.py
msgid "Red"
msgstr "Réd Ⱡ'σяєм#"
#: poll.py
msgid "Blue"
msgstr "Blüé Ⱡ'σяєм ι#"
#: poll.py
msgid "Other"
msgstr "Öthér Ⱡ'σяєм ιρѕ#"
#: poll.py
msgid "The answer options on this poll."
msgstr ""
"Thé änswér öptïöns ön thïs pöll. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
#: poll.py
msgid "Answer"
msgstr "Ànswér Ⱡ'σяєм ιρѕυ#"
#: poll.py
msgid "Question"
msgstr "Qüéstïön Ⱡ'σяєм ιρѕυм ∂#"
#: poll.py
msgid "Yes"
msgstr "Ýés Ⱡ'σяєм#"
#: poll.py
msgid "No"
msgstr "Nö Ⱡ'σя#"
#: poll.py
msgid "Maybe"
msgstr "Mäýßé Ⱡ'σяєм ιρѕ#"
#: public/handlebars/poll_results.handlebars
msgid "Results gathered from {total} respondents."
msgstr ""
"Résülts gäthéréd fröm {total} réspöndénts. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя#"
#: public/handlebars/poll_results.handlebars
msgid "Results gathered from {total} respondent."
msgstr ""
"Résülts gäthéréd fröm {total} réspöndént. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυ#"
#: public/handlebars/poll_studio.handlebars
msgid "move poll up"
msgstr "mövé pöll üp Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: public/handlebars/poll_studio.handlebars
msgid "move poll down"
msgstr "mövé pöll döwn Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
#: public/handlebars/poll_studio.handlebars
msgid "Delete"
msgstr "Délété Ⱡ'σяєм ιρѕυ#"
#: public/handlebars/poll_studio.handlebars
msgid "Image URL"
msgstr "Ìmägé ÛRL Ⱡ'σяєм ιρѕυм ∂σł#"
#: public/handlebars/poll_studio.handlebars
msgid "Image alternative text"
msgstr "Ìmägé ältérnätïvé téxt Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢#"
#: public/handlebars/poll_studio.handlebars
msgid ""
"You can make limited use of Markdown in answer texts, preferably only bold "
"and italics."
msgstr ""
"Ýöü çän mäké lïmïtéd üsé öf Märkdöwn ïn änswér téxts, préféräßlý önlý ßöld "
"änd ïtälïçs. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє#"
#: public/handlebars/poll_studio.handlebars
msgid ""
"This must have an image URL or text, and can have both. If you add an "
"image, you must also provide an alternative text that describes the image in"
" a way that would allow someone to answer the poll if the image did not "
"load."
msgstr ""
"Thïs müst hävé än ïmägé ÛRL ör téxt, änd çän hävé ßöth. Ìf ýöü ädd än "
"ïmägé, ýöü müst älsö prövïdé än ältérnätïvé téxt thät désçrïßés thé ïmägé ïn"
" ä wäý thät wöüld ällöw söméöné tö änswér thé pöll ïf thé ïmägé dïd nöt "
"löäd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ "
"єιυѕмσ∂ тємρσя ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм"
" νєηιαм, qυιѕ ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα "
"¢σммσ∂σ ¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт"
" єѕѕє ¢ιłłυм ∂σłσяє єυ ƒυgιαт #"
# -*- coding: utf-8 -*-
#
# Make '_' a no-op so we can scrape strings
def _(text):
return text
......@@ -44,7 +44,7 @@ def package_data(pkg, roots):
setup(
name='xblock-poll',
version='1.0',
version='1.1',
description='An XBlock for polling users.',
packages=[
'poll',
......@@ -61,5 +61,5 @@ setup(
'survey = poll:SurveyBlock',
]
},
package_data=package_data("poll", ["static", "public"]),
package_data=package_data("poll", ["static", "public", "translations"]),
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment