Merge pull request #262 from SamR1/fix-zip-import

Fix zip archive import
This commit is contained in:
Sam 2022-11-05 08:28:04 +01:00 committed by GitHub
commit 18ecdc582c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 89 additions and 32 deletions

View File

@ -1 +1 @@
<!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"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.50f5c7a2.js"></script><script defer="defer" src="/static/js/app.f7049224.js"></script><link href="/static/css/app.564b9516.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html> <!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"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.50f5c7a2.js"></script><script defer="defer" src="/static/js/app.3ce255f9.js"></script><link href="/static/css/app.564b9516.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{7885:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});var a=t(6252),r=t(2262),l=t(3577),o=(t(7658),t(9150)),n=t(3170);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-30799d13"]]);var Z=F,x=t(5630),D=t(5801),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]); "use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{7885:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});var a=t(6252),r=t(2262),l=t(3577),o=(t(7658),t(9150)),n=t(3170);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-30799d13"]]);var Z=F,x=t(5630),D=t(5801),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]);
//# sourceMappingURL=statistics.c9a8011a.js.map //# sourceMappingURL=statistics.04a17f2e.js.map

File diff suppressed because one or more lines are too long

View File

@ -1112,7 +1112,7 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
data = self.assert_500(response, 'error during gpx processing') data = self.assert_500(response, 'error during gpx processing')
assert 'data' not in data assert 'data' not in data
def test_it_imports_only_max_number_of_files( def test_it_returns_400_when_files_in_archive_exceed_limit(
self, self,
app_with_max_workouts: Flask, app_with_max_workouts: Flask,
user_1: User, user_1: User,
@ -1127,7 +1127,7 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
app_with_max_workouts, user_1.email app_with_max_workouts, user_1.email
) )
client.post( response = client.post(
'/api/workouts', '/api/workouts',
data=dict( data=dict(
file=(zip_file, 'gpx_test.zip'), data='{"sport_id": 1}' file=(zip_file, 'gpx_test.zip'), data='{"sport_id": 1}'
@ -1138,12 +1138,11 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
), ),
) )
response = client.get( self.assert_400(
'/api/workouts', response,
headers=dict(Authorization=f'Bearer {auth_token}'), 'the number of files in the archive exceeds the limit',
'fail',
) )
data = json.loads(response.data.decode())
assert len(data['data']['workouts']) == 2
def test_it_returns_error_if_archive_size_exceeds_limit( def test_it_returns_error_if_archive_size_exceeds_limit(
self, self,
@ -1178,6 +1177,40 @@ class TestPostWorkoutWithZipArchive(ApiTestCaseMixin):
) )
assert 'data' not in data assert 'data' not in data
def test_it_returns_error_if_a_file_from_archive_size_exceeds_limit(
self,
app_with_max_file_size: Flask,
user_1: User,
sport_1_cycling: Sport,
) -> None:
file_path = os.path.join(
app_with_max_file_size.root_path, 'tests/files/gpx_test.zip'
)
# 'gpx_test.zip' contains 3 gpx files (same data) and 1 non-gpx file
with open(file_path, 'rb') as zip_file:
client, auth_token = self.get_test_client_and_auth_token(
app_with_max_file_size, user_1.email
)
response = client.post(
'/api/workouts',
data=dict(
file=(zip_file, 'gpx_test.zip'), data='{"sport_id": 1}'
),
headers=dict(
content_type='multipart/form-data',
Authorization=f'Bearer {auth_token}',
),
)
data = self.assert_400(
response,
'at least one file in zip archive exceeds size limit, '
'please check the archive',
'fail',
)
assert 'data' not in data
class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin): class TestPostAndGetWorkoutWithGpx(ApiTestCaseMixin):
def workout_assertion( def workout_assertion(

View File

@ -333,6 +333,14 @@ def process_one_gpx_file(
raise WorkoutException('fail', 'Error during workout save.', e) raise WorkoutException('fail', 'Error during workout save.', e)
def is_gpx_file(filename: str) -> bool:
return (
'.' in filename
and filename.rsplit('.', 1)[1].lower()
in current_app.config['WORKOUT_ALLOWED_EXTENSIONS']
)
def process_zip_archive( def process_zip_archive(
common_params: Dict, extract_dir: str, stopped_speed_threshold: float common_params: Dict, extract_dir: str, stopped_speed_threshold: float
) -> List: ) -> List:
@ -341,21 +349,33 @@ def process_zip_archive(
does not exceed defined limit. does not exceed defined limit.
""" """
with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref: with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref:
max_file_size = current_app.config['max_single_file_size']
gpx_files_count = 0
files_with_invalid_size_count = 0
for zip_info in zip_ref.infolist():
if is_gpx_file(zip_info.filename):
gpx_files_count += 1
if zip_info.file_size > max_file_size:
files_with_invalid_size_count += 1
if gpx_files_count > current_app.config['gpx_limit_import']:
raise WorkoutException(
'fail', 'the number of files in the archive exceeds the limit'
)
if files_with_invalid_size_count > 0:
raise WorkoutException(
'fail',
'at least one file in zip archive exceeds size limit, '
'please check the archive',
)
zip_ref.extractall(extract_dir) zip_ref.extractall(extract_dir)
new_workouts = [] new_workouts = []
gpx_files_limit = current_app.config['gpx_limit_import']
gpx_files_ok = 0
for gpx_file in os.listdir(extract_dir): for gpx_file in os.listdir(extract_dir):
if ( if is_gpx_file(gpx_file):
'.' in gpx_file
and gpx_file.rsplit('.', 1)[1].lower()
in current_app.config['WORKOUT_ALLOWED_EXTENSIONS']
):
gpx_files_ok += 1
if gpx_files_ok > gpx_files_limit:
break
file_path = os.path.join(extract_dir, gpx_file) file_path = os.path.join(extract_dir, gpx_file)
params = common_params params = common_params
params['file_path'] = file_path params['file_path'] = file_path

View File

@ -1022,7 +1022,7 @@ def post_workout(auth_user: User) -> Union[Tuple[Dict, int], HttpResponse]:
appLog.error(e.e) appLog.error(e.e)
if e.status == 'error': if e.status == 'error':
return InternalServerErrorResponse(e.message) return InternalServerErrorResponse(e.message)
return InvalidPayloadErrorResponse(e.message) return InvalidPayloadErrorResponse(e.message, e.status)
shutil.rmtree(folders['extract_dir'], ignore_errors=True) shutil.rmtree(folders['extract_dir'], ignore_errors=True)
shutil.rmtree(folders['tmp_dir'], ignore_errors=True) shutil.rmtree(folders['tmp_dir'], ignore_errors=True)

View File

@ -1,6 +1,7 @@
{ {
"ERROR": { "ERROR": {
"UNKNOWN": "Error. Please try again or contact the administrator.", "UNKNOWN": "Error. Please try again or contact the administrator.",
"at least one file in zip archive exceeds size limit, please check the archive": "At least one file in zip archive exceeds size limit, please check the archive.",
"email: valid email must be provided": "Email: valid email must be provided.", "email: valid email must be provided": "Email: valid email must be provided.",
"error during gpx processing": "Error during gpx processing.", "error during gpx processing": "Error during gpx processing.",
"error during gpx file parsing": "Error during gpx file parsing.", "error during gpx file parsing": "Error during gpx file parsing.",
@ -18,6 +19,7 @@
"new email must be different than curent email": "The new email must be different than curent email", "new email must be different than curent email": "The new email must be different than curent email",
"no file part": "No file provided.", "no file part": "No file provided.",
"no selected file": "No selected file.", "no selected file": "No selected file.",
"the number of files in the archive exceeds the limit": "The number of files in the archive exceeds the limit.",
"password: password and password confirmation do not match": "Password: password and password confirmation don't match.", "password: password and password confirmation do not match": "Password: password and password confirmation don't match.",
"provide a valid auth token": "Provide a valid auth token.", "provide a valid auth token": "Provide a valid auth token.",
"sorry, that username is already taken": "Sorry, that username is already taken.", "sorry, that username is already taken": "Sorry, that username is already taken.",

View File

@ -10,11 +10,11 @@
"ADMIN_CONTACT": "Email de l'administrateur pour contact ", "ADMIN_CONTACT": "Email de l'administrateur pour contact ",
"MAX_USERS_LABEL": "Nombre maximum d'utilisateurs actifs ", "MAX_USERS_LABEL": "Nombre maximum d'utilisateurs actifs ",
"MAX_USERS_HELP": "Si égal à 0, pas limite d'inscription", "MAX_USERS_HELP": "Si égal à 0, pas limite d'inscription",
"MAX_FILES_IN_ZIP_LABEL": "Taille max. des archives zip (en Mo) ", "MAX_FILES_IN_ZIP_LABEL": "Nombre max. de fichiers dans une archive zip ",
"NO_CONTACT_EMAIL": "non renseigné", "NO_CONTACT_EMAIL": "non renseigné",
"SINGLE_UPLOAD_MAX_SIZE_LABEL": "Taille max. des fichiers (en Mo) ", "SINGLE_UPLOAD_MAX_SIZE_LABEL": "Taille max. des fichiers (en Mo) ",
"TITLE": "Configuration de l'application", "TITLE": "Configuration de l'application",
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Nombre max. de fichiers dans une archive zip " "ZIP_UPLOAD_MAX_SIZE_LABEL": "Taille max. des archives zip (en Mo) "
}, },
"BACK_TO_ADMIN": "Revenir à l'admin", "BACK_TO_ADMIN": "Revenir à l'admin",
"CONFIRM_USER_ACCOUNT_DELETION": "Êtes-vous sûr de vouloir supprimer le compte de l'utilisateur {0} ? Toutes les données seront définitivement.", "CONFIRM_USER_ACCOUNT_DELETION": "Êtes-vous sûr de vouloir supprimer le compte de l'utilisateur {0} ? Toutes les données seront définitivement.",

View File

@ -1,6 +1,7 @@
{ {
"ERROR": { "ERROR": {
"UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.", "UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
"at least one file in zip archive exceeds size limit, please check the archive": "Au moins un fichier de l'archive zip dépasse la taille maximale, veuillez vérifier l'archive.",
"email: valid email must be provided": "Courriel : une adresse électronique valide doit être fournie.", "email: valid email must be provided": "Courriel : une adresse électronique valide doit être fournie.",
"error during gpx processing": "Erreur lors du traitement du fichier gpx.", "error during gpx processing": "Erreur lors du traitement du fichier gpx.",
"error during gpx file parsing": "Erreur lors de l'analyse du fichier.", "error during gpx file parsing": "Erreur lors de l'analyse du fichier.",
@ -24,6 +25,7 @@
"signature expired, please log in again": "Signature expirée. Merci de vous reconnecter.", "signature expired, please log in again": "Signature expirée. Merci de vous reconnecter.",
"sorry, that username is already taken": "Désolé, ce nom d'utilisateur est déjà utilisé.", "sorry, that username is already taken": "Désolé, ce nom d'utilisateur est déjà utilisé.",
"successfully registered": "Inscription validée.", "successfully registered": "Inscription validée.",
"the number of files in the archive exceeds the limit": "Le nombre de fichiers de l'archive dépasse la limite.",
"user does not exist": "L'utilisateur n'existe pas.", "user does not exist": "L'utilisateur n'existe pas.",
"valid email must be provided for admin contact": "Une adresse électronique doit être fournie pour le contact de l'administrateur", "valid email must be provided for admin contact": "Une adresse électronique doit être fournie pour le contact de l'administrateur",
"you can not delete your account, no other user has admin rights": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.", "you can not delete your account, no other user has admin rights": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.",