1
0
Fork 0
mirror of https://github.com/YunoHost/apps.git synced 2024-09-03 20:06:07 +02:00

appstore: initialize i18n stuff

This commit is contained in:
Alexandre Aubin 2023-09-05 20:24:38 +02:00
parent fdad58a45d
commit 55e0a098ef
13 changed files with 473 additions and 99 deletions

1
store/.gitignore vendored
View file

@ -1,2 +1,3 @@
config.toml
.stars
messages.pot

View file

@ -29,3 +29,21 @@ And then start the dev server:
source venv/bin/activate
FLASK_APP=app.py FLASK_ENV=development flask run
```
## Translation
It's based on Flask-Babel : https://python-babel.github.io/
```
source venv/bin/activate
pybabel extract --ignore-dirs venv -F babel.cfg -o messages.pot .
# If working on a new locale : initialize it (in this example: fr)
pybabel init -i messages.pot -d translations -l fr
# Otherwise, update the existing .po:
pybabel update -i messages.pot -d translations
# ... translate stuff in translations/<lang>/LC_MESSAGES/messages.po
# then compile:
pybabel compile -d translations
```

View file

@ -11,6 +11,8 @@ import json
import sys
from slugify import slugify
from flask import Flask, send_from_directory, render_template, session, redirect, request
from flask_babel import Babel
from flask_babel import gettext as _
from github import Github, InputGitAuthor
from .utils import get_catalog, get_wishlist, get_stars, get_app_md_and_screenshots
@ -46,6 +48,26 @@ if config.get("DEBUG"):
# This is the secret key used for session signing
app.secret_key = config["COOKIE_SECRET"]
AVAILABLE_LANGUAGES = ["en"] + os.listdir("translations")
def get_locale():
# try to guess the language from the user accept
# header the browser transmits. We support de/fr/en in this
# example. The best match wins.
return request.accept_languages.best_match(AVAILABLE_LANGUAGES)
babel = Babel(app, locale_selector=get_locale)
@app.template_filter('localize')
def localize(d):
if not isinstance(d, dict):
return d
else:
locale = get_locale()
if locale in d:
return d[locale]
else:
return d["en"]
###############################################################################
@app.route('/favicon.ico')
@ -55,13 +77,14 @@ def favicon():
@app.route('/')
def index():
return render_template("index.html", user=session.get('user', {}), catalog=get_catalog())
return render_template("index.html", locale=get_locale(), user=session.get('user', {}), catalog=get_catalog())
@app.route('/catalog')
def browse_catalog():
return render_template(
"catalog.html",
locale=get_locale(),
init_sort=request.args.get("sort"),
init_search=request.args.get("search"),
init_category=request.args.get("category"),
@ -69,7 +92,7 @@ def browse_catalog():
user=session.get('user', {}),
catalog=get_catalog(),
timestamp_now=int(time.time()),
stars=get_stars()
stars=get_stars(),
)
@ -82,16 +105,16 @@ def app_info(app_id):
get_app_md_and_screenshots(app_folder, infos)
return render_template("app.html", user=session.get('user', {}), app_id=app_id, infos=infos, catalog=get_catalog(), stars=get_stars())
return render_template("app.html", locale=get_locale(), user=session.get('user', {}), app_id=app_id, infos=infos, catalog=get_catalog(), stars=get_stars())
@app.route('/app/<app_id>/<action>')
def star_app(app_id, action):
assert action in ["star", "unstar"]
if app_id not in get_catalog()["apps"] and app_id not in get_wishlist():
return f"App {app_id} not found", 404
return _("App %(app_id) not found", app_id=app_id), 404
if not session.get('user', {}):
return f"You must be logged in to be able to star an app", 401
return _("You must be logged in to be able to star an app"), 401
app_star_folder = os.path.join(".stars", app_id)
app_star_for_this_user = os.path.join(".stars", app_id, session.get('user', {})["id"])
@ -114,7 +137,7 @@ def star_app(app_id, action):
@app.route('/wishlist')
def browse_wishlist():
return render_template("wishlist.html", user=session.get('user', {}), wishlist=get_wishlist(), stars=get_stars())
return render_template("wishlist.html", locale=get_locale(), user=session.get('user', {}), wishlist=get_wishlist(), stars=get_stars())
@app.route('/wishlist/add', methods=['GET', 'POST'])
@ -123,8 +146,8 @@ def add_to_wishlist():
user = session.get('user', {})
if not user:
errormsg = "You must be logged in to submit an app to the wishlist"
return render_template("wishlist_add.html", user=session.get('user', {}), successmsg=None, errormsg=errormsg)
errormsg = _("You must be logged in to submit an app to the wishlist")
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=errormsg)
name = request.form['name'].strip().replace("\n", "")
description = request.form['description'].strip().replace("\n", "")
@ -132,19 +155,19 @@ def add_to_wishlist():
website = request.form['website'].strip().replace("\n", "")
checks = [
(len(name) >= 3, "App name should be at least 3 characters"),
(len(name) <= 30, "App name should be less than 30 characters"),
(len(description) >= 5, "App name should be at least 5 characters"),
(len(description) <= 100, "App name should be less than 100 characters"),
(len(upstream) >= 10, "Upstream code repo URL should be at least 10 characters"),
(len(upstream) <= 150, "Upstream code repo URL should be less than 150 characters"),
(len(website) <= 150, "Website URL should be less than 150 characters"),
(re.match(r"^[\w\.\-\(\)\ ]+$", name), "App name contains special characters"),
(len(name) >= 3, _("App name should be at least 3 characters")),
(len(name) <= 30, _("App name should be less than 30 characters")),
(len(description) >= 5, _("App description should be at least 5 characters")),
(len(description) <= 100, _("App description should be less than 100 characters")),
(len(upstream) >= 10, _("Upstream code repo URL should be at least 10 characters")),
(len(upstream) <= 150, _("Upstream code repo URL should be less than 150 characters")),
(len(website) <= 150, _("Website URL should be less than 150 characters")),
(re.match(r"^[\w\.\-\(\)\ ]+$", name), _("App name contains special characters")),
]
for check, errormsg in checks:
if not check:
return render_template("wishlist_add.html", user=session.get('user', {}), successmsg=None, errormsg=errormsg)
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=errormsg)
slug = slugify(name)
github = Github(config["GITHUB_TOKEN"])
@ -156,7 +179,7 @@ def add_to_wishlist():
new_wishlist = toml.loads(current_wishlist_rawtoml)
if slug in new_wishlist:
return render_template("wishlist_add.html", user=session.get('user', {}), successmsg=None, errormsg=f"An entry with the name {slug} already exists in the wishlist")
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=_("An entry with the name %(slug) already exists in the wishlist", slug=slug))
new_wishlist[slug] = {
"name": name,
@ -175,8 +198,8 @@ def add_to_wishlist():
except exception as e:
print("... Failed to create branch ?")
print(e)
errormsg = "Failed to create the pull request to add the app to the wishlist ... please report the issue to the yunohost team"
return render_template("wishlist_add.html", user=session.get('user', {}), successmsg=None, errormsg=errormsg)
errormsg = _("Failed to create the pull request to add the app to the wishlist ... please report the issue to the yunohost team")
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=errormsg)
message = f"Add {name} to wishlist"
repo.update_file(
@ -206,10 +229,12 @@ Proposed by **{session['user']['username']}**
title=message, body=body, head=new_branch, base="app-store" # FIXME app-store -> repo.default_branch
)
successmsg = f"Your proposed app has succesfully been submitted. It must now be validated by the YunoHost team. You can track progress here: https://github.com/YunoHost/apps/pull/{pr.number}"
return render_template("wishlist_add.html", user=session.get('user', {}), successmsg=successmsg)
url = f"https://github.com/YunoHost/apps/pull/{pr.number}"
successmsg = _("Your proposed app has succesfully been submitted. It must now be validated by the YunoHost team. You can track progress here: %(url)s", url=url)
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=successmsg)
else:
return render_template("wishlist_add.html", user=session.get('user', {}), successmsg=None, errormsg=None)
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=None)
###############################################################################

2
store/babel.cfg Normal file
View file

@ -0,0 +1,2 @@
[python: **.py]
[jinja2: **/templates/**.html]

View file

@ -5,3 +5,5 @@ toml
pycmarkgfm
gunicorn
emoji
Babel
Flask-Babel

View file

@ -1,4 +1,3 @@
{% set locale = 'en' %}
{% extends "base.html" %}
{% block main %}
<div class="max-w-screen-md mx-auto pt-5">
@ -15,7 +14,7 @@
<h2 class="pl-2 pt-3 text-3xl font-bold text-gray-900">{{ infos["manifest"]["name"] }}</h2>
{% if infos['category'] %}
<span class="ml-2 mb-1 rounded-full px-2.5 py-0.5 text-[10px] border text-{{ catalog['categories'][infos['category']]['color'] }}-500 border-{{ catalog['categories'][infos['category']]['color'] }}-400 ">
{{ catalog['categories'][infos['category']]['title'][locale].lower() }}
{{ catalog['categories'][infos['category']]['title']|localize|lower }}
</span>
{% endif %}
<div class="grow"></div>
@ -65,7 +64,7 @@
href="{{ infos["manifest"]["upstream"]["demo"] }}"
>
<i class="fa fa-external-link fa-fw" aria-hidden="true"></i>
Try the demo
{{ _("Try the demo") }}
</a>
{% endif %}
</div>
@ -77,9 +76,9 @@
</span>
<p class="text-sm text-slate-500">Current version: {{ infos["manifest"]["version"] }}</p>
<p class="text-sm text-slate-500">{{ _("Current version: %(version)s", version=infos["manifest"]["version"]) }}</p>
{% if infos["potential_alternative_to"] %}
<p class="text-sm text-slate-500">Potential alternative to: {{ infos["potential_alternative_to"]|join(', ') }}</p>
<p class="text-sm text-slate-500">{{ _("Potential alternative to: %(alternatives)s", alternatives=infos["potential_alternative_to"]|join(', ')) }}</p>
{% endif %}
<div class="from-markdown">{{ infos["full_description_html"]|safe }}</div>
@ -90,44 +89,45 @@
{% if infos["manifest"]["integration"]["architectures"] != "all" %}
<div class="my-3 rounded-md bg-orange-200 text-orange-800 px-5 py-2">
<i class="fa fa-exclamation-triangle fa-fw"></i> This app is only compatible with these specific architectures : {{ infos["manifest"]["integration"]["architectures"]|join(', ') }}
<i class="fa fa-exclamation-triangle fa-fw"></i> {{ _("This app is only compatible with these specific architectures: %(archs)s", archs=infos["manifest"]["integration"]["architectures"]|join(', ')) }}
</div>
{% endif %}
{% if infos["manifest"]["integration"]["ram"]["build_binary"] >= 500 * 1024 * 1024 %}
<div class="my-3 rounded-md bg-orange-200 text-orange-800 px-5 py-2">
<i class="fa fa-exclamation-triangle fa-fw"></i> This app requires an unusual amount of RAM to build : {{ infos["manifest"]["integration"]["ram"]["build"] }}
<i class="fa fa-exclamation-triangle fa-fw"></i> {{ _("This app requires an unusual amount of RAM to install: %(ram)s", ram=infos["manifest"]["integration"]["ram"]["build"]) }}
</div>
{% endif %}
{% if infos["pre_install_html"] %}
<div class="my-3 rounded-md bg-blue-200 text-blue-800 px-5 py-2">
<h3 class="inline-block text-xl mb-2 font-semibold">Important infos before installing</h3>
<h3 class="inline-block text-xl mb-2 font-semibold">{{ _("Important infos before installing") }}</h3>
<div class="from-markdown">{{ infos["pre_install_html"] | safe }}</div>
</div>
{% endif %}
{% if infos["antifeatures"] %}
<h3 class="inline-block text-xl mb-2 font-semibold">Anti-features</h3> <p class="inline-block text-sm">(This app has features you may not like)</p>
<h3 class="inline-block text-xl mb-2 font-semibold">{{ _("Anti-features") }}</h3>
<p class="inline-block text-sm">{{ _("(This app has features you may not like)") }}</p>
<div class="my-3 rounded-md bg-red-200 text-red-800 px-5 py-2">
<ul>
{% for antifeature in infos["antifeatures"] %}
<li class="mb-1"><i class="fa fa-{{ catalog['antifeatures'][antifeature]['icon'] }} fa-fw" aria-hidden="true"></i> {{ catalog['antifeatures'][antifeature]['description'][locale] }}</li>
<li class="mb-1"><i class="fa fa-{{ catalog['antifeatures'][antifeature]['icon'] }} fa-fw" aria-hidden="true"></i> {{ catalog['antifeatures'][antifeature]['description']|localize }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<h3 class="text-xl mb-2 font-semibold">Useful links</h3>
<h3 class="text-xl mb-2 font-semibold">{{ _("Useful links") }}</h3>
<div>
{% set upstream = infos["manifest"]["upstream"] %}
<a class="block btn btn-link my-1" href="https://spdx.org/licenses/{{upstream.license}}"><i class="fa fa-institution fa-fw" aria-hidden="true"></i> License : {{ upstream.license }}</a>
{% if upstream.website %}<a class="block btn btn-link my-1" href="{{ upstream.website }}"><i class="fa fa-globe fa-fw" aria-hidden="true"></i> Official website</a>{% endif %}
{% if upstream.admindoc %}<a class="block btn btn-link my-1" href="{{ upstream.admindoc }}"><i class="fa fa-book fa-fw" aria-hidden="true"></i> Official admin documentation</a>{% endif %}
{% if upstream.userdoc %}<a class="block btn btn-link my-1" href="{{ upstream.userdoc }}"><i class="fa fa-book fa-fw" aria-hidden="true"></i> Official user documentation</a>{% endif %}
{% if upstream.code %}<a class="block btn btn-link my-1" href="{{ upstream.code }}"><i class="fa fa-code fa-fw" aria-hidden="true"></i> Official code repository</a>{% endif %}
<a class="block btn btn-link my-1" href="{{ infos["git"]["url"] }}"><i class="fa fa-code fa-fw" aria-hidden="true"></i> YunoHost package repository</a>
<a class="block btn btn-link my-1" href="https://spdx.org/licenses/{{upstream.license}}"><i class="fa fa-institution fa-fw" aria-hidden="true"></i> {{ _("License: %(license)s", license=upstream.license) }}</a>
{% if upstream.website %}<a class="block btn btn-link my-1" href="{{ upstream.website }}"><i class="fa fa-globe fa-fw" aria-hidden="true"></i> {{ _(" Official website") }}</a>{% endif %}
{% if upstream.admindoc %}<a class="block btn btn-link my-1" href="{{ upstream.admindoc }}"><i class="fa fa-book fa-fw" aria-hidden="true"></i> {{ _("Official admin documentation") }}</a>{% endif %}
{% if upstream.userdoc %}<a class="block btn btn-link my-1" href="{{ upstream.userdoc }}"><i class="fa fa-book fa-fw" aria-hidden="true"></i> {{ _("Official user documentation") }}</a>{% endif %}
{% if upstream.code %}<a class="block btn btn-link my-1" href="{{ upstream.code }}"><i class="fa fa-code fa-fw" aria-hidden="true"></i> {{ _("Official code repository") }}</a>{% endif %}
<a class="block btn btn-link my-1" href="{{ infos["git"]["url"] }}"><i class="fa fa-code fa-fw" aria-hidden="true"></i> {{ _("YunoHost package repository") }}</a>
</div>
</div>
{% endblock %}

View file

@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<html lang="{{ locale }}">
<head>
<title>YunoHost app store</title>
<title>{{ _("YunoHost app store") }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="{{ url_for('static', filename='tailwindcss.js') }}"></script>
@ -53,7 +53,7 @@
class="flex h-16 items-center gap-8 px-4 sm:px-6 lg:px-8"
>
<a class="block text-teal-600" href="/">
<span class="sr-only">Home</span>
<span class="sr-only">{{ _("Home") }}</span>
<img src="{{ url_for('static', filename='ynh_logo_roundcorner.png') }}" style="height: 3em;" />
</a>
@ -62,13 +62,13 @@
<ul class="flex items-center gap-6 text-sm">
<li>
<a class="text-gray-800 font-bold transition hover:text-gray-500/75" href="{{ url_for('browse_catalog') }}">
Catalog
{{ _("Catalog") }}
</a>
</li>
<li>
<a class="text-gray-800 font-bold transition hover:text-gray-500/75" href="{{ url_for('browse_wishlist') }}">
Wishlist
{{ _("Wishlist") }}
</a>
</li>
</ul>
@ -81,21 +81,21 @@
href="https://yunohost.org/docs/"
>
<i class="fa fa-external-link fa-fw" aria-hidden="true"></i>
YunoHost documentation
{{ _("YunoHost documentation") }}
</a>
{% if not user %}
<a
class="btn btn-primary hidden md:inline-block"
href="{{ url_for('login_using_discourse') }}"
>
Login using YunoHost's forum
{{ _("Login using YunoHost's forum") }}
</a>
{% else %}
<button
type="button"
class="group flex shrink-0 items-center rounded-lg transition"
>
<span class="sr-only">Menu</span>
<span class="sr-only">{{ _("Menu") }}</span>
<img
alt="Man"
src="{{ user['avatar_url'] }}"
@ -120,7 +120,7 @@
<button
class="block rounded bg-gray-100 p-2.5 text-gray-600 transition hover:text-gray-600/75 md:hidden"
>
<span class="sr-only">Toggle menu</span>
<span class="sr-only">{{ _("Toggle menu") }}</span>
<i class="fa fa-bars h-5 w-5" aria-hidden="true"></i>
</button>
</div>

View file

@ -1,5 +1,3 @@
{% set locale = 'en' %}
{% macro appCard(app, infos, timestamp_now, catalog) -%}
{% set this_app_stars = stars.get(app, {})|length %}
@ -53,14 +51,14 @@
</span>
</span>
<p class="max-w-[40ch] text-xs text-gray-500">
{{ infos['manifest']['description']['en'] }}
{{ infos['manifest']['description']|localize }}
</p>
<div class="hidden">
{{ infos["potential_alternative_to"]|join(', ') }}
</div>
{% if infos['category'] %}
<span class="rounded-full px-2.5 py-0.5 text-[10px] border text-{{ catalog['categories'][infos['category']]['color'] }}-500 border-{{ catalog['categories'][infos['category']]['color'] }}-400 ">
{{ catalog['categories'][infos['category']]['title'][locale].lower() }}
{{ catalog['categories'][infos['category']]['title']|localize|lower }}
</span>
{% endif %}
</div>
@ -73,18 +71,18 @@
{% block main %}
<div class="mt-5 text-center">
<h2 class="text-2xl font-bold text-gray-900">
Application Catalog
{{ _("Application Catalog") }}
</h2>
</div>
<div class="max-w-screen-md mx-auto mt-3 mb-3">
<div class="flex flex-row">
<div class="px-2 inline-block relative basis-2/3 text-gray-700">
<label for="search" class="sr-only"> Search </label>
<label for="search" class="sr-only"> {{ _("Search") }} </label>
<input
type="text"
id="search"
placeholder="Search for..."
placeholder="{{ _('Search for...') }}"
{% if init_search %}value="{{ init_search }}"{% endif %}
class="w-full rounded-md border-gray-200 shadow-sm sm:text-sm py-2 pe-10"
/>
@ -100,10 +98,10 @@
id="selectcategory"
class="w-full rounded-md border-gray-200 shadow-sm sm:test-sm px-2 py-1.5"
>
<option value="">All apps</option>
<option value="">{{ _("All apps") }}</option>
{% for id, category in catalog['categories'].items() %}
{{ category['title'][locale] }}
<option {% if id == init_category %}selected{% endif %} value="{{ id }}" {{ id == init_category }} >{{ category['title'][locale] }}</option>
{{ category['title']|localize }}
<option {% if id == init_category %}selected{% endif %} value="{{ id }}" {{ id == init_category }} >{{ category['title']|localize }}</option>
{% endfor %}
</select>
</div>
@ -111,15 +109,15 @@
<div class="flex flex-row justify-center items-center pt-2 text-center text-sm">
<div class="inline-block px-2">
Sort by
{{ _("Sort by") }}
<select
name="selectsort"
id="selectsort"
class="inline-block rounded-md border-gray-200 text-sm ml-1 pl-1 pr-7 h-8 py-0"
>
<option {% if not init_sort %}selected{% endif %} value="">Alphabetical</option>
<option {% if init_sort == "newest" %}selected{% endif %} value="newest">Newest</option>
<option {% if init_sort == "popularity" %}selected{% endif %} value="popularity">Popularity</option>
<option {% if not init_sort %}selected{% endif %} value="">{{ _("Alphabetical") }}</option>
<option {% if init_sort == "newest" %}selected{% endif %} value="newest">{{ _("Newest") }}</option>
<option {% if init_sort == "popularity" %}selected{% endif %} value="popularity">{{ _("Popularity") }}</option>
</select>
</div>
<div class="inline-block flex items-center px-2">
@ -132,7 +130,7 @@
<span class="absolute inset-y-0 start-0 m-1 h-2 w-2 rounded-full bg-white transition-all peer-checked:start-3">
</span>
</label>
Show only apps you starred
{{ _("Show only apps you starred") }}
</div>
</div>
</div>
@ -148,21 +146,21 @@
<div id="noResultFound" class="text-center pt-5 hidden">
<p class="text-lg font-bold text-gray-900 mb-5">
No results found
{{ _("No results found.") }}
</p>
<p class="text-md text-gray-900">
Not finding what you are looking for ?<br/>
Checkout the wishlist !
{{ _("Not finding what you are looking for?") }}<br/>
{{ _("Checkout the wishlist!") }}
</p>
</div>
<div id="lowQualityAppTitle" class="text-center pt-10">
<h2 class="text-lg font-bold text-gray-900">
Applications currently broken or low-quality
{{ _("Applications currently flagged as broken or low-quality") }}
</h2>
<p class="text-sm">
These are apps which failed our automatic tests.<br/>
This is usually a temporary situation which requires packagers to fix something in the app.
{{ _("These are apps which failed our automatic tests.") }}<br/>
{{ _("This is usually a temporary situation which requires packagers to fix something in the app.") }}
</p>
</div>

View file

@ -1,11 +1,10 @@
{% extends "base.html" %}
{% block main %}
{% set locale = 'en' %}
<div class="mx-auto w-full text-center p-8">
<img src="{{ url_for('static', filename='ynh_logo_black.svg') }}" class="w-32 mx-auto" />
<h2 class="text-2xl font-bold text-gray-900">
Application Store
{{ _("Application Store") }}
</h2>
</div>
@ -16,7 +15,7 @@
class="h-full relative block overflow-hidden hover:bg-gray-200 pt-12"
>
<h3 class="text-md font-bold text-gray-900">
Browse all applications
{{ _("Browse all applications") }}
</h3>
</a>
</div>
@ -28,10 +27,10 @@
>
<h3 class="text-md font-bold text-gray-900">
<i class="fa fa-{{ category['icon'] }}" aria-hidden="true"></i>
{{ category['title'][locale] }}
{{ category['title']|localize }}
</h3>
<p class="mx-auto max-w-[40ch] text-xs text-gray-500">
{{ category['description'][locale] }}
{{ category['description']|localize }}
</p>
</a>
</div>

View file

@ -2,19 +2,19 @@
{% block main %}
<div class="mt-5 text-center">
<h2 class="text-2xl font-bold text-gray-900">
Application Wishlist
{{ _("Application Wishlist") }}
</h2>
</div>
<div class="max-w-screen-md mx-auto mt-3 mb-3">
<div class="flex flex-row">
<div class="px-2 inline-block relative basis-2/3 text-gray-700">
<label for="search" class="sr-only"> Search </label>
<label for="search" class="sr-only"> {{ _("Search") }} </label>
<input
type="text"
id="search"
placeholder="Search for..."
placeholder="{{ _('Search for...') }}"
class="w-full rounded-md border-gray-200 shadow-sm sm:text-sm py-2 pe-10"
/>
@ -23,33 +23,28 @@
</span>
</div>
<a
class="btn btn-primary-outline"
href="{{ url_for('add_to_wishlist') }}"
>
<a class="btn btn-primary-outline" href="{{ url_for('add_to_wishlist') }}">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
Add an app to the wishlist
{{ _("Suggest an app") }}
</a>
</div>
</div>
</div>
<div class="overflow-x-auto max-w-screen-lg mx-auto pt-5">
<table class="min-w-full divide-y-2 divide-gray-200 bg-white text-sm">
<thead>
<tr>
<th class="whitespace-nowrap px-4 py-2 font-medium text-gray-900">
Name
{{ _("Name") }}
</th>
<th class="whitespace-nowrap px-4 py-2 font-medium text-gray-900">
Description
{{ _("Description") }}
</th>
<th class="py-2"></th>
<th class="py-2"></th>
<th class="whitespace-nowrap px-4 py-2 font-medium text-gray-900 max-w-[5em]">Popularity</th>
<th class="whitespace-nowrap px-4 py-2 font-medium text-gray-900 max-w-[5em]">{{ _("Popularity") }}</th>
</tr>
</thead>

View file

@ -2,7 +2,7 @@
{% block main %}
<div class="mt-5 text-center">
<h2 class="text-2xl font-bold text-gray-900">
Add an application to the wishlist
{{ _("Suggest an application to be added to YunoHost's catalog") }}
</h2>
</div>
@ -23,7 +23,7 @@
<div role="alert" class="rounded-md border-s-4 border-orange-500 bg-orange-50 p-4 mb-5">
<p class="mt-2 text-sm text-orange-700 font-bold">
<i class="fa fa-exclamation-triangle fa-fw" aria-hidden="true"></i>
You must first login to be allowed to submit an app to the wishlist
{{ _("You must first login to be allowed to submit an app to the wishlist") }}
</p>
</div>
{% endif %}
@ -31,10 +31,10 @@
<div role="alert" class="rounded-md border-s-4 border-sky-500 bg-sky-50 p-4">
<p class="mt-2 text-sm text-sky-700 font-bold">
<i class="fa fa-info-circle fa-fw" aria-hidden="true"></i>
Please check the license of the app your are proposing
{{ _("Please check the license of the app your are proposing") }}
</p>
<p class="mt-2 text-sm text-sky-700">
The YunoHost project will only package free/open-source software (with possible case-by-case exceptions for apps which are not-totally-free)
{{ _("The YunoHost project will only package free/open-source software (with possible case-by-case exceptions for apps which are not-totally-free)") }}
</p>
</div>
@ -50,26 +50,26 @@
<form method="POST" action="{{ url_for('add_to_wishlist') }}" class="mt-8 mb-8" >
<label for="name" class="mt-5 block font-bold text-gray-700">Name</label>
<label for="name" class="mt-5 block font-bold text-gray-700">{{ _("Name") }}</label>
<input name="name" type="text" class="w-full mt-1 rounded-md border-gray-200 text-gray-700 shadow-sm" maxlength="30" required onkeyup="this.value = this.value.replace(/[^a-zA-Z0-9.-\\(\\)\\ ]/, '')" />
<label for="description" class="mt-5 block font-bold text-gray-700">Description</label>
<label for="description" class="mt-5 block font-bold text-gray-700">{{ _("Description") }}</label>
<textarea name="description" type="text" class="w-full mt-1 rounded-md border-gray-200 text-gray-700 shadow-sm" required rows='3' maxlength='100'></textarea>
<span class="text-xs text-gray-600"><span class="font-bold">Please be concise and focus on what the app does.</span> No need to repeat "[App] is ...". No need to state that it is free/open-source or self-hosted (otherwise it wouldn't be packaged for YunoHost). Avoid marketing stuff like 'the most', or vague properties like 'easy', 'simple', 'lightweight'.</span>
<span class="text-xs text-gray-600"><span class="font-bold">{{ _("Please be concise and focus on what the app does.") }}</span> {{ _("No need to repeat '[App] is ...'. No need to state that it is free/open-source or self-hosted (otherwise it wouldn't be packaged for YunoHost). Avoid marketing stuff like 'the most', or vague properties like 'easy', 'simple', 'lightweight'.") }}</span>
<label for="upstream" class="mt-5 block font-bold text-gray-700">Project code repository</label>
<label for="upstream" class="mt-5 block font-bold text-gray-700">{{ _("Project code repository") }}</label>
<input name="upstream" type="url" class="w-full mt-1 rounded-md border-gray-200 text-gray-700 shadow-sm" maxlength="150" required />
<label for="website" class="mt-5 block font-bold text-gray-700">Project website</label>
<label for="website" class="mt-5 block font-bold text-gray-700">{{ _("Project website") }}</label>
<input name="website" type="url" class="w-full mt-1 rounded-md border-gray-200 text-gray-700 shadow-sm" maxlength="150" />
<span class="text-xs text-gray-600">Please <emph>do not</emph> just copy-paste the code repository URL. If the project has no proper website, then leave the field empty.</span>
<span class="text-xs text-gray-600">{{ _("Please *do not* just copy-paste the code repository URL. If the project has no proper website, then leave the field empty.") }}</span>
<button
type="submit"
class="block mx-auto btn btn-primary mt-5 {% if user %}hover:bg-blue-700{% endif %}"
{% if not user %}disabled{% endif %}
>
Submit
{{ _("Submit") }}
</button>
</form>

Binary file not shown.

View file

@ -0,0 +1,334 @@
# French translations for PROJECT.
# Copyright (C) 2023 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-09-05 20:41+0200\n"
"PO-Revision-Date: 2023-09-05 19:50+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n"
"Language-Team: fr <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.12.1\n"
#: app.py:115
msgid "App %(app_id) not found"
msgstr "L'app %(app_id) n'a pas été trouvée"
#: app.py:117
msgid "You must be logged in to be able to star an app"
msgstr "Vous devez être connecté·e pour mettre une app en favoris"
#: app.py:149
msgid "You must be logged in to submit an app to the wishlist"
msgstr "Vous devez être connecté·e pour proposer une app pour la liste de souhait"
#: app.py:158
msgid "App name should be at least 3 characters"
msgstr "Le nom d'app devrait contenir au moins 3 caractères"
#: app.py:159
msgid "App name should be less than 30 characters"
msgstr "Le nom d'app devrait contenir moins de 30 caractères"
#: app.py:160
msgid "App description should be at least 5 characters"
msgstr "La description de l'app devrait contenir au moins 5 caractères"
#: app.py:161
msgid "App description should be less than 100 characters"
msgstr "La description de l'app devrait contenir moins de 100 caractères"
#: app.py:162
msgid "Upstream code repo URL should be at least 10 characters"
msgstr "L'URL du dépôt de code devrait contenir au moins 10 caractères"
#: app.py:163
msgid "Upstream code repo URL should be less than 150 characters"
msgstr "L'URL du dépôt de code devrait contenir moins de 150 caractères"
#: app.py:164
msgid "Website URL should be less than 150 characters"
msgstr "L'URL du site web devrait contenir moins de 150 caractères"
#: app.py:165
msgid "App name contains special characters"
msgstr "Le nom de l'app contiens des caractères spéciaux"
#: app.py:182
msgid "An entry with the name %(slug) already exists in the wishlist"
msgstr "Une entrée nommée $(slug) existe déjà dans la liste de souhait"
#: app.py:201
msgid ""
"Failed to create the pull request to add the app to the wishlist ... "
"please report the issue to the yunohost team"
msgstr ""
"Échec de la création de la demande d'intégration de l'app dans la liste "
"de souhait ... merci de rapport le problème à l'équipe YunoHost"
#: app.py:234
msgid ""
"Your proposed app has succesfully been submitted. It must now be "
"validated by the YunoHost team. You can track progress here: %(url)s"
msgstr ""
"Un demande d'intégration à la liste de souhait a bien été créée pour "
"cette app. Elle doit maintenant être validée par l'équipe YunoHost. Vous "
"pouvez suivre cette demande ici: %(url)s"
#: templates/app.html:67
msgid "Try the demo"
msgstr "Essayer la démo"
#: templates/app.html:79
#, python-format
msgid "Current version: %(version)s"
msgstr "Version actuelle: %(version)s"
#: templates/app.html:81
#, python-format
msgid "Potential alternative to: %(alternatives)s"
msgstr "Alternative potentielle à : %(alternatives)s"
#: templates/app.html:92
#, python-format
msgid "This app is only compatible with these specific architectures: %(archs)s"
msgstr ""
"Cette app est uniquement compatible avec les architectures suivantes : "
"%(archs)s"
#: templates/app.html:98
#, python-format
msgid "This app requires an unusual amount of RAM to install: %(ram)s"
msgstr ""
"Cette app requiert une quantité inhabituelle de RAM pour être installée :"
" %(ram)s"
#: templates/app.html:104
msgid "Important infos before installing"
msgstr "Informations importantes avant l'installation"
#: templates/app.html:110
msgid "Anti-features"
msgstr "Anti-fonctionnalités"
#: templates/app.html:111
msgid "(This app has features you may not like)"
msgstr "(Cette app a des spécificités que vous pourriez ne pas aimer)"
#: templates/app.html:122
msgid "Useful links"
msgstr "Liens utiles"
#: templates/app.html:125
#, python-format
msgid "License: %(license)s"
msgstr "Licence: %(license)s"
#: templates/app.html:126
msgid " Official website"
msgstr "Site officiel"
#: templates/app.html:127
msgid "Official admin documentation"
msgstr "Documentation officielle pour les admins"
#: templates/app.html:128
msgid "Official user documentation"
msgstr "Documentation officielle pour les utilisateur·ice·s"
#: templates/app.html:129
msgid "Official code repository"
msgstr "Dépôt de code officiel"
#: templates/app.html:130
msgid "YunoHost package repository"
msgstr "Dépôt de code du paquet YunoHost"
#: templates/base.html:5
msgid "YunoHost app store"
msgstr "Store d'apps de YunoHost"
#: templates/base.html:56
msgid "Home"
msgstr "Accueil"
#: templates/base.html:65
msgid "Catalog"
msgstr "Catalogue"
#: templates/base.html:71
msgid "Wishlist"
msgstr "Liste de souhaits"
#: templates/base.html:84
msgid "YunoHost documentation"
msgstr "Documentation YunoHost"
#: templates/base.html:91
msgid "Login using YunoHost's forum"
msgstr "Se connecter via le forum YunoHost"
#: templates/base.html:98
msgid "Menu"
msgstr "Menu"
#: templates/base.html:123
msgid "Toggle menu"
msgstr "Activer le menu"
#: templates/catalog.html:74
msgid "Application Catalog"
msgstr "Catalogue d'applications"
#: templates/catalog.html:80 templates/wishlist.html:12
msgid "Search"
msgstr "Recherche"
#: templates/catalog.html:85 templates/wishlist.html:17
msgid "Search for..."
msgstr "Rechercher..."
#: templates/catalog.html:101
msgid "All apps"
msgstr "Toutes les apps"
#: templates/catalog.html:112
msgid "Sort by"
msgstr "Trier par"
#: templates/catalog.html:118
msgid "Alphabetical"
msgstr "Alphabétique"
#: templates/catalog.html:119
msgid "Newest"
msgstr "Nouveauté"
#: templates/catalog.html:120 templates/wishlist.html:47
msgid "Popularity"
msgstr "Popularité"
#: templates/catalog.html:133
msgid "Show only apps you starred"
msgstr "Montrer uniquement mes favoris"
#: templates/catalog.html:149
msgid "No results found."
msgstr "Aucun résultat trouvé."
#: templates/catalog.html:152
msgid "Not finding what you are looking for?"
msgstr "Vous ne trouvez pas ce que vous cherchez ?"
#: templates/catalog.html:153
msgid "Checkout the wishlist!"
msgstr "Jetez un oeil à la liste de souhait !"
#: templates/catalog.html:159
msgid "Applications currently flagged as broken or low-quality"
msgstr "Applications actuellement marquées comme cassées ou de mauvaise qualité"
#: templates/catalog.html:162
msgid "These are apps which failed our automatic tests."
msgstr "Il s'agit d'apps qui n'ont pas validé nos tests automatisés."
#: templates/catalog.html:163
msgid ""
"This is usually a temporary situation which requires packagers to fix "
"something in the app."
msgstr ""
"Il s'agit généralement d'une situation temporaire qui requiert que des "
"packageur·euse·s corrigent un problème dans l'app."
#: templates/index.html:7
msgid "Application Store"
msgstr "Store d'application"
#: templates/index.html:18
msgid "Browse all applications"
msgstr "Toutes les applications"
#: templates/wishlist.html:5
msgid "Application Wishlist"
msgstr "Liste de souhait d'applications"
#: templates/wishlist.html:28
msgid "Suggest an app"
msgstr "Suggérer une app"
#: templates/wishlist.html:40 templates/wishlist_add.html:53
msgid "Name"
msgstr "Nom"
#: templates/wishlist.html:43 templates/wishlist_add.html:56
msgid "Description"
msgstr "Description"
#: templates/wishlist_add.html:5
msgid "Suggest an application to be added to YunoHost's catalog"
msgstr "Suggérer une application à ajouter dans le catalogue de YunoHost"
#: templates/wishlist_add.html:26
msgid "You must first login to be allowed to submit an app to the wishlist"
msgstr "Vous devez être connecté·e pour proposer une app pour la liste de souhait"
#: templates/wishlist_add.html:34
msgid "Please check the license of the app your are proposing"
msgstr "Merci de vérifier la licence de l'app que vous proposez"
#: templates/wishlist_add.html:37
msgid ""
"The YunoHost project will only package free/open-source software (with "
"possible case-by-case exceptions for apps which are not-totally-free)"
msgstr ""
"Le projet YunoHost intègrera uniquement des logiciels libre/open-source "
"(avec quelques possibles exceptions au cas-par-cas pour des apps qui ne "
"sont pas entièrement libres)"
#: templates/wishlist_add.html:58
msgid "Please be concise and focus on what the app does."
msgstr "Prière de rester concis et de se concentrer sur ce que l'app fait."
#: templates/wishlist_add.html:58
msgid ""
"No need to repeat '[App] is ...'. No need to state that it is free/open-"
"source or self-hosted (otherwise it wouldn't be packaged for YunoHost). "
"Avoid marketing stuff like 'the most', or vague properties like 'easy', "
"'simple', 'lightweight'."
msgstr ""
"Il n'est pas nécessaire de répéter '[App] est ...', ni que l'app est "
"libre/open-source (sinon, elle ne serait pas intégrable au catalogue). "
"Évitez les formulations marketing type 'le meilleur', ou les propriétés "
"vagues telles que 'facile', 'simple', 'léger'."
#: templates/wishlist_add.html:60
msgid "Project code repository"
msgstr "Dépôt de code officiel"
#: templates/wishlist_add.html:63
msgid "Project website"
msgstr "Site officiel"
#: templates/wishlist_add.html:65
msgid ""
"Please *do not* just copy-paste the code repository URL. If the project "
"has no proper website, then leave the field empty."
msgstr ""
"Prière de ne pas juste copier-coller l'URL du dépôt de code. Si le projet"
" n'a pas de vrai site web, laissez le champ vide."
#: templates/wishlist_add.html:72
msgid "Submit"
msgstr "Envoyer"
#~ msgid "Add an app to the wishlist"
#~ msgstr "Ajouter une app à la liste"