Drop remnants of in-context localization (#2862)
* Drop pontoon/in-context/ * Drop subpages * Drop project links, url & width fields * Drop pontoon.js * Drop remaining mentions of in-context localization
|
@ -12,7 +12,6 @@ specs/
|
|||
/static/
|
||||
|
||||
# Jinja templates
|
||||
pontoon/base/templates/js/pontoon.js
|
||||
translate/public/translate.html
|
||||
**/templates/**/*.html
|
||||
|
||||
|
@ -23,4 +22,3 @@ pontoon/base/static/css/boilerplate.css
|
|||
pontoon/base/static/css/fontawesome-all.css
|
||||
pontoon/base/static/css/nprogress.css
|
||||
pontoon/base/static/js/lib/
|
||||
pontoon/in_context/static/
|
||||
|
|
|
@ -12,7 +12,6 @@ specs/
|
|||
/static/
|
||||
|
||||
# Jinja templates
|
||||
pontoon/base/templates/js/pontoon.js
|
||||
translate/public/translate.html
|
||||
**/templates/**/*.html
|
||||
|
||||
|
@ -23,4 +22,3 @@ pontoon/base/static/css/boilerplate.css
|
|||
pontoon/base/static/css/fontawesome-all.css
|
||||
pontoon/base/static/css/nprogress.css
|
||||
pontoon/base/static/js/lib/
|
||||
pontoon/in_context/static/
|
||||
|
|
2
app.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "pontoon",
|
||||
"description": "In-place localization tool.",
|
||||
"description": "Mozilla localization platform",
|
||||
"keywords": [
|
||||
"l10n",
|
||||
"localization",
|
||||
|
|
|
@ -107,12 +107,10 @@ Installation
|
|||
your Postgres database. It takes the form
|
||||
``postgres://username:password@server_addr/database_name``.
|
||||
|
||||
- ``SITE_URL`` should be set to the URL you will use to connect to your
|
||||
local development site. Some people prefer to use
|
||||
``http://127.0.0.1:8000`` instead of ``localhost``. However, should you
|
||||
decide to change the ``SITE_URL``, you also need to request_
|
||||
the new ``FXA_CLIENT_ID`` and ``FXA_SECRET_KEY``, and our in-context demo
|
||||
site ``http://localhost:8000/in-context/`` will require change of base url.
|
||||
- ``SITE_URL`` should be set to the URL you will use to connect to your local development site.
|
||||
Some people prefer to use ``http://127.0.0.1:8000`` instead of ``localhost``.
|
||||
However, should you decide to change the ``SITE_URL``,
|
||||
you also need to request_ the new ``FXA_CLIENT_ID`` and ``FXA_SECRET_KEY``.
|
||||
|
||||
6. Initialize your database by running the migrations:
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "pontoon",
|
||||
"version": "0.1.0",
|
||||
"description": "Mozilla in-place localization tool.",
|
||||
"description": "Mozilla localization platform",
|
||||
"private": true,
|
||||
"browserslist": [
|
||||
"chrome >= 80",
|
||||
|
|
|
@ -9,7 +9,6 @@ from pontoon.base.models import (
|
|||
Locale,
|
||||
Project,
|
||||
Repository,
|
||||
Subpage,
|
||||
)
|
||||
from pontoon.base.forms import HtmlField
|
||||
from pontoon.tags.models import Tag
|
||||
|
@ -52,9 +51,6 @@ class ProjectForm(forms.ModelForm):
|
|||
"data_source",
|
||||
"can_be_requested",
|
||||
"configuration_file",
|
||||
"url",
|
||||
"width",
|
||||
"links",
|
||||
"info",
|
||||
"admin_notes",
|
||||
"deadline",
|
||||
|
@ -74,11 +70,6 @@ class ProjectForm(forms.ModelForm):
|
|||
).order_by("email")
|
||||
|
||||
|
||||
SubpageInlineFormSet = inlineformset_factory(
|
||||
Project, Subpage, extra=1, fields=("project", "name", "url")
|
||||
)
|
||||
|
||||
|
||||
RepositoryInlineFormSet = inlineformset_factory(
|
||||
Project,
|
||||
Repository,
|
||||
|
|
|
@ -220,7 +220,7 @@ $(function () {
|
|||
$(this).parents('.repository').find('.website-wrapper input').val(val);
|
||||
});
|
||||
|
||||
// Delete inline form item (e.g. subpage or external resource)
|
||||
// Delete inline form item (e.g. external resource)
|
||||
$('body').on('click.pontoon', '.delete-inline', function (e) {
|
||||
e.preventDefault();
|
||||
$(this).parent().toggleClass('delete');
|
||||
|
@ -228,9 +228,8 @@ $(function () {
|
|||
});
|
||||
$('.inline [checked]').click().prev().click();
|
||||
|
||||
// Add inline form item (e.g. subpage or external resource)
|
||||
// Add inline form item (e.g. external resource)
|
||||
var count = {
|
||||
subpage: $('.subpage:last').data('count'),
|
||||
externalresource: $('.externalresource:last').data('count'),
|
||||
entity: $('.entity:last').data('count'),
|
||||
tag: $('.tag:last').data('count'),
|
||||
|
|
|
@ -263,26 +263,6 @@
|
|||
<option value="Issue tracking">
|
||||
</datalist>
|
||||
|
||||
<h3>In-context localization <span class="small stress">(optional)</span></h3>
|
||||
|
||||
<div class="clearfix">
|
||||
{{ form.url.label_tag() }}
|
||||
{{ form.url }}
|
||||
{{ form.url.errors }}
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
{{ form.width.label_tag() }}
|
||||
{{ form.width }}
|
||||
{{ form.width.errors }}
|
||||
</div>
|
||||
<div class="checkbox clearfix">
|
||||
<label for="id_links">
|
||||
{{ form.links }}{{ form.links.label }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{{ inline_formset.render(title='Subpages', type='subpage', formset=subpage_formset) }}
|
||||
|
||||
<section class="tags">
|
||||
<h3>
|
||||
<span>Tags</span>
|
||||
|
|
|
@ -398,15 +398,10 @@ def test_project_add_locale(client_superuser):
|
|||
# or I don't know how to handle that more gracefully.
|
||||
form = ProjectForm(instance=project)
|
||||
form_data = dict(form.initial)
|
||||
del form_data["width"]
|
||||
del form_data["deadline"]
|
||||
del form_data["contact"]
|
||||
form_data.update(
|
||||
{
|
||||
"subpage_set-INITIAL_FORMS": "0",
|
||||
"subpage_set-TOTAL_FORMS": "1",
|
||||
"subpage_set-MIN_NUM_FORMS": "0",
|
||||
"subpage_set-MAX_NUM_FORMS": "1000",
|
||||
"externalresource_set-TOTAL_FORMS": "1",
|
||||
"externalresource_set-MAX_NUM_FORMS": "1000",
|
||||
"externalresource_set-MIN_NUM_FORMS": "0",
|
||||
|
|
|
@ -17,7 +17,6 @@ from pontoon.administration.forms import (
|
|||
ExternalResourceInlineFormSet,
|
||||
ProjectForm,
|
||||
RepositoryInlineFormSet,
|
||||
SubpageInlineFormSet,
|
||||
TagInlineFormSet,
|
||||
)
|
||||
from pontoon.base import utils
|
||||
|
@ -87,7 +86,6 @@ def manage_project(request, slug=None, template="admin_project.html"):
|
|||
raise PermissionDenied
|
||||
|
||||
form = ProjectForm()
|
||||
subpage_formset = SubpageInlineFormSet()
|
||||
repo_formset = RepositoryInlineFormSet()
|
||||
external_resource_formset = ExternalResourceInlineFormSet()
|
||||
tag_formset = TagInlineFormSet()
|
||||
|
@ -116,7 +114,6 @@ def manage_project(request, slug=None, template="admin_project.html"):
|
|||
project = Project.objects.visible_for(request.user).get(pk=pk)
|
||||
form = ProjectForm(request.POST, instance=project)
|
||||
# Needed if form invalid
|
||||
subpage_formset = SubpageInlineFormSet(request.POST, instance=project)
|
||||
repo_formset = RepositoryInlineFormSet(request.POST, instance=project)
|
||||
tag_formset = (
|
||||
TagInlineFormSet(request.POST, instance=project)
|
||||
|
@ -132,14 +129,12 @@ def manage_project(request, slug=None, template="admin_project.html"):
|
|||
except MultiValueDictKeyError:
|
||||
form = ProjectForm(request.POST)
|
||||
# Needed if form invalid
|
||||
subpage_formset = SubpageInlineFormSet(request.POST)
|
||||
repo_formset = RepositoryInlineFormSet(request.POST)
|
||||
external_resource_formset = ExternalResourceInlineFormSet(request.POST)
|
||||
tag_formset = None
|
||||
|
||||
if form.is_valid():
|
||||
project = form.save(commit=False)
|
||||
subpage_formset = SubpageInlineFormSet(request.POST, instance=project)
|
||||
repo_formset = RepositoryInlineFormSet(request.POST, instance=project)
|
||||
external_resource_formset = ExternalResourceInlineFormSet(
|
||||
request.POST, instance=project
|
||||
|
@ -147,8 +142,7 @@ def manage_project(request, slug=None, template="admin_project.html"):
|
|||
if tag_formset:
|
||||
tag_formset = TagInlineFormSet(request.POST, instance=project)
|
||||
formsets_valid = (
|
||||
subpage_formset.is_valid()
|
||||
and repo_formset.is_valid()
|
||||
repo_formset.is_valid()
|
||||
and external_resource_formset.is_valid()
|
||||
and (tag_formset.is_valid() if tag_formset else True)
|
||||
)
|
||||
|
@ -191,7 +185,6 @@ def manage_project(request, slug=None, template="admin_project.html"):
|
|||
pretranslation_enabled=True
|
||||
)
|
||||
|
||||
subpage_formset.save()
|
||||
repo_formset.save()
|
||||
external_resource_formset.save()
|
||||
if tag_formset:
|
||||
|
@ -203,7 +196,6 @@ def manage_project(request, slug=None, template="admin_project.html"):
|
|||
_create_or_update_translated_resources(project, locales)
|
||||
|
||||
# Properly displays formsets, but removes errors (if valid only)
|
||||
subpage_formset = SubpageInlineFormSet(instance=project)
|
||||
repo_formset = RepositoryInlineFormSet(instance=project)
|
||||
external_resource_formset = ExternalResourceInlineFormSet(
|
||||
instance=project
|
||||
|
@ -223,7 +215,6 @@ def manage_project(request, slug=None, template="admin_project.html"):
|
|||
project = Project.objects.get(slug=slug)
|
||||
pk = project.pk
|
||||
form = ProjectForm(instance=project)
|
||||
subpage_formset = SubpageInlineFormSet(instance=project)
|
||||
repo_formset = RepositoryInlineFormSet(instance=project)
|
||||
tag_formset = (
|
||||
TagInlineFormSet(instance=project) if project.tags_enabled else None
|
||||
|
@ -273,7 +264,6 @@ def manage_project(request, slug=None, template="admin_project.html"):
|
|||
data = {
|
||||
"slug": slug,
|
||||
"form": form,
|
||||
"subpage_formset": subpage_formset,
|
||||
"repo_formset": repo_formset,
|
||||
"tag_formset": tag_formset,
|
||||
"external_resource_formset": external_resource_formset,
|
||||
|
|
|
@ -208,19 +208,6 @@ class RepositoryInline(admin.TabularInline):
|
|||
)
|
||||
|
||||
|
||||
class SubpageInline(admin.TabularInline):
|
||||
model = models.Subpage
|
||||
extra = 0
|
||||
verbose_name_plural = "Subpages"
|
||||
fields = (
|
||||
"project",
|
||||
"name",
|
||||
"url",
|
||||
"resources",
|
||||
)
|
||||
raw_id_fields = ("resources",)
|
||||
|
||||
|
||||
class ProjectAdmin(admin.ModelAdmin):
|
||||
search_fields = ["name", "slug"]
|
||||
list_display = (
|
||||
|
@ -270,11 +257,9 @@ class ProjectAdmin(admin.ModelAdmin):
|
|||
),
|
||||
},
|
||||
),
|
||||
("WEBSITE", {"fields": ("url", "width", "links")}),
|
||||
)
|
||||
readonly_fields = AGGREGATED_STATS_FIELDS + ("latest_translation",)
|
||||
inlines = (
|
||||
SubpageInline,
|
||||
ProjectLocaleInline,
|
||||
RepositoryInline,
|
||||
ExternalProjectResourceInline,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# Generated by Django 3.2.15 on 2023-05-27 10:16
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("base", "0043_alter_locale_accesskey_localization"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="Subpage",
|
||||
),
|
||||
]
|
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 3.2.15 on 2023-05-27 11:00
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("base", "0044_delete_subpage"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="project",
|
||||
name="links",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="project",
|
||||
name="url",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="project",
|
||||
name="width",
|
||||
),
|
||||
]
|
|
@ -1076,11 +1076,10 @@ class Locale(AggregatedStats):
|
|||
]
|
||||
|
||||
def parts_stats(self, project):
|
||||
"""Get locale-project pages/paths with stats."""
|
||||
"""Get locale-project paths with stats."""
|
||||
|
||||
def get_details(parts):
|
||||
return parts.order_by("title").values(
|
||||
"url",
|
||||
"title",
|
||||
"resource__path",
|
||||
"resource__deadline",
|
||||
|
@ -1092,95 +1091,15 @@ class Locale(AggregatedStats):
|
|||
"approved_strings",
|
||||
)
|
||||
|
||||
pages = project.subpage_set.all()
|
||||
translatedresources = TranslatedResource.objects.filter(
|
||||
resource__project=project, resource__entities__obsolete=False, locale=self
|
||||
).distinct()
|
||||
details = []
|
||||
unbound_details = []
|
||||
|
||||
# If subpages aren't defined,
|
||||
# return resource paths with corresponding stats
|
||||
if len(pages) == 0:
|
||||
details = get_details(
|
||||
translatedresources.annotate(
|
||||
title=F("resource__path"), url=F("resource__project__url")
|
||||
)
|
||||
)
|
||||
|
||||
# If project has defined subpages, return their names with
|
||||
# corresponding project stats. If subpages have defined resources,
|
||||
# only include stats for page resources.
|
||||
elif len(pages) > 0:
|
||||
# Each subpage must have resources defined
|
||||
if pages[0].resources.exists():
|
||||
locale_pages = pages.filter(resources__translatedresources__locale=self)
|
||||
details = get_details(
|
||||
# List only subpages, whose resources are available for locale
|
||||
locale_pages.annotate(
|
||||
title=F("name"),
|
||||
resource__path=F("resources__path"),
|
||||
resource__deadline=F("resources__deadline"),
|
||||
resource__total_strings=F("resources__total_strings"),
|
||||
pretranslated_strings=F(
|
||||
"resources__translatedresources__pretranslated_strings"
|
||||
),
|
||||
strings_with_errors=F(
|
||||
"resources__translatedresources__strings_with_errors"
|
||||
),
|
||||
strings_with_warnings=F(
|
||||
"resources__translatedresources__strings_with_warnings"
|
||||
),
|
||||
unreviewed_strings=F(
|
||||
"resources__translatedresources__unreviewed_strings"
|
||||
),
|
||||
approved_strings=F(
|
||||
"resources__translatedresources__approved_strings"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
locale_pages = pages.filter(
|
||||
project__resources__translatedresources__locale=self
|
||||
).exclude(project__resources__total_strings=0)
|
||||
details = get_details(
|
||||
locale_pages.annotate(
|
||||
title=F("name"),
|
||||
resource__path=F("project__resources__path"),
|
||||
resource__deadline=F("project__resources__deadline"),
|
||||
resource__total_strings=F("project__resources__total_strings"),
|
||||
pretranslated_strings=F(
|
||||
"project__resources__translatedresources__pretranslated_strings"
|
||||
),
|
||||
strings_with_errors=F(
|
||||
"project__resources__translatedresources__strings_with_errors"
|
||||
),
|
||||
strings_with_warnings=F(
|
||||
"project__resources__translatedresources__strings_with_warnings"
|
||||
),
|
||||
unreviewed_strings=F(
|
||||
"project__resources__translatedresources__unreviewed_strings"
|
||||
),
|
||||
approved_strings=F(
|
||||
"project__resources__translatedresources__approved_strings"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# List resources not bound to subpages as regular resources
|
||||
bound_resources = locale_pages.values_list("resources", flat=True)
|
||||
unbound_tr = translatedresources.exclude(resource__pk__in=bound_resources)
|
||||
unbound_details = get_details(
|
||||
unbound_tr.annotate(
|
||||
title=F("resource__path"), url=F("resource__project__url")
|
||||
)
|
||||
)
|
||||
details = list(
|
||||
get_details(translatedresources.annotate(title=F("resource__path")))
|
||||
)
|
||||
|
||||
all_resources = ProjectLocale.objects.get(project=project, locale=self)
|
||||
|
||||
details_list = list(details) + list(unbound_details)
|
||||
details_list.append(
|
||||
details.append(
|
||||
{
|
||||
"title": "all-resources",
|
||||
"resource__path": [],
|
||||
|
@ -1194,7 +1113,7 @@ class Locale(AggregatedStats):
|
|||
}
|
||||
)
|
||||
|
||||
return details_list
|
||||
return details
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
old = Locale.objects.get(pk=self.pk) if self.pk else None
|
||||
|
@ -1387,20 +1306,6 @@ class Project(AggregatedStats):
|
|||
choices=Visibility.choices,
|
||||
)
|
||||
|
||||
# Website for in place localization
|
||||
url = models.URLField("URL", blank=True)
|
||||
width = models.PositiveIntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="""
|
||||
Default website (iframe) width in pixels.
|
||||
If set, sidebar will be opened by default.
|
||||
""",
|
||||
)
|
||||
links = models.BooleanField(
|
||||
"Keep links on the project website clickable", default=False
|
||||
)
|
||||
|
||||
langpack_url = models.URLField(
|
||||
"Language pack URL",
|
||||
blank=True,
|
||||
|
@ -1466,9 +1371,6 @@ class Project(AggregatedStats):
|
|||
"name": self.name,
|
||||
"slug": self.slug,
|
||||
"info": self.info,
|
||||
"url": self.url,
|
||||
"width": self.width or "",
|
||||
"links": self.links or "",
|
||||
"langpack_url": self.langpack_url or "",
|
||||
"contact": self.contact.serialize() if self.contact else None,
|
||||
}
|
||||
|
@ -1625,13 +1527,6 @@ class Project(AggregatedStats):
|
|||
resource__project=self, resource__entities__obsolete=False
|
||||
).distinct().aggregate_stats(self)
|
||||
|
||||
def parts_to_paths(self, paths):
|
||||
try:
|
||||
subpage = Subpage.objects.get(project=self, name__in=paths)
|
||||
return subpage.resources.values_list("path")
|
||||
except Subpage.DoesNotExist:
|
||||
return paths
|
||||
|
||||
@property
|
||||
def avg_string_count(self):
|
||||
return int(self.total_strings / self.enabled_locales)
|
||||
|
@ -2310,16 +2205,6 @@ class Resource(models.Model):
|
|||
return path_format
|
||||
|
||||
|
||||
class Subpage(models.Model):
|
||||
project = models.ForeignKey(Project, models.CASCADE)
|
||||
name = models.CharField(max_length=128)
|
||||
url = models.URLField("URL", blank=True)
|
||||
resources = models.ManyToManyField(Resource, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class EntityQuerySet(models.QuerySet):
|
||||
def get_filtered_entities(
|
||||
self, locale, query, rule, project=None, match_all=True, prefetch=None
|
||||
|
@ -3043,7 +2928,6 @@ class Entity(DirtyFieldsMixin, models.Model):
|
|||
|
||||
# Filter by path
|
||||
if paths:
|
||||
paths = project.parts_to_paths(paths)
|
||||
entities = entities.filter(resource__path__in=paths)
|
||||
|
||||
if status:
|
||||
|
@ -3400,7 +3284,6 @@ class Translation(DirtyFieldsMixin, models.Model):
|
|||
)
|
||||
|
||||
if paths:
|
||||
paths = project.parts_to_paths(paths)
|
||||
translations = translations.filter(entity__resource__path__in=paths)
|
||||
|
||||
return translations
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
.pontoon-hovered {
|
||||
outline: 1px dashed !important;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.pontoon-editable-toolbar {
|
||||
background-color: #ebebeb;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999999999;
|
||||
display: none;
|
||||
border-top: 1px dashed #000000;
|
||||
border-left: 1px dashed #000000;
|
||||
border-right: 1px dashed #000000;
|
||||
}
|
||||
|
||||
.pontoon-editable-toolbar.bottom {
|
||||
border-top: none;
|
||||
border-bottom: 1px dashed #000000;
|
||||
}
|
||||
|
||||
.pontoon-editable-toolbar a {
|
||||
background: transparent none 0 0 no-repeat;
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
float: left;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.pontoon-editable-toolbar .edit {
|
||||
background-image: url('../img/edit.png');
|
||||
}
|
||||
|
||||
.pontoon-editable-toolbar .cancel {
|
||||
background-image: url('../img/cancel.png');
|
||||
display: none;
|
||||
}
|
Двоичные данные
pontoon/base/static/img/cancel.png
До Ширина: | Высота: | Размер: 1.5 KiB |
Двоичные данные
pontoon/base/static/img/edit.png
До Ширина: | Высота: | Размер: 1.5 KiB |
Двоичные данные
pontoon/base/static/img/logo.png
До Ширина: | Высота: | Размер: 987 B |
Двоичные данные
pontoon/base/static/img/logo32.png
До Ширина: | Высота: | Размер: 196 B |
Двоичные данные
pontoon/base/static/img/save.png
До Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -1,739 +0,0 @@
|
|||
(function () {
|
||||
|
||||
// Main code
|
||||
function jqueryLoaded() {
|
||||
$(function() {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send data to main Pontoon code
|
||||
*/
|
||||
function sendData() {
|
||||
// Deep copy: http://api.jquery.com/jQuery.extend
|
||||
var entities = $.extend(true, [], Pontoon.entities);
|
||||
$(entities).each(function () {
|
||||
delete this.node;
|
||||
});
|
||||
|
||||
postMessage("DATA", {
|
||||
entities: entities
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Render main UI and handle events
|
||||
*/
|
||||
function renderHandle() {
|
||||
sendData();
|
||||
postMessage("RENDER", {
|
||||
url: Pontoon.project.url,
|
||||
title: Pontoon.project.title
|
||||
});
|
||||
|
||||
// Do not change anything when cancelled
|
||||
$(".pontoon-editable-toolbar > .cancel").click(function () {
|
||||
var element = $(this).parent()[0].target,
|
||||
entity = element.entity,
|
||||
string = entity.translation[0].string;
|
||||
|
||||
$(element).html(string !== null ? string : entity.original);
|
||||
postMessage("INACTIVE", entity.id);
|
||||
});
|
||||
|
||||
// In-place keyboard shortcuts
|
||||
$("html").unbind("keydown.pontoon").bind("keydown.pontoon", function (e) {
|
||||
var key = e.which,
|
||||
toolbar = $(".pontoon-editable-toolbar"),
|
||||
cancel = toolbar.find(".cancel");
|
||||
|
||||
if (cancel.is(":visible")) {
|
||||
if (key === 27) { // Esc: status quo
|
||||
cancel.click();
|
||||
hideToolbar(toolbar[0].target);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Makes DOM nodes hoverable and localizable in the sidebar
|
||||
*
|
||||
* entity Entity object
|
||||
*/
|
||||
function makeLocalizable(entity) {
|
||||
entity.body = true;
|
||||
$(entity.node).each(function() {
|
||||
this[0].entity = entity; // Store entity reference to the node
|
||||
|
||||
this.prop('lang', Pontoon.locale.code);
|
||||
this.prop('dir', 'auto');
|
||||
|
||||
// Show/hide toolbar on node hover
|
||||
if (!this.handlersAttached) {
|
||||
this.hover(function () {
|
||||
showToolbar(this);
|
||||
}, function() {
|
||||
hideToolbar(this);
|
||||
});
|
||||
this.handlersAttached = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Show/hide toolbar on entity hover
|
||||
entity.hover = function () {
|
||||
showToolbar(this.node[0][0]);
|
||||
};
|
||||
entity.unhover = function () {
|
||||
hideToolbar(this.node[0][0]);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Extract entities from the document, not prepared for working with Pontoon
|
||||
*
|
||||
* Create entity object from every non-empty text node
|
||||
* Exclude nodes from special tags (e.g. <script>) and with translate=no attribute
|
||||
* Skip nodes already included in parent nodes
|
||||
* Add temporary pontoon-entity class to prevent duplicate entities when guessing
|
||||
*/
|
||||
function loadEntitiesGuess() {
|
||||
var counter = 0;
|
||||
|
||||
// <noscript> contents are not in the DOM
|
||||
$('noscript').each(function() {
|
||||
$("<div/>", {
|
||||
class: "pontoon-noscript",
|
||||
innerHTML: $(this).text()
|
||||
}).appendTo("body");
|
||||
});
|
||||
|
||||
$(':not("script, style, iframe, noscript, [translate=\'no\']")').contents().each(function () {
|
||||
if (this.nodeType === Node.TEXT_NODE && $.trim(this.nodeValue).length > 0 && $(this).parents(".pontoon-entity").length === 0) {
|
||||
var entity = {},
|
||||
parent = $(this).parent();
|
||||
|
||||
// If project uses hooks, but not available in the DB, remove <!--l10n--> comment nodes
|
||||
parent.contents().each(function () {
|
||||
if (this.nodeType === Node.COMMENT_NODE && this.nodeValue.indexOf('l10n') === 0) {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
|
||||
entity.id = counter;
|
||||
counter++;
|
||||
entity.original = parent.html();
|
||||
|
||||
// Head entities cannot be edited in place
|
||||
if ($(this).parents('head').length === 0) {
|
||||
entity.node = [parent];
|
||||
makeLocalizable(entity);
|
||||
}
|
||||
|
||||
// Remove entities from child nodes if parent node is entity
|
||||
parent.find(".pontoon-entity").each(function() {
|
||||
delete this.entity;
|
||||
Pontoon.entities.pop();
|
||||
entity.id--;
|
||||
counter--;
|
||||
});
|
||||
|
||||
Pontoon.entities.push(entity);
|
||||
parent.addClass("pontoon-entity");
|
||||
}
|
||||
});
|
||||
|
||||
$(".pontoon-entity").removeClass("pontoon-entity");
|
||||
$(".pontoon-noscript").remove();
|
||||
|
||||
renderHandle();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Match entities and elements with exact same contents
|
||||
*/
|
||||
function loadEntitiesMatch() {
|
||||
var counter = 0,
|
||||
elements = {};
|
||||
|
||||
$(':not("script, style, iframe, noscript, [translate=\'no\']")')
|
||||
.children().each(function() {
|
||||
var text = $.trim($(this).html());
|
||||
if (!elements[text]) {
|
||||
elements[text] = {
|
||||
node: [$(this)],
|
||||
body: [!$(this).parents('head').length]
|
||||
};
|
||||
} else {
|
||||
elements[text].node.push($(this));
|
||||
elements[text].body.push(!$(this).parents('head').length);
|
||||
}
|
||||
});
|
||||
|
||||
$(Pontoon.entities).each(function(i, entity) {
|
||||
// Renedered text could be different than source
|
||||
$('body').append('<div id="pontoon-string">' + entity.original + '</div>');
|
||||
|
||||
var translation = entity.translation[0].string,
|
||||
original = $('#pontoon-string').html(),
|
||||
element = elements[original];
|
||||
|
||||
entity.id = counter;
|
||||
|
||||
if (element) {
|
||||
$(element.node).each(function(i) {
|
||||
if (translation !== null) {
|
||||
this.html(translation);
|
||||
}
|
||||
|
||||
// Head entities cannot be edited in place
|
||||
if (element.body[i]) {
|
||||
if (!entity.node) {
|
||||
entity.node = [this];
|
||||
} else {
|
||||
entity.node.push(this);
|
||||
}
|
||||
makeLocalizable(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#pontoon-string').remove();
|
||||
counter++;
|
||||
});
|
||||
|
||||
renderHandle();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Match entities and strings prepended with l10n comment nodes
|
||||
* Example: <!--l10n-->Hello World
|
||||
*/
|
||||
function loadEntitiesHooks() {
|
||||
var counter = 0,
|
||||
l10n = {};
|
||||
|
||||
// Create object with l10n comment nodes
|
||||
$(':not("script, style, iframe, noscript, [translate=\'no\']")').contents().each(function () {
|
||||
if (this.nodeType === Node.COMMENT_NODE && this.nodeValue.indexOf('l10n') === 0) {
|
||||
var element = $(this).parent();
|
||||
$(this).remove();
|
||||
if (!l10n[element.html()]) {
|
||||
l10n[element.html()] = [element];
|
||||
} else {
|
||||
l10n[element.html()].push(element);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Match l10n comment nodes with DB data
|
||||
$(Pontoon.entities).each(function(i, entity) {
|
||||
// Renedered text could be different than source
|
||||
$('body').append('<div id="pontoon-string">' + this.original + '</div>');
|
||||
|
||||
var parent = l10n[$('#pontoon-string').html()],
|
||||
translation = this.translation[0].string;
|
||||
entity.id = counter;
|
||||
|
||||
// Head strings cannot be edited in place
|
||||
$(parent).each(function() {
|
||||
if (translation !== null) {
|
||||
this.html(translation);
|
||||
}
|
||||
if (this.parents('head').length === 0) {
|
||||
if (!entity.node) {
|
||||
entity.node = [this];
|
||||
} else {
|
||||
entity.node.push(this);
|
||||
}
|
||||
makeLocalizable(entity);
|
||||
}
|
||||
});
|
||||
|
||||
$('#pontoon-string').remove();
|
||||
counter++;
|
||||
});
|
||||
|
||||
renderHandle();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Match entities and elements with data-l10n-id attribute
|
||||
*
|
||||
* TODO: also match HTML attributes
|
||||
* https://github.com/l20n/l20n.js
|
||||
*/
|
||||
function loadEntitiesL20n() {
|
||||
var counter = 0;
|
||||
|
||||
$(Pontoon.entities).each(function(i, entity) {
|
||||
var translation = entity.translation[0].string;
|
||||
entity.id = counter;
|
||||
|
||||
$('[data-l10n-id="' + entity.key + '"]').each(function() {
|
||||
if (translation !== null) {
|
||||
$(this).html(translation);
|
||||
}
|
||||
if ($(this).parents('head').length === 0 && !$(this).is('input')) {
|
||||
if (!entity.node) {
|
||||
entity.node = [$(this)];
|
||||
} else {
|
||||
entity.node.push($(this));
|
||||
}
|
||||
makeLocalizable(entity);
|
||||
}
|
||||
});
|
||||
|
||||
counter++;
|
||||
});
|
||||
|
||||
renderHandle();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Show editable toolbar
|
||||
*
|
||||
* node DOM node
|
||||
*/
|
||||
function showToolbar(node) {
|
||||
if ($(node).is('.pontoon-editable-toolbar')) {
|
||||
showToolbar(node.target);
|
||||
return;
|
||||
|
||||
} else {
|
||||
var toolbar = $('.pontoon-editable-toolbar'),
|
||||
curTarget = toolbar[0].target,
|
||||
newTarget = node;
|
||||
|
||||
if ($(curTarget).is('.pontoon-localizing')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (curTarget && curTarget !== newTarget) {
|
||||
hideToolbar(curTarget);
|
||||
}
|
||||
|
||||
// Toolbar position
|
||||
var left = newTarget.getBoundingClientRect().left + window.scrollX,
|
||||
top = newTarget.getBoundingClientRect().top + window.scrollY,
|
||||
toolbarTop = top - toolbar.outerHeight();
|
||||
|
||||
toolbar.css('left', left);
|
||||
|
||||
// Display toolbar at the bottom if otherwise too high
|
||||
if (toolbarTop >= 0) {
|
||||
toolbar.removeClass('bottom').css('top', toolbarTop);
|
||||
} else{
|
||||
toolbar.addClass('bottom').css('top', top + $(newTarget).outerHeight());
|
||||
}
|
||||
}
|
||||
|
||||
var toolbarNode = toolbar[0];
|
||||
|
||||
if (toolbarNode.I !== null) {
|
||||
clearTimeout(toolbarNode.I);
|
||||
toolbarNode.I = null;
|
||||
}
|
||||
|
||||
if (newTarget) {
|
||||
toolbarNode.target = newTarget;
|
||||
}
|
||||
|
||||
$(newTarget)
|
||||
.addClass('pontoon-hovered')
|
||||
.unbind("dblclick.pontoon").bind("dblclick.pontoon", function() {
|
||||
$('.pontoon-editable-toolbar > .edit').click();
|
||||
});
|
||||
|
||||
postMessage("HOVER", newTarget.entity.id);
|
||||
toolbar.show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Hide editable toolbar
|
||||
*
|
||||
* node DOM node
|
||||
*/
|
||||
function hideToolbar(node) {
|
||||
if ($(node).is('.pontoon-editable-toolbar')) {
|
||||
var toolbar = $(node);
|
||||
} else {
|
||||
var toolbar = $('.pontoon-editable-toolbar');
|
||||
}
|
||||
|
||||
var toolbarNode = toolbar[0],
|
||||
target = toolbarNode.target;
|
||||
|
||||
if ($(target).is('.pontoon-localizing')) {
|
||||
return;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (target) {
|
||||
target.blur();
|
||||
|
||||
if (target === toolbar[0].target) {
|
||||
toolbar[0].target = null;
|
||||
$(target).removeClass('pontoon-hovered');
|
||||
postMessage("UNHOVER", target.entity.id);
|
||||
toolbar.hide();
|
||||
|
||||
} else {
|
||||
$(target).removeClass('pontoon-hovered');
|
||||
postMessage("UNHOVER", target.entity.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toolbar[0].I = setTimeout(hide, 5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enable editable mode
|
||||
* inplace Was called in place?
|
||||
*/
|
||||
function startEditing(inplace) {
|
||||
var toolbar = $('.pontoon-editable-toolbar'),
|
||||
target = toolbar[0].target;
|
||||
|
||||
toolbar
|
||||
.children().show().end()
|
||||
.find('.edit').hide();
|
||||
|
||||
$(target).addClass('pontoon-localizing');
|
||||
postMessage("ACTIVE", target.entity.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Disable editable mode
|
||||
*/
|
||||
function stopEditing() {
|
||||
var toolbar = $('.pontoon-editable-toolbar'),
|
||||
target = toolbar[0].target;
|
||||
|
||||
toolbar
|
||||
.children().hide().end()
|
||||
.find('.edit').show();
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(target)
|
||||
.removeClass('pontoon-localizing')
|
||||
.unbind('dblclick.pontoon');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handle messages from project code
|
||||
*/
|
||||
function receiveMessage(e) {
|
||||
if (fromTrustedSource(e)) {
|
||||
var message = JSON.parse(e.data);
|
||||
|
||||
switch (message.type) {
|
||||
|
||||
case "HOVER":
|
||||
Pontoon.entities[message.value].hover();
|
||||
break;
|
||||
|
||||
case "UNHOVER":
|
||||
Pontoon.entities[message.value].unhover();
|
||||
break;
|
||||
|
||||
case "NAVIGATE":
|
||||
// Stop editing old entity
|
||||
var target = $('.pontoon-editable-toolbar')[0].target;
|
||||
|
||||
if (target) {
|
||||
var entity = target.entity,
|
||||
string = entity.translation[0].string;
|
||||
|
||||
$(target)
|
||||
.removeClass('pontoon-localizing')
|
||||
.html(string !== null ? string : entity.original);
|
||||
hideToolbar(target);
|
||||
}
|
||||
|
||||
// Start editing new entity
|
||||
var entity = Pontoon.entities[message.value];
|
||||
|
||||
if (entity.body) {
|
||||
entity.hover();
|
||||
startEditing();
|
||||
}
|
||||
break;
|
||||
|
||||
case "SAVE":
|
||||
var entity = Pontoon.entities[message.value.id],
|
||||
translation = message.value.translation;
|
||||
|
||||
entity.translation[0].string = translation;
|
||||
|
||||
$(entity.node).each(function() {
|
||||
this.html(translation);
|
||||
});
|
||||
break;
|
||||
|
||||
case "DELETE":
|
||||
if (message.value.id) {
|
||||
var entity = Pontoon.entities[message.value.id];
|
||||
|
||||
$(entity.node).each(function() {
|
||||
this.html(entity.original);
|
||||
});
|
||||
|
||||
entity.translation[0].pk = null;
|
||||
entity.translation[0].string = null;
|
||||
entity.translation[0].approved = false;
|
||||
entity.translation[0].fuzzy = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "CANCEL":
|
||||
$('.pontoon-editable-toolbar > .cancel').click();
|
||||
break;
|
||||
|
||||
case "RESIZE":
|
||||
var toolbar = $('.pontoon-editable-toolbar'),
|
||||
node = toolbar[0].target;
|
||||
|
||||
if (node) {
|
||||
left = node.getBoundingClientRect().left + window.scrollX;
|
||||
toolbar.css('left', left);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "UPDATE-ATTRIBUTE":
|
||||
var object = message.value.object,
|
||||
attribute = message.value.attribute,
|
||||
value = message.value.value;
|
||||
|
||||
Pontoon[object][attribute] = value;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for main code messages
|
||||
window.addEventListener("message", receiveMessage, false);
|
||||
|
||||
// Inject toolbar stylesheet
|
||||
$('<link>', {
|
||||
rel: 'stylesheet',
|
||||
href: Pontoon.app.path + 'static/css/pontoon.css'
|
||||
}).appendTo('head');
|
||||
|
||||
// Disable links
|
||||
$('a').click(function(e) {
|
||||
if (!Pontoon.project.links) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Prepare editable toolbar
|
||||
var toolbar = $(
|
||||
"<div class='pontoon-editable-toolbar'>" +
|
||||
"<a href='#' class='edit'></a>" +
|
||||
"<a href='#' class='cancel'></a>" +
|
||||
"</div>").appendTo($('body'));
|
||||
|
||||
toolbar.hover(function () {
|
||||
showToolbar(this);
|
||||
}, function () {
|
||||
hideToolbar(this);
|
||||
})
|
||||
.find('.edit').click(function () {
|
||||
if (toolbar[0].target) {
|
||||
startEditing(true);
|
||||
}
|
||||
return false;
|
||||
}).end()
|
||||
.find('.cancel').click(function () {
|
||||
stopEditing();
|
||||
return false;
|
||||
});
|
||||
|
||||
$(window).scroll(function(e) {
|
||||
var toolbar = $('.pontoon-editable-toolbar'),
|
||||
target = toolbar[0].target;
|
||||
|
||||
if (target) {
|
||||
var top = target.getBoundingClientRect().top + window.scrollY,
|
||||
|
||||
toolbarTop = top - toolbar.outerHeight();
|
||||
toolbar.css('top', toolbarTop);
|
||||
}
|
||||
});
|
||||
|
||||
// Select appropriate way of loading entities
|
||||
var entities = Pontoon.entities;
|
||||
if (entities.length > 0) {
|
||||
|
||||
if (entities[0].format === 'ftl') {
|
||||
/*
|
||||
if (document.l10n) {
|
||||
document.l10n.get('main').interactive.then(function() {
|
||||
console.log($('[data-l10n-id]').length);
|
||||
});
|
||||
} else {
|
||||
console.log("FAIL");
|
||||
}
|
||||
*/
|
||||
var i = 0,
|
||||
interval = setInterval(function() {
|
||||
if (document.l10n || i > 100) {
|
||||
clearInterval(interval);
|
||||
setTimeout(function() {
|
||||
loadEntitiesL20n();
|
||||
}, 1500);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}, 100);
|
||||
|
||||
} else {
|
||||
var hooks = false;
|
||||
|
||||
// Detect hooks
|
||||
$(':not("script, style, iframe, noscript, [translate=\'no\']")').contents().filter(function () {
|
||||
return this.nodeType == 8;
|
||||
}).each(function (i, e) {
|
||||
if (e.nodeValue.indexOf('l10n') !== -1) {
|
||||
hooks = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// If available, use hooks to detect translatable nodes
|
||||
if (hooks) {
|
||||
loadEntitiesHooks();
|
||||
|
||||
// Otherwise match nodes against entities
|
||||
} else {
|
||||
loadEntitiesMatch();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loadEntitiesGuess();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load jQuery if not loaded yet
|
||||
function loadJquery() {
|
||||
if (!window.jQuery) {
|
||||
if (!jqueryAppended && document.body) {
|
||||
var script = document.createElement('script');
|
||||
script.src = '//pontoon.mozilla.org/static/js/lib/jquery-3.6.1.js';
|
||||
document.body.appendChild(script);
|
||||
|
||||
jqueryAppended = true;
|
||||
arguments.callee();
|
||||
|
||||
} else {
|
||||
window.setTimeout(arguments.callee, 100);
|
||||
}
|
||||
|
||||
} else {
|
||||
jqueryLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
function fromTrustedSource(e) {
|
||||
var trustedOrigins = {{ settings.JS_TRUSTED_ORIGINS | to_json() | safe }},
|
||||
trusted = trustedOrigins.indexOf(e.origin) > -1;
|
||||
|
||||
if (e.source === appWindow && trusted) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for main code trigger
|
||||
function initizalize(e) {
|
||||
if (fromTrustedSource(e)) {
|
||||
var message = JSON.parse(e.data);
|
||||
if (message.type === "ARE YOU READY?") {
|
||||
postMessage("READY", null, appWindow, '*');
|
||||
}
|
||||
|
||||
if (message.type === "INITIALIZE") {
|
||||
Pontoon = {
|
||||
app: {
|
||||
win: appWindow,
|
||||
path: message.value.path,
|
||||
},
|
||||
project: {
|
||||
win: window,
|
||||
url: window.location.href,
|
||||
title: document.title.split("-->")[1] || document.title,
|
||||
slug: message.value.slug,
|
||||
links: message.value.links
|
||||
},
|
||||
entities: message.value.entities,
|
||||
locale: message.value.locale,
|
||||
user: message.value.user
|
||||
};
|
||||
|
||||
loadJquery();
|
||||
window.removeEventListener("message", initizalize, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* window.postMessage improved
|
||||
*
|
||||
* messageType data type to be sent to the other window
|
||||
* messageValue data value to be sent to the other window
|
||||
* otherWindow reference to another window
|
||||
* targetOrigin specifies what the origin of otherWindow must be
|
||||
*/
|
||||
function postMessage(messageType, messageValue, otherWindow, targetOrigin) {
|
||||
var otherWindow = otherWindow || appWindow,
|
||||
targetOrigin = targetOrigin || Pontoon.app.path,
|
||||
message = {
|
||||
type: messageType,
|
||||
value: messageValue
|
||||
};
|
||||
otherWindow.postMessage(JSON.stringify(message), targetOrigin);
|
||||
}
|
||||
|
||||
var jqueryAppended = false;
|
||||
window.addEventListener("message", initizalize, false);
|
||||
})();
|
||||
|
||||
// When loaded inside web client, notify it and wait for messages
|
||||
var appWindow = window.opener || ((window !== window.top) ? window.top : undefined);
|
||||
if (appWindow) {
|
||||
appWindow.postMessage(JSON.stringify({
|
||||
type: "READY"
|
||||
}), '*');
|
||||
}
|
|
@ -77,7 +77,6 @@ class GroupFactory(DjangoModelFactory):
|
|||
class ProjectFactory(DjangoModelFactory):
|
||||
name = Sequence(lambda n: f"Project {n}")
|
||||
slug = LazyAttribute(lambda p: slugify(p.name))
|
||||
links = False
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
|
|
|
@ -4,7 +4,6 @@ from pontoon.base.models import ChangedEntityLocale, Entity, Project
|
|||
from pontoon.test.factories import (
|
||||
EntityFactory,
|
||||
ResourceFactory,
|
||||
SubpageFactory,
|
||||
TermFactory,
|
||||
TranslationFactory,
|
||||
)
|
||||
|
@ -17,7 +16,6 @@ def entity_test_models(translation_a, locale_b):
|
|||
|
||||
- 2 translations of a plural entity
|
||||
- 1 translation of a non-plural entity
|
||||
- A subpage that contains the plural entity
|
||||
"""
|
||||
|
||||
entity_a = translation_a.entity
|
||||
|
@ -57,12 +55,7 @@ def entity_test_models(translation_a, locale_b):
|
|||
active=True,
|
||||
string="Translation %s" % entity_b.string,
|
||||
)
|
||||
subpageX = SubpageFactory(
|
||||
project=project_a,
|
||||
name="Subpage",
|
||||
)
|
||||
subpageX.resources.add(entity_a.resource)
|
||||
return translation_a, translation_a_pl, translationX, subpageX
|
||||
return translation_a, translation_a_pl, translationX
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -249,7 +242,7 @@ def test_entity_project_locale_filter(admin, entity_test_models, locale_b, proje
|
|||
"""
|
||||
Evaluate entities filtering by locale, project, obsolete.
|
||||
"""
|
||||
tr0, tr0pl, trX, subpageX = entity_test_models
|
||||
tr0, tr0pl, trX = entity_test_models
|
||||
locale_a = tr0.locale
|
||||
resource0 = tr0.entity.resource
|
||||
project_a = tr0.entity.resource.project
|
||||
|
@ -274,7 +267,7 @@ def test_entity_project_locale_no_paths(
|
|||
If paths not specified, return all project entities along with their
|
||||
translations for locale.
|
||||
"""
|
||||
tr0, tr0pl, trX, subpageX = entity_test_models
|
||||
tr0, tr0pl, trX = entity_test_models
|
||||
locale_a = tr0.locale
|
||||
preferred_source_locale = ""
|
||||
entity_a = tr0.entity
|
||||
|
@ -344,7 +337,7 @@ def test_entity_project_locale_paths(admin, entity_test_models):
|
|||
If paths specified, return project entities from these paths only along
|
||||
with their translations for locale.
|
||||
"""
|
||||
tr0, tr0pl, trX, subpageX = entity_test_models
|
||||
tr0, tr0pl, trX = entity_test_models
|
||||
locale_a = tr0.locale
|
||||
preferred_source_locale = ""
|
||||
project_a = tr0.entity.resource.project
|
||||
|
@ -365,37 +358,6 @@ def test_entity_project_locale_paths(admin, entity_test_models):
|
|||
assert entities[0]["translation"][0]["string"] == trX.string
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_entity_project_locale_subpages(admin, entity_test_models):
|
||||
"""
|
||||
If paths specified as subpages, return project entities from paths
|
||||
assigned to these subpages only along with their translations for
|
||||
locale.
|
||||
"""
|
||||
tr0 = entity_test_models[0]
|
||||
subpageX = entity_test_models[3]
|
||||
locale_a = tr0.locale
|
||||
preferred_source_locale = ""
|
||||
entity_a = tr0.entity
|
||||
resource0 = tr0.entity.resource
|
||||
project_a = tr0.entity.resource.project
|
||||
subpages = [subpageX.name]
|
||||
entities = Entity.map_entities(
|
||||
locale_a,
|
||||
preferred_source_locale,
|
||||
Entity.for_project_locale(
|
||||
admin,
|
||||
project_a,
|
||||
locale_a,
|
||||
subpages,
|
||||
),
|
||||
)
|
||||
assert len(entities) == 1
|
||||
assert entities[0]["path"] == resource0.path
|
||||
assert entities[0]["original"] == entity_a.string
|
||||
assert entities[0]["translation"][0]["string"] == tr0.string
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_entity_project_locale_plurals(
|
||||
admin,
|
||||
|
@ -406,7 +368,7 @@ def test_entity_project_locale_plurals(
|
|||
"""
|
||||
For pluralized strings, return all available plural forms.
|
||||
"""
|
||||
tr0, tr0pl, trX, subpageX = entity_test_models
|
||||
tr0, tr0pl, trX = entity_test_models
|
||||
locale_a = tr0.locale
|
||||
preferred_source_locale = ""
|
||||
entity_a = tr0.entity
|
||||
|
|
|
@ -7,7 +7,6 @@ from pontoon.test.factories import (
|
|||
EntityFactory,
|
||||
LocaleFactory,
|
||||
ResourceFactory,
|
||||
SubpageFactory,
|
||||
TranslatedResourceFactory,
|
||||
)
|
||||
|
||||
|
@ -104,7 +103,7 @@ def test_locale_managers_group(locale_a, locale_b, user_a):
|
|||
@pytest.mark.django_db
|
||||
def test_locale_parts_stats_no_page_one_resource(locale_parts):
|
||||
"""
|
||||
Return resource paths and stats if no subpage and one resource defined.
|
||||
Return resource paths and stats if one resource defined.
|
||||
"""
|
||||
locale_c, locale_b, entityX = locale_parts
|
||||
project = entityX.resource.project
|
||||
|
@ -149,56 +148,3 @@ def test_locale_parts_stats_no_page_multiple_resources(locale_parts):
|
|||
assert len(detailsY) == 2
|
||||
assert detailsY[0]["title"] == "/other/path.po"
|
||||
assert detailsY[0]["unreviewed_strings"] == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_locale_parts_stats_pages_not_tied_to_resources(locale_parts):
|
||||
"""
|
||||
Return subpage name and stats.
|
||||
"""
|
||||
locale_a, locale_b, entity_a = locale_parts
|
||||
project = entity_a.resource.project
|
||||
SubpageFactory.create(project=project, name="Subpage")
|
||||
details = locale_a.parts_stats(project)
|
||||
assert details[0]["title"] == "Subpage"
|
||||
assert details[0]["unreviewed_strings"] == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_locale_parts_stats_pages_tied_to_resources(locale_parts):
|
||||
"""
|
||||
Return subpage name and stats for locales resources are available for.
|
||||
"""
|
||||
locale_a, locale_b, entity_a = locale_parts
|
||||
project = entity_a.resource.project
|
||||
resourceX = ResourceFactory.create(
|
||||
project=project,
|
||||
path="/other/path.po",
|
||||
)
|
||||
EntityFactory.create(resource=resourceX, string="Entity X")
|
||||
TranslatedResourceFactory.create(
|
||||
resource=resourceX,
|
||||
locale=locale_a,
|
||||
)
|
||||
TranslatedResourceFactory.create(
|
||||
resource=resourceX,
|
||||
locale=locale_b,
|
||||
)
|
||||
sub1 = SubpageFactory.create(
|
||||
project=project,
|
||||
name="Subpage",
|
||||
)
|
||||
sub1.resources.add(resourceX)
|
||||
sub2 = SubpageFactory.create(
|
||||
project=project,
|
||||
name="Other Subpage",
|
||||
)
|
||||
sub2.resources.add(resourceX)
|
||||
details0 = locale_a.parts_stats(project)
|
||||
detailsX = locale_b.parts_stats(project)
|
||||
assert details0[0]["title"] == "Other Subpage"
|
||||
assert details0[0]["unreviewed_strings"] == 0
|
||||
assert details0[1]["title"] == "Subpage"
|
||||
assert details0[1]["unreviewed_strings"] == 0
|
||||
assert detailsX[0]["title"] == "Other Subpage"
|
||||
assert detailsX[0]["unreviewed_strings"] == 0
|
||||
|
|
|
@ -188,25 +188,13 @@ def _download_file(prefixes, dirnames, vcs_project, relative_path):
|
|||
return temp.name
|
||||
|
||||
|
||||
def _get_relative_path_from_part(slug, part):
|
||||
"""Check if part is a Resource path or Subpage name."""
|
||||
# Avoid circular import; someday we should refactor to avoid.
|
||||
from pontoon.base.models import Subpage
|
||||
|
||||
try:
|
||||
subpage = Subpage.objects.get(project__slug=slug, name=part)
|
||||
return subpage.resources.first().path
|
||||
except Subpage.DoesNotExist:
|
||||
return part
|
||||
|
||||
|
||||
def get_download_content(slug, code, part):
|
||||
"""
|
||||
Get content of the file to be downloaded.
|
||||
|
||||
:arg str slug: Project slug.
|
||||
:arg str code: Locale code.
|
||||
:arg str part: Resource path or Subpage name.
|
||||
:arg str part: Resource path.
|
||||
"""
|
||||
# Avoid circular import; someday we should refactor to avoid.
|
||||
from pontoon.sync import formats
|
||||
|
@ -229,10 +217,7 @@ def get_download_content(slug, code, part):
|
|||
|
||||
# Download a single file if project has 1 or >= 10 resources
|
||||
else:
|
||||
relative_path = _get_relative_path_from_part(slug, part)
|
||||
resources = [
|
||||
get_object_or_404(Resource, project__slug=slug, path=relative_path)
|
||||
]
|
||||
resources = [get_object_or_404(Resource, project__slug=slug, path=part)]
|
||||
|
||||
locale_prefixes = project.repositories
|
||||
|
||||
|
@ -331,7 +316,7 @@ def handle_upload_content(slug, code, part, f, user):
|
|||
|
||||
:arg str slug: Project slug.
|
||||
:arg str code: Locale code.
|
||||
:arg str part: Resource path or Subpage name.
|
||||
:arg str part: Resource path.
|
||||
:arg UploadedFile f: UploadedFile instance.
|
||||
:arg User user: User uploading the file.
|
||||
"""
|
||||
|
@ -348,10 +333,9 @@ def handle_upload_content(slug, code, part, f, user):
|
|||
Translation,
|
||||
)
|
||||
|
||||
relative_path = _get_relative_path_from_part(slug, part)
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
locale = get_object_or_404(Locale, code=code)
|
||||
resource = get_object_or_404(Resource, project__slug=slug, path=relative_path)
|
||||
resource = get_object_or_404(Resource, project__slug=slug, path=part)
|
||||
|
||||
# Store uploaded file to a temporary file and parse it
|
||||
extension = os.path.splitext(f.name)[1]
|
||||
|
@ -370,7 +354,7 @@ def handle_upload_content(slug, code, part, f, user):
|
|||
)
|
||||
entities_qs = (
|
||||
Entity.objects.filter(
|
||||
resource__project=project, resource__path=relative_path, obsolete=False
|
||||
resource__project=project, resource__path=part, obsolete=False
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
|
|
|
@ -96,29 +96,7 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Section-5: In-context localization -->
|
||||
<section id="section-5" class="section">
|
||||
<div class="container">
|
||||
<div class="content-wrapper flex-col-2 flex-direction-col">
|
||||
<h2>One more thing.</h2>
|
||||
<p>
|
||||
One of the coolest features of Pontoon
|
||||
is localizing web content in-place,
|
||||
on the web page itself.
|
||||
The moment you submit a translation,
|
||||
it replaces the original text in the web page,
|
||||
making you the first proofreader and tester.
|
||||
Talk about context?
|
||||
</p>
|
||||
<a class="button primary" href="{% url 'pontoon.translate.locale.agnostic' 'pontoon-intro' 'messages.properties' %}">Try In-context Localization</a>
|
||||
</div>
|
||||
<div class="image-wrapper flex-col-2">
|
||||
<img class="section-side-image-3" src="static/img/section-5.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Section-6: Final -->
|
||||
<!-- Section-5: Final -->
|
||||
<section id="section-6" class="section">
|
||||
<div class="container">
|
||||
<div class="content-wrapper flex-direction-col">
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
This is a simple in-context localization demo page of [Pontoon](https://github.com/mozilla/pontoon).
|
|
@ -1,834 +0,0 @@
|
|||
/*!
|
||||
* Start Bootstrap - Agency Bootstrap Theme (http://startbootstrap.com)
|
||||
* Code licensed under the Apache License v2.0.
|
||||
* For details, see http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*/
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #FD5F60;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
p.large {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a,
|
||||
a:hover,
|
||||
a:focus,
|
||||
a:active,
|
||||
a.active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #FD5F60;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus,
|
||||
a:active,
|
||||
a.active {
|
||||
color: #4FC4F6;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.img-centered {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.bg-light-gray {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.bg-darkest-gray {
|
||||
background-color: #3F4752;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
border-color: #FD5F60;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
.btn-primary:hover,
|
||||
.btn-primary:focus,
|
||||
.btn-primary:active,
|
||||
.btn-primary.active,
|
||||
.open .dropdown-toggle.btn-primary {
|
||||
border-color: #4FC4F6;
|
||||
color: #fff;
|
||||
background-color: #4FC4F6;
|
||||
}
|
||||
|
||||
.btn-primary:active,
|
||||
.btn-primary.active,
|
||||
.open .dropdown-toggle.btn-primary {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.btn-primary.disabled,
|
||||
.btn-primary[disabled],
|
||||
fieldset[disabled] .btn-primary,
|
||||
.btn-primary.disabled:hover,
|
||||
.btn-primary[disabled]:hover,
|
||||
fieldset[disabled] .btn-primary:hover,
|
||||
.btn-primary.disabled:focus,
|
||||
.btn-primary[disabled]:focus,
|
||||
fieldset[disabled] .btn-primary:focus,
|
||||
.btn-primary.disabled:active,
|
||||
.btn-primary[disabled]:active,
|
||||
fieldset[disabled] .btn-primary:active,
|
||||
.btn-primary.disabled.active,
|
||||
.btn-primary[disabled].active,
|
||||
fieldset[disabled] .btn-primary.active {
|
||||
border-color: #FD5F60;
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
.btn-primary .badge {
|
||||
color: #FD5F60;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn-xl {
|
||||
padding: 20px 40px;
|
||||
border-color: #FD5F60;
|
||||
border-radius: 3px;
|
||||
text-transform: uppercase;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
.btn-xl:hover,
|
||||
.btn-xl:focus,
|
||||
.btn-xl:active,
|
||||
.btn-xl.active,
|
||||
.open .dropdown-toggle.btn-xl {
|
||||
border-color: #4FC4F6;
|
||||
color: #fff;
|
||||
background-color: #4FC4F6;
|
||||
}
|
||||
|
||||
.btn-xl:active,
|
||||
.btn-xl.active,
|
||||
.open .dropdown-toggle.btn-xl {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.btn-xl.disabled,
|
||||
.btn-xl[disabled],
|
||||
fieldset[disabled] .btn-xl,
|
||||
.btn-xl.disabled:hover,
|
||||
.btn-xl[disabled]:hover,
|
||||
fieldset[disabled] .btn-xl:hover,
|
||||
.btn-xl.disabled:focus,
|
||||
.btn-xl[disabled]:focus,
|
||||
fieldset[disabled] .btn-xl:focus,
|
||||
.btn-xl.disabled:active,
|
||||
.btn-xl[disabled]:active,
|
||||
fieldset[disabled] .btn-xl:active,
|
||||
.btn-xl.disabled.active,
|
||||
.btn-xl[disabled].active,
|
||||
fieldset[disabled] .btn-xl.active {
|
||||
border-color: #FD5F60;
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
.btn-xl .badge {
|
||||
color: #FD5F60;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.navbar-default {
|
||||
border-color: transparent;
|
||||
background-color: #3F4752;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-brand {
|
||||
color: #FFFFFF;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-brand:hover,
|
||||
.navbar-default .navbar-brand:focus,
|
||||
.navbar-default .navbar-brand:active,
|
||||
.navbar-default .navbar-brand.active {
|
||||
color: #FD5F60;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-collapse {
|
||||
border-color: rgba(255,255,255,.02);
|
||||
}
|
||||
|
||||
.navbar-default .navbar-toggle {
|
||||
border-color: #FD5F60;
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-toggle .icon-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-toggle:hover,
|
||||
.navbar-default .navbar-toggle:focus {
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
.navbar-default .nav li a {
|
||||
text-transform: uppercase;
|
||||
font-weight: 100;
|
||||
letter-spacing: 1px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.navbar-default .nav li a:hover,
|
||||
.navbar-default .nav li a:focus {
|
||||
outline: 0;
|
||||
color: #FD5F60;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav>.active>a {
|
||||
border-radius: 0;
|
||||
color: #fff;
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav>.active>a:hover,
|
||||
.navbar-default .navbar-nav>.active>a:focus {
|
||||
color: #fff;
|
||||
background-color: #4FC4F6;
|
||||
}
|
||||
|
||||
@media(min-width:768px) {
|
||||
.navbar-default {
|
||||
padding: 10px 0;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-brand {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav>.active>a {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.navbar-default.navbar-shrink {
|
||||
background-color: #3F4752;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background: #3F4752;
|
||||
}
|
||||
|
||||
header .wrapper img {
|
||||
position: absolute;
|
||||
margin: 0 auto;
|
||||
top: 10px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
header .intro-text {
|
||||
padding-top: 100px;
|
||||
padding-bottom: 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
header .intro-text .intro-lead-in {
|
||||
margin-bottom: 25px;
|
||||
font-size: 22px;
|
||||
font-weight: 100;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
header .intro-text .intro-heading {
|
||||
margin-bottom: 25px;
|
||||
text-transform: uppercase;
|
||||
font-size: 50px;
|
||||
font-weight: 400;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
@media(min-width:768px) {
|
||||
header .intro-text {
|
||||
padding-top: 200px;
|
||||
padding-bottom: 150px;
|
||||
}
|
||||
|
||||
header .intro-text .intro-lead-in {
|
||||
margin-bottom: 25px;
|
||||
font-size: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
header .intro-text .intro-heading {
|
||||
margin-bottom: 50px;
|
||||
font-size: 70px;
|
||||
line-height: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width:992px) {
|
||||
header .intro-text .intro-heading {
|
||||
font-size: 85px;
|
||||
line-height: 85px;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 100px 0;
|
||||
}
|
||||
|
||||
section h2.section-heading {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
section h3.section-subheading {
|
||||
margin-bottom: 75px;
|
||||
text-transform: none;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@media(min-width:768px) {
|
||||
section {
|
||||
padding: 150px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrap {
|
||||
background: #fd5f60;
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.icon-wrap.expand {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.service-heading {
|
||||
margin: 15px 0;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item {
|
||||
right: 0;
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-link {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-link .portfolio-hover {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
background: rgba(254,209,54,.9);
|
||||
-webkit-transition: all ease .5s;
|
||||
-moz-transition: all ease .5s;
|
||||
transition: all ease .5s;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-link .portfolio-hover:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
margin-top: -12px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content i {
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content h3,
|
||||
#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item img {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-caption {
|
||||
margin: 0 auto;
|
||||
padding: 25px;
|
||||
max-width: 360px;
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-caption h4 {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
#portfolio .portfolio-item .portfolio-caption p {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
#portfolio * {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@media(min-width:767px) {
|
||||
#portfolio .portfolio-item {
|
||||
margin: 0 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.timeline:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 40px;
|
||||
width: 2px;
|
||||
margin-left: -1.5px;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.timeline>li {
|
||||
position: relative;
|
||||
margin-bottom: 50px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.timeline>li:before,
|
||||
.timeline>li:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.timeline>li:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-panel {
|
||||
float: right;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 0 20px 0 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-panel:before {
|
||||
right: auto;
|
||||
left: -15px;
|
||||
border-right-width: 15px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-panel:after {
|
||||
right: auto;
|
||||
left: -14px;
|
||||
border-right-width: 14px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-image {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-left: 0;
|
||||
border: 7px solid #f1f1f1;
|
||||
border-radius: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-image h4 {
|
||||
margin-top: 26px;
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.timeline>li.timeline-inverted>.timeline-panel {
|
||||
float: right;
|
||||
padding: 0 20px 0 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.timeline>li.timeline-inverted>.timeline-panel:before {
|
||||
right: auto;
|
||||
left: -15px;
|
||||
border-right-width: 15px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.timeline>li.timeline-inverted>.timeline-panel:after {
|
||||
right: auto;
|
||||
left: -14px;
|
||||
border-right-width: 14px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.timeline>li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.timeline .timeline-heading h4 {
|
||||
margin-top: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.timeline .timeline-heading h4.subheading {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.timeline .timeline-body>p,
|
||||
.timeline .timeline-body>ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media(min-width:768px) {
|
||||
.timeline:before {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.timeline>li {
|
||||
margin-bottom: 100px;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-panel {
|
||||
float: left;
|
||||
width: 41%;
|
||||
padding: 0 20px 20px 30px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-image {
|
||||
left: 50%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-left: -50px;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-image h4 {
|
||||
margin-top: 34px;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.timeline>li.timeline-inverted>.timeline-panel {
|
||||
float: right;
|
||||
padding: 0 30px 20px 20px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width:992px) {
|
||||
.timeline>li {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-panel {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-image {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin-left: -75px;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-image h4 {
|
||||
margin-top: 55px;
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.timeline>li.timeline-inverted>.timeline-panel {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width:1200px) {
|
||||
.timeline>li {
|
||||
min-height: 170px;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-panel {
|
||||
padding: 0 20px 20px 100px;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-image {
|
||||
width: 170px;
|
||||
height: 170px;
|
||||
margin-left: -85px;
|
||||
}
|
||||
|
||||
.timeline>li .timeline-image h4 {
|
||||
margin-top: 65px;
|
||||
}
|
||||
|
||||
.timeline>li.timeline-inverted>.timeline-panel {
|
||||
padding: 0 100px 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#team {
|
||||
background: #272A2F;
|
||||
padding: 100px 0 50px;
|
||||
}
|
||||
|
||||
#team h2 {
|
||||
color: #DDDDDD;
|
||||
}
|
||||
|
||||
.team-member {
|
||||
margin-bottom: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team-member > img {
|
||||
background: #F1F1F1;
|
||||
border: 7px solid #F1F1F1;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.team-member h4 {
|
||||
color: #DDDDDD;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 0;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.team-member p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.team-member .fa {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.team-member ul.social-buttons li a {
|
||||
background: #7BC876;
|
||||
-webkit-transition: all .3s;
|
||||
-moz-transition: all .3s;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.team-member ul.social-buttons li a:hover {
|
||||
background-color: #4FC4F6;
|
||||
}
|
||||
|
||||
.team-member ul.social-buttons li a i {
|
||||
color: #111111;
|
||||
}
|
||||
|
||||
aside.clients img {
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #3F4752;
|
||||
font-size: 14px;
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer span.copyright {
|
||||
color: #FFFFFF;
|
||||
text-transform: uppercase;
|
||||
text-transform: none;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
footer ul.quicklinks {
|
||||
margin-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
text-transform: none;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
ul.social-buttons {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ul.social-buttons li a {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 100%;
|
||||
font-size: 20px;
|
||||
line-height: 40px;
|
||||
outline: 0;
|
||||
color: #fff;
|
||||
background-color: #272A2F;
|
||||
-webkit-transition: all .3s;
|
||||
-moz-transition: all .3s;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
ul.social-buttons li a:hover,
|
||||
ul.social-buttons li a:focus,
|
||||
ul.social-buttons li a:active {
|
||||
background-color: #FD5F60;
|
||||
}
|
||||
|
||||
ul.social-buttons li a img {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.btn:focus,
|
||||
.btn:active,
|
||||
.btn.active,
|
||||
.btn:active:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.portfolio-modal .modal-content {
|
||||
padding: 100px 0;
|
||||
min-height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
background-clip: border-box;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.portfolio-modal .modal-content h2 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
.portfolio-modal .modal-content p {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.portfolio-modal .modal-content p.item-intro {
|
||||
margin: 20px 0 30px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.portfolio-modal .modal-content ul.list-inline {
|
||||
margin-top: 0;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.portfolio-modal .modal-content img {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.portfolio-modal .close-modal {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 25px;
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.portfolio-modal .close-modal:hover {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
.portfolio-modal .close-modal .lr {
|
||||
z-index: 1051;
|
||||
width: 1px;
|
||||
height: 75px;
|
||||
margin-left: 35px;
|
||||
background-color: #3F4752;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.portfolio-modal .close-modal .lr .rl {
|
||||
z-index: 1052;
|
||||
width: 1px;
|
||||
height: 75px;
|
||||
background-color: #3F4752;
|
||||
-webkit-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #FED271;
|
||||
}
|
||||
|
||||
::selection {
|
||||
text-shadow: none;
|
||||
background: #FED271;
|
||||
}
|
||||
|
||||
img::selection {
|
||||
background: 0 0;
|
||||
}
|
||||
|
||||
img::-moz-selection {
|
||||
background: 0 0;
|
||||
}
|
||||
|
||||
body {
|
||||
webkit-tap-highlight-color: #FED271;
|
||||
}
|
Двоичные данные
pontoon/in_context/static/img/facebook.png
До Ширина: | Высота: | Размер: 291 B |
Двоичные данные
pontoon/in_context/static/img/how/1.png
До Ширина: | Высота: | Размер: 15 KiB |
Двоичные данные
pontoon/in_context/static/img/how/2.png
До Ширина: | Высота: | Размер: 12 KiB |
Двоичные данные
pontoon/in_context/static/img/how/3.png
До Ширина: | Высота: | Размер: 7.2 KiB |
Двоичные данные
pontoon/in_context/static/img/how/4.png
До Ширина: | Высота: | Размер: 14 KiB |
Двоичные данные
pontoon/in_context/static/img/map-image.png
До Ширина: | Высота: | Размер: 338 KiB |
Двоичные данные
pontoon/in_context/static/img/twitter.png
До Ширина: | Высота: | Размер: 471 B |
Двоичные данные
pontoon/in_context/static/img/what/fa-comments-o.png
До Ширина: | Высота: | Размер: 1.7 KiB |
Двоичные данные
pontoon/in_context/static/img/what/fa-expand.png
До Ширина: | Высота: | Размер: 608 B |
Двоичные данные
pontoon/in_context/static/img/what/fa-eye.png
До Ширина: | Высота: | Размер: 1.6 KiB |
Двоичные данные
pontoon/in_context/static/img/youtube.png
До Ширина: | Высота: | Размер: 508 B |
|
@ -1,26 +0,0 @@
|
|||
/*!
|
||||
* Start Bootstrap - Agnecy Bootstrap Theme (http://startbootstrap.com)
|
||||
* Code licensed under the Apache License v2.0.
|
||||
* For details, see http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*/
|
||||
|
||||
// jQuery for page scrolling feature
|
||||
$(function() {
|
||||
$('a.page-scroll').bind('click', function(event) {
|
||||
var $anchor = $(this);
|
||||
$('html, body').stop().animate({
|
||||
scrollTop: $($anchor.attr('href')).offset().top
|
||||
}, 1500);
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
// Highlight the top nav as scrolling occurs
|
||||
$('body').scrollspy({
|
||||
target: '.navbar-fixed-top'
|
||||
});
|
||||
|
||||
// Closes the Responsive Menu on Menu Item Click
|
||||
$('.navbar-collapse ul li a').click(function() {
|
||||
$('.navbar-toggle:visible').click();
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
/**
|
||||
* cbpAnimatedHeader.min.js v1.0.0
|
||||
* http://www.codrops.com
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Copyright 2013, Codrops
|
||||
* http://www.codrops.com
|
||||
*/
|
||||
var cbpAnimatedHeader=function(){function i(){window.addEventListener("scroll",function(e){if(!n){n=true;setTimeout(s,250)}},false)}function s(){var e=o();if(e>=r){$(t).addClass("navbar-shrink")}else{$(t).removeClass("navbar-shrink")}n=false}function o(){return window.pageYOffset||e.scrollTop}var e=document.documentElement,t=document.querySelector(".navbar-default"),n=false,r=100;i()}()
|
|
@ -1,213 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Pontoon Intro">
|
||||
<meta name="author" content="Mozilla">
|
||||
|
||||
<title data-l10n-id="title">Pontoon Intro</title>
|
||||
<base target="_parent" />
|
||||
|
||||
{% stylesheet 'in_context' %}
|
||||
</head>
|
||||
|
||||
<body id="page-top" class="index">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-default navbar-fixed-top">
|
||||
<div class="container">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header page-scroll">
|
||||
<a class="navbar-brand page-scroll" href="#page-top" data-l10n-id="navigation-title">Pontoon by Mozilla</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.container-fluid -->
|
||||
</nav>
|
||||
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<div class="wrapper">
|
||||
<img src="{{ static('img/map-image.png') }}" alt="Map of the World">
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="intro-text">
|
||||
<div class="intro-lead-in" data-l10n-id="upper-title">In-context localization</div>
|
||||
<div class="intro-heading">
|
||||
<div data-l10n-id="headline-1">Localize the web.</div>
|
||||
<div data-l10n-id="headline-2">In place.</div>
|
||||
</div>
|
||||
<a href="#services" class="page-scroll btn btn-xl" data-l10n-id="call-to-action">Tell Me More</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- What Section -->
|
||||
<section id="services" class="bg-light-gray">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading" data-l10n-id="what-title">What is in-context localization?</h2>
|
||||
<h3 class="section-subheading text-muted" data-l10n-id="what-desc">It allows you to localize web content in place, with context and spatial limitations right in front of you.</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center">
|
||||
<div class="col-md-4">
|
||||
<div class="icon-wrap">
|
||||
<img src="{{ static('img/what/fa-comments-o.png') }}" alt="Context icon">
|
||||
</div>
|
||||
<h4 class="service-heading" data-l10n-id="context">Understand context</h4>
|
||||
<p class="text-muted" data-l10n-id="context-desc">By localizing web page on the page itself, you no longer need to worry if the word you are translating is a verb or noun.</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="icon-wrap expand">
|
||||
<img src="{{ static('img/what/fa-expand.png') }}" alt="Spatial limitations icon">
|
||||
</div>
|
||||
<h4 class="service-heading" data-l10n-id="space">See spatial limitations</h4>
|
||||
<p class="text-muted" data-l10n-id="space-desc">Avoid breaking user interface by seeing how much space is available for your translations, which is especially useful with apps.</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="icon-wrap">
|
||||
<img src="{{ static('img/what/fa-eye.png') }}" alt="Preview icon">
|
||||
</div>
|
||||
<h4 class="service-heading" data-l10n-id="preview">Get instant preview</h4>
|
||||
<p class="text-muted" data-l10n-id="preview-desc">The moment you submit translation, it replaces the original text in the web page, making you the first proofreader and tester.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How Section -->
|
||||
<section id="about">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading" data-l10n-id="how-title">How does it work?</h2>
|
||||
<h3 class="section-subheading text-muted" data-l10n-id="how-desc">In-context localization is a very simple and intuitive tool that requires little to no technical skill for localizers to use.</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<ul class="timeline">
|
||||
<li>
|
||||
<div class="timeline-image">
|
||||
<img class="img-circle img-responsive" src="{{ static('img/how/1.png') }}" alt="">
|
||||
</div>
|
||||
<div class="timeline-panel">
|
||||
<div class="timeline-heading">
|
||||
<h4 data-l10n-id="hover">Hover</h4>
|
||||
<h4 class="subheading" data-l10n-id="hover-sub">over web content</h4>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<p class="text-muted" data-l10n-id="hover-desc">Move your mouse over headings, links, paragraphs or other text blocks on this page. A dashed rectangle will appear around each of these blocks, marking strings that are available for localization on the page itself.</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="timeline-inverted">
|
||||
<div class="timeline-image">
|
||||
<img class="img-circle img-responsive" src="{{ static('img/how/2.png') }}" alt="">
|
||||
</div>
|
||||
<div class="timeline-panel">
|
||||
<div class="timeline-heading">
|
||||
<h4 data-l10n-id="select">Select</h4>
|
||||
<h4 class="subheading" data-l10n-id="select-sub">a text block</h4>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<p class="text-muted" data-l10n-id="select-desc">A toolbar appears above the dashed rectangle, allowing you to select the corresponding text block for editing. To do so, either click on the Edit button in the toolbar, or double-click anywhere inside the dashed border.</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="timeline-image">
|
||||
<img class="img-circle img-responsive" src="{{ static('img/how/3.png') }}" alt="">
|
||||
</div>
|
||||
<div class="timeline-panel">
|
||||
<div class="timeline-heading">
|
||||
<h4 data-l10n-id="translate">Translate</h4>
|
||||
<h4 class="subheading" data-l10n-id="translate-sub">selected text</h4>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<p class="text-muted" data-l10n-id="translate-desc">When you enter editing mode, the entire text block will be selected. You can start typing your translation immediately while seeing the exact context of your source string and how much space is available for your translation.</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="timeline-inverted">
|
||||
<div class="timeline-image">
|
||||
<img class="img-circle img-responsive" src="{{ static('img/how/4.png') }}" alt="">
|
||||
</div>
|
||||
<div class="timeline-panel">
|
||||
<div class="timeline-heading">
|
||||
<h4 data-l10n-id="save">Save</h4>
|
||||
<h4 class="subheading" data-l10n-id="save-sub">your translation</h4>
|
||||
</div>
|
||||
<div class="timeline-body">
|
||||
<p class="text-muted" data-l10n-id="save-desc">As soon as you are happy with your translation, you can save it by pressing Enter or clicking the save icon in the toolbar. To quit translation mode without saving changes, press Esc or click the cancel icon in the toolbar.</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="timeline-inverted">
|
||||
<span class="timeline-image link">
|
||||
<h4 data-l10n-id="profit">That's it!</h4>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<span class="copyright" data-l10n-id="author">Crafted by Mozilla</span>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<ul class="list-inline social-buttons">
|
||||
<li>
|
||||
<a href="https://twitter.com/firefox">
|
||||
<img src="{{ static('img/twitter.png') }}" alt="Twitter icon">
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.facebook.com/Firefox">
|
||||
<img src="{{ static('img/facebook.png') }}" alt="Firefox icon">
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.youtube.com/firefoxchannel">
|
||||
<img src="{{ static('img/youtube.png') }}" alt="Youtube icon">
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<ul class="list-inline quicklinks">
|
||||
<li><a href="https://www.mozilla.org/contribute/" data-l10n-id="join-us">Join us</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="{{ static('js/lib/jquery-3.6.1.js') }}"></script>
|
||||
|
||||
<!-- Enable in-context localization with Pontoon -->
|
||||
<script src="{{ settings.SITE_URL + '/pontoon.js' }}"></script>
|
||||
|
||||
<!-- Bootstrap Core JavaScript -->
|
||||
<script src="{{ static('js/bootstrap.min.js') }}"></script>
|
||||
|
||||
<!-- Plugin JavaScript -->
|
||||
<script src="{{ static('js/cbpAnimatedHeader.min.js') }}"></script>
|
||||
|
||||
<!-- Custom Theme JavaScript -->
|
||||
<script src="{{ static('js/agency.js') }}"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||
from django.urls import path
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
# In-context demo
|
||||
path(
|
||||
"in-context/",
|
||||
views.in_context,
|
||||
name="pontoon.in_context",
|
||||
),
|
||||
# Legacy: Redirect to /in-context
|
||||
path(
|
||||
"intro/",
|
||||
RedirectView.as_view(pattern_name="pontoon.in_context", permanent=True),
|
||||
),
|
||||
]
|
|
@ -1,7 +0,0 @@
|
|||
from django.shortcuts import render
|
||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||
|
||||
|
||||
@xframe_options_sameorigin
|
||||
def in_context(request):
|
||||
return render(request, "in_context.html")
|
|
@ -55,9 +55,7 @@ def ajax_resources(request, code, slug):
|
|||
"""Resources tab."""
|
||||
locale = get_object_or_404(Locale, code=code)
|
||||
project = get_object_or_404(
|
||||
Project.objects.visible_for(request.user)
|
||||
.available()
|
||||
.prefetch_related("subpage_set"),
|
||||
Project.objects.visible_for(request.user).available(),
|
||||
slug=slug,
|
||||
)
|
||||
|
||||
|
@ -69,29 +67,8 @@ def ajax_resources(request, code, slug):
|
|||
if not len(translatedresources_qs):
|
||||
raise Http404
|
||||
|
||||
pages = {}
|
||||
for page in project.subpage_set.all():
|
||||
latest_page_translatedresource = None
|
||||
page_translatedresources_qs = TranslatedResource.objects.filter(
|
||||
resource__in=page.resources.all(), locale=locale
|
||||
).prefetch_related("resource", "latest_translation__user")
|
||||
|
||||
for page_translatedresource in page_translatedresources_qs:
|
||||
latest = (
|
||||
latest_page_translatedresource.latest_translation
|
||||
if latest_page_translatedresource
|
||||
else None
|
||||
)
|
||||
if latest is None or (
|
||||
page_translatedresource.latest_translation.latest_activity["date"]
|
||||
> latest.latest_activity["date"]
|
||||
):
|
||||
latest_page_translatedresource = page_translatedresource
|
||||
|
||||
pages[page.name] = latest_page_translatedresource
|
||||
|
||||
translatedresources = {s.resource.path: s for s in translatedresources_qs}
|
||||
translatedresources = dict(list(translatedresources.items()) + list(pages.items()))
|
||||
translatedresources = dict(list(translatedresources.items()))
|
||||
parts = locale.parts_stats(project)
|
||||
|
||||
resource_priority_map = project.resource_priority_map()
|
||||
|
|
|
@ -148,7 +148,6 @@ INSTALLED_APPS = (
|
|||
"pontoon.base",
|
||||
"pontoon.contributors",
|
||||
"pontoon.checks",
|
||||
"pontoon.in_context",
|
||||
"pontoon.insights",
|
||||
"pontoon.localizations",
|
||||
"pontoon.machinery",
|
||||
|
@ -411,13 +410,6 @@ PIPELINE_CSS = {
|
|||
),
|
||||
"output_filename": "css/contributors.min.css",
|
||||
},
|
||||
"in_context": {
|
||||
"source_filenames": (
|
||||
"css/bootstrap.min.css",
|
||||
"css/agency.css",
|
||||
),
|
||||
"output_filename": "css/in_context.min.css",
|
||||
},
|
||||
"terms": {
|
||||
"source_filenames": ("css/terms.css",),
|
||||
"output_filename": "css/terms.min.css",
|
||||
|
@ -848,10 +840,9 @@ CELERY_ACCEPT_CONTENT = ["pickle"]
|
|||
|
||||
# Settings related to the CORS mechanisms.
|
||||
# For the sake of integration with other sites,
|
||||
# some of javascript files (e.g. pontoon.js)
|
||||
# require Access-Control-Allow-Origin header to be set as '*'.
|
||||
# all origins are allowed for the GraphQL endpoint.
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
CORS_URLS_REGEX = r"^/(pontoon\.js|graphql/?)$"
|
||||
CORS_URLS_REGEX = r"^/graphql/?$"
|
||||
|
||||
SOCIALACCOUNT_ENABLED = True
|
||||
SOCIALACCOUNT_ADAPTER = "pontoon.base.adapter.PontoonSocialAdapter"
|
||||
|
@ -908,14 +899,6 @@ SOCIALACCOUNT_PROVIDERS = {
|
|||
},
|
||||
}
|
||||
|
||||
# Defined all trusted origins that will be returned in pontoon.js file.
|
||||
if os.environ.get("JS_TRUSTED_ORIGINS"):
|
||||
JS_TRUSTED_ORIGINS = os.environ.get("JS_TRUSTED_ORIGINS").split(",")
|
||||
else:
|
||||
JS_TRUSTED_ORIGINS = [
|
||||
SITE_URL,
|
||||
]
|
||||
|
||||
# Configuration of `django-notifications-hq` app
|
||||
DJANGO_NOTIFICATIONS_CONFIG = {
|
||||
# Attach extra arguments passed to notify.send(...) to the .data attribute
|
||||
|
|
|
@ -14,7 +14,6 @@ from pontoon.base.models import (
|
|||
ProjectLocale,
|
||||
Repository,
|
||||
Resource,
|
||||
Subpage,
|
||||
TranslatedResource,
|
||||
Translation,
|
||||
TranslationMemoryEntry,
|
||||
|
@ -42,7 +41,6 @@ class GroupFactory(DjangoModelFactory):
|
|||
class ProjectFactory(DjangoModelFactory):
|
||||
name = Sequence(lambda n: f"Project {n}")
|
||||
slug = LazyAttribute(lambda p: slugify(p.name))
|
||||
links = False
|
||||
visibility = Project.Visibility.PUBLIC
|
||||
|
||||
class Meta:
|
||||
|
@ -93,14 +91,6 @@ class ResourceFactory(DjangoModelFactory):
|
|||
model = Resource
|
||||
|
||||
|
||||
class SubpageFactory(DjangoModelFactory):
|
||||
project = SubFactory(ProjectFactory)
|
||||
name = Sequence(lambda n: "subpage%s" % n)
|
||||
|
||||
class Meta:
|
||||
model = Subpage
|
||||
|
||||
|
||||
class LocaleFactory(DjangoModelFactory):
|
||||
code = Sequence(lambda n: f"en-{n}")
|
||||
name = Sequence(lambda n: f"English #{n}")
|
||||
|
|
|
@ -13,10 +13,6 @@ class LocaleConverter(StringConverter):
|
|||
|
||||
register_converter(LocaleConverter, "locale")
|
||||
|
||||
pontoon_js_view = TemplateView.as_view(
|
||||
template_name="js/pontoon.js", content_type="text/javascript"
|
||||
)
|
||||
|
||||
permission_denied_view = TemplateView.as_view(template_name="403.html")
|
||||
page_not_found_view = TemplateView.as_view(template_name="404.html")
|
||||
server_error_view = TemplateView.as_view(template_name="500.html")
|
||||
|
@ -53,9 +49,9 @@ urlpatterns = [
|
|||
"favicon.ico",
|
||||
RedirectView.as_view(url="/static/img/favicon.ico", permanent=True),
|
||||
),
|
||||
# Include script
|
||||
path("pontoon.js", pontoon_js_view),
|
||||
path("static/js/pontoon.js", pontoon_js_view),
|
||||
# Legacy
|
||||
path("in-context/", RedirectView.as_view(url="/", permanent=True)),
|
||||
path("intro/", RedirectView.as_view(url="/", permanent=True)),
|
||||
# Include URL configurations from installed apps
|
||||
path("terminology/", include("pontoon.terminology.urls")),
|
||||
path("translations/", include("pontoon.translations.urls")),
|
||||
|
@ -72,7 +68,6 @@ urlpatterns = [
|
|||
path("", include("pontoon.batch.urls")),
|
||||
path("", include("pontoon.api.urls")),
|
||||
path("", include("pontoon.homepage.urls")),
|
||||
path("", include("pontoon.in_context.urls")),
|
||||
path("", include("pontoon.uxactionlog.urls")),
|
||||
# Team page: Must be at the end
|
||||
path("<locale:locale>/", team, name="pontoon.teams.team"),
|
||||
|
|
|
@ -32,8 +32,6 @@ export function UserMenuDialog({
|
|||
|
||||
const canDownload =
|
||||
project !== 'all-projects' && resource !== 'all-resources';
|
||||
|
||||
/* TODO: Also disable for subpages (in-context l10n) when supported */
|
||||
const canUpload = canDownload && isTranslator && !entity.readonly;
|
||||
|
||||
const ref = useRef<HTMLUListElement>(null);
|
||||
|
|