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
This commit is contained in:
Eemeli Aro 2023-06-15 13:59:23 +03:00 коммит произвёл GitHub
Родитель 22dd3e107e
Коммит 5a2b145170
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
52 изменённых файлов: 75 добавлений и 7976 удалений

Просмотреть файл

@ -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/

Просмотреть файл

@ -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;
}

5673
pontoon/in_context/static/css/bootstrap.min.css поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Двоичные данные
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

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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);