def download_dictionary(request, project, lang): """ Exports dictionary into various formats. """ prj = get_project(request, project) lang = get_object_or_404(Language, code=lang) # Parse parameters export_format = None if "format" in request.GET: export_format = request.GET["format"] if export_format not in ("csv", "po", "tbx"): export_format = "csv" # Grab all words words = Dictionary.objects.filter(project=prj, language=lang).order_by("source") # Translate toolkit based export if export_format in ("po", "tbx"): return download_dictionary_ttkit(export_format, prj, lang, words) # Manually create CSV file response = HttpResponse(content_type="text/csv; charset=utf-8") filename = "dictionary-%s-%s.csv" % (prj.slug, lang.code) response["Content-Disposition"] = "attachment; filename=%s" % filename writer = csv.writer(response) # Add header writer.writerow(("source", "target")) for word in words.iterator(): writer.writerow((word.source.encode("utf8"), word.target.encode("utf8"))) return response
def show_project(request, project): obj = get_project(request, project) dicts = Dictionary.objects.filter(project=obj).values_list( 'language', flat=True).distinct() last_changes = Change.objects.prefetch().filter( Q(translation__subproject__project=obj) | Q(dictionary__project=obj)).order_by('-timestamp')[:10] return render_to_response( 'project.html', RequestContext( request, { 'object': obj, 'dicts': Language.objects.filter(id__in=dicts), 'last_changes': last_changes, 'last_changes_rss': reverse('rss-project', kwargs={'project': obj.slug}), 'last_changes_url': urlencode({'project': obj.slug}), }))
def reset_project(request, project): obj = get_project(request, project) if obj.do_reset(request): messages.success(request, _('All repositories have been reset.')) return redirect(obj)
def download_dictionary(request, project, lang): """Export dictionary into various formats.""" prj = get_project(request, project) lang = get_object_or_404(Language, code=lang) # Parse parameters export_format = None if 'format' in request.GET: export_format = request.GET['format'] if export_format not in ('csv', 'po', 'tbx', 'xliff'): export_format = 'csv' # Grab all words words = Dictionary.objects.filter(project=prj, language=lang).order_by(Lower('source')) # Translate toolkit based export exporter = get_exporter(export_format)( prj, lang, get_site_url( reverse('show_dictionary', kwargs={ 'project': prj.slug, 'lang': lang.code })), fieldnames=('source', 'target'), ) # Add words for word in words.iterator(): exporter.add_dictionary(word) # Save to response return exporter.get_response('glossary-{project}-{language}.{extension}')
def add_user(request, project): obj = get_project(request, project) form = AddUserForm(request.POST) if not obj.enable_acl: messages.error(request, _('ACL not enabled for this project!')) elif form.is_valid(): try: user = User.objects.get( Q(username=form.cleaned_data['name']) | Q(email=form.cleaned_data['name'])) obj.add_user(user) messages.success(request, _('User has been added to this project.')) except User.DoesNotExist: messages.error(request, _('No matching user found!')) except User.MultipleObjectsReturned: messages.error(request, _('More users matched!')) else: messages.error(request, _('Invalid user specified!')) return redirect_param( 'project', '#acl', project=obj.slug, )
def update_project(request, project): obj = get_project(request, project) if not can_update_translation(request.user, obj): raise PermissionDenied() return perform_update(request, obj)
def show_project(request, project): obj = get_project(request, project) dict_langs = Dictionary.objects.filter(project=obj).values_list( 'language', flat=True).distinct() dicts = [] for language in Language.objects.filter(id__in=dict_langs): dicts.append({ 'language': language, 'count': Dictionary.objects.filter(language=language, project=obj).count(), }) last_changes = Change.objects.prefetch().filter( Q(translation__subproject__project=obj) | Q(dictionary__project=obj))[:10] return render( request, 'project.html', { 'object': obj, 'project': obj, 'dicts': dicts, 'last_changes': last_changes, 'last_changes_url': urlencode({'project': obj.slug}), 'add_user_form': UserManageForm(), })
def git_status_project(request, project): obj = get_project(request, project) return render_to_response('js/git-status.html', RequestContext(request, { 'object': obj, }))
def update_project(request, project): obj = get_project(request, project) if not request.user.has_perm('vcs.update', obj): raise PermissionDenied() return perform_update(request, obj)
def delete_dictionary(request, project, lang, pk): prj = get_project(request, project) if not can_delete_dictionary(request.user, prj): raise PermissionDenied() lang = get_object_or_404(Language, code=lang) word = get_object_or_404( Dictionary, project=prj, language=lang, id=pk ) word.delete() params = {} for param in ('letter', 'limit', 'page'): if param in request.POST: params[param] = request.POST[param] if params: param = '?' + urlencode(params) else: param = '' return redirect_param( 'show_dictionary', param, project=prj.slug, lang=lang.code )
def parse_url(request, project, component=None, lang=None): context = {} if component is None: obj = get_project(request, project) perms = {'project': obj} unit_set = Unit.objects.filter(translation__component__project=obj) context['project'] = obj elif lang is None: obj = get_component(request, project, component) perms = {'project': obj.project} unit_set = Unit.objects.filter(translation__component=obj) context['component'] = obj context['project'] = obj.project else: obj = get_translation(request, project, component, lang) perms = {'translation': obj} unit_set = obj.unit_set context['translation'] = obj context['component'] = obj.component context['project'] = obj.component.project if not can_translate(request.user, **perms): raise PermissionDenied() return obj, unit_set, context
def push_project(request, project): obj = get_project(request, project) if obj.do_push(request): messages.success(request, _('All repositories were pushed.')) return redirect(obj)
def update_project(request, project): obj = get_project(request, project) if obj.do_update(request, method=request.GET.get('method', None)): messages.success(request, _('All repositories were updated.')) return redirect(obj)
def commit_project(request, project): obj = get_project(request, project) obj.commit_pending(request) messages.success(request, _('All pending translations were committed.')) return redirect(obj)
def edit_dictionary(request, project, lang): prj = get_project(request, project) lang = get_object_or_404(Language, code=lang) word = get_object_or_404(Dictionary, project=prj, language=lang, id=request.GET.get("id")) if request.method == "POST": form = WordForm(request.POST) if form.is_valid(): word.edit(request, form.cleaned_data["source"], form.cleaned_data["target"]) return redirect("show_dictionary", project=prj.slug, lang=lang.code) else: form = WordForm(initial={"source": word.source, "target": word.target}) last_changes = Change.objects.last_changes(request.user).filter(dictionary=word)[:10] return render( request, "edit_dictionary.html", { "title": dict_title(prj, lang), "project": prj, "language": lang, "form": form, "last_changes": last_changes, "last_changes_url": urlencode({"project": prj.slug, "lang": lang.code, "glossary": 1}), }, )
def push_project(request, project): obj = get_project(request, project) if not request.user.has_perm('vcs.push', obj): raise PermissionDenied() return perform_push(request, obj)
def render_widget(request, project, widget='287x66', color=None, lang=None, extension='png'): # We intentionally skip ACL here to allow widget sharing obj = get_project(request, project, skip_acl=True) # Handle language parameter if lang is not None: lang = try_set_language(lang) # Get widget class try: widget_class = WIDGETS[widget] except KeyError: raise Http404() # Construct object widget = widget_class(obj, color, lang) # Redirect widget if hasattr(widget, 'redirect'): return redirect(widget.redirect()) # Render widget widget.render() # Get image data data = widget.get_image() return HttpResponse(content_type=widget.content_type, content=data)
def reset_project(request, project): obj = get_project(request, project) if not request.user.has_perm('vcs.reset', obj): raise PermissionDenied() return perform_reset(request, obj)
def commit_project(request, project): obj = get_project(request, project) if not can_commit_translation(request.user, obj): raise PermissionDenied() return perform_commit(request, obj)
def reset_project(request, project): obj = get_project(request, project) if obj.do_reset(request): messages.info(request, _('All repositories have been reset.')) return redirect(obj)
def upload_dictionary(request, project, lang): prj = get_project(request, project) lang = get_object_or_404(Language, code=lang) form = DictUploadForm(request.POST, request.FILES) if form.is_valid(): try: count = Dictionary.objects.upload(request, prj, lang, request.FILES['file'], form.cleaned_data['method']) if count == 0: messages.warning(request, _('No words to import found in file.')) else: messages.success( request, ungettext('Imported %d word from the uploaded file.', 'Imported %d words from the uploaded file.', count) % count) except Exception as error: report_error(error, sys.exc_info(), request) messages.error(request, _('File upload has failed: %s') % unicode(error)) else: messages.error(request, _('Failed to process form!')) return redirect('show_dictionary', project=prj.slug, lang=lang.code)
def commit_project(request, project): obj = get_project(request, project) obj.commit_pending(request) messages.info(request, _('All pending translations were committed.')) return redirect(obj)
def update_project(request, project): obj = get_project(request, project) if obj.do_update(request): messages.info(request, _("All repositories were updated.")) return redirect(obj)
def update_project(request, project): obj = get_project(request, project) if obj.do_update(request): messages.info(request, _('All repositories were updated.')) return redirect(obj)
def render_widget(request, project, widget='287x66', color=None, lang=None, extension='png'): # We intentionally skip ACL here to allow widget sharing obj = get_project(request, project, skip_acl=True) # Handle language parameter if lang is not None: lang = try_set_language(lang) # Get widget class try: widget_class = WIDGETS[widget] except KeyError: raise Http404() # Construct object widget = widget_class(obj, color, lang) # Redirect widget if hasattr(widget, 'redirect'): return redirect(widget.redirect()) # Render widget widget.render() # Get image data data = widget.get_image() return HttpResponse( content_type=widget.content_type, content=data )
def push_project(request, project): obj = get_project(request, project) if obj.do_push(request): messages.info(request, _('All repositories were pushed.')) return redirect(obj)
def show_project(request, lang, project): try: obj = Language.objects.get(code=lang) except Language.DoesNotExist: obj = Language.objects.fuzzy_get(lang) if isinstance(obj, Language): return redirect(obj) raise Http404('No Language matches the given query.') pobj = get_project(request, project) last_changes = Change.objects.last_changes(request.user).filter( translation__language=obj, subproject__project=pobj)[:10] translations = obj.translation_set.enabled().filter( subproject__project=pobj).order_by('subproject__project__slug', 'subproject__slug') return render( request, 'language-project.html', { 'language': obj, 'project': pobj, 'last_changes': last_changes, 'last_changes_url': urlencode({ 'lang': obj.code, 'project': pobj.slug }), 'translations': translations, 'title': '{0} - {1}'.format(pobj, obj), 'show_only_component': True, })
def search(request, project=None, subproject=None, lang=None): """ Performs site-wide search on units. """ search_form = SiteSearchForm(request.GET) context = { 'search_form': search_form, } if subproject: obj = get_subproject(request, project, subproject) context['subproject'] = obj context['project'] = obj.project elif project: obj = get_project(request, project) context['project'] = obj else: obj = None if lang: context['language'] = get_object_or_404(Language, code=lang) if search_form.is_valid(): units = Unit.objects.search( None, search_form.cleaned_data, ) # Filter results by ACL if subproject: units = units.filter(translation__subproject=obj) elif project: units = units.filter(translation__subproject__project=obj) else: projects = Project.objects.get_acl_ids(request.user) units = units.filter( translation__subproject__project_id__in=projects) if lang: units = units.filter(translation__language=context['language']) limit = request.GET.get('limit', 50) page = request.GET.get('page', 1) paginator = Paginator(units, limit) try: units = paginator.page(page) except PageNotAnInteger: # If page is not an integer, deliver first page. units = paginator.page(1) except EmptyPage: # If page is out of range (e.g. 9999), deliver last page of # results. units = paginator.page(paginator.num_pages) context['page_obj'] = units context['title'] = _('Search for %s') % (search_form.cleaned_data['q']) context['query_string'] = search_form.urlencode() context['search_query'] = search_form.cleaned_data['q'] else: messages.error(request, _('Invalid search query!')) return render(request, 'search.html', context)
def change_access(request, project): obj = get_project(request, project) if not request.user.has_perm('billing:project.permissions', obj): raise PermissionDenied() form = ProjectAccessForm(request.POST, instance=obj) if not form.is_valid(): for error in form.errors: for message in form.errors[error]: messages.error(request, message) else: form.save() Change.objects.create( project=obj, action=Change.ACTION_ACCESS_EDIT, user=request.user, details={'access_control': obj.access_control}, ) messages.success( request, _('Project access control has been changed.') ) return redirect( 'manage-access', project=obj.slug, )
def change_project(request, project): obj = get_project(request, project) if not request.user.has_perm('project.edit', obj): raise Http404() if request.method == 'POST': settings_form = ProjectSettingsForm(request.POST, instance=obj) if settings_form.is_valid(): settings_form.save() messages.success(request, _('Settings saved')) return redirect('settings', project=obj.slug) else: messages.error( request, _('Invalid settings, please check the form for errors!') ) else: settings_form = ProjectSettingsForm(instance=obj) return render( request, 'project-settings.html', { 'object': obj, 'settings_form': settings_form, } )
def commit_project(request, project): obj = get_project(request, project) obj.commit_pending(request) messages.info(request, _("All pending translations were committed.")) return redirect(obj)
def reset_project(request, project): obj = get_project(request, project) if obj.do_reset(request): messages.info(request, _("All repositories have been reset.")) return redirect(obj)
def delete_user(request, project): obj = get_project(request, project) form = AddUserForm(request.POST) if form.is_valid(): try: user = User.objects.get( username=form.cleaned_data['name'] ) obj.remove_user(user) messages.success( request, _('User has been removed from this project.') ) except User.DoesNotExist: messages.error(request, _('No matching user found!')) except User.MultipleObjectsReturned: messages.error(request, _('More users matched!')) else: messages.error(request, _('Invalid user specified!')) return redirect_param( 'project', '#acl', project=obj.slug, )
def git_status_project(request, project): obj = get_project(request, project) if not can_see_repository_status(request.user, obj): raise PermissionDenied() statuses = [] included = set() not_linked = obj.subproject_set.exclude(repo__startswith="weblate://") linked = obj.subproject_set.filter(repo__startswith="weblate://") for subproject in not_linked: statuses.append((subproject.__unicode__(), subproject.repository.status)) included.add(subproject.get_repo_link_url()) for subproject in linked.exclude(repo__in=included): statuses.append((subproject.__unicode__(), subproject.repository.status)) return render( request, "js/git-status.html", { "object": obj, "project": obj, "changes": Change.objects.filter(subproject__project=obj, action__in=Change.ACTIONS_REPOSITORY)[:10], "statuses": statuses, }, )
def push_project(request, project): obj = get_project(request, project) if obj.do_push(request): messages.info(request, _("All repositories were pushed.")) return redirect(obj)
def add_user(request, project): obj = get_project(request, project) form = AddUserForm(request.POST) if not obj.enable_acl: messages.error(request, _('ACL not enabled for this project!')) elif form.is_valid(): try: user = User.objects.get( Q(username=form.cleaned_data['name']) | Q(email=form.cleaned_data['name']) ) obj.add_user(user) messages.success( request, _('User has been added to this project.') ) except User.DoesNotExist: messages.error(request, _('No matching user found!')) except User.MultipleObjectsReturned: messages.error(request, _('More users matched!')) else: messages.error(request, _('Invalid user specified!')) return redirect_param( 'project', '#acl', project=obj.slug, )
def reset_project(request, project): obj = get_project(request, project) if not can_reset_translation(request.user, obj): raise PermissionDenied() return perform_reset(request, obj)
def show_project(request, project): obj = get_project(request, project) dicts = Dictionary.objects.filter( project=obj ).values_list( 'language', flat=True ).distinct() last_changes = Change.objects.prefetch().filter( Q(translation__subproject__project=obj) | Q(dictionary__project=obj) ).order_by('-timestamp')[:10] return render( request, 'project.html', { 'object': obj, 'dicts': Language.objects.filter(id__in=dicts), 'last_changes': last_changes, 'last_changes_rss': reverse( 'rss-project', kwargs={'project': obj.slug} ), 'last_changes_url': urlencode( {'project': obj.slug} ), } )
def upload_dictionary(request, project, lang): prj = get_project(request, project) lang = get_object_or_404(Language, code=lang) form = DictUploadForm(request.POST, request.FILES) if form.is_valid(): try: count = Dictionary.objects.upload( request, prj, lang, request.FILES['file'], form.cleaned_data['method'] ) import_message( request, count, _('No words to import found in file.'), ungettext( 'Imported %d word from the uploaded file.', 'Imported %d words from the uploaded file.', count ) ) except Exception as error: report_error(error, sys.exc_info(), request) messages.error( request, _('File upload has failed: %s') % force_text(error) ) else: messages.error(request, _('Failed to process form!')) return redirect( 'show_dictionary', project=prj.slug, lang=lang.code )
def delete_dictionary(request, project, lang, pk): prj = get_project(request, project) if not request.user.has_perm('glossary.delete', prj): raise PermissionDenied() lang = get_object_or_404(Language, code=lang) word = get_object_or_404( Dictionary, project=prj, language=lang, id=pk ) word.delete() params = {} for param in ('letter', 'limit', 'page'): if param in request.POST: params[param] = request.POST[param] if params: param = '?' + urlencode(params) else: param = '' return redirect_param( 'show_dictionary', param, project=prj.slug, lang=lang.code )
def widgets(request, project): obj = get_project(request, project) # Parse possible language selection form = EngageForm(obj, request.GET) lang = None component = None if form.is_valid(): if form.cleaned_data['lang']: lang = Language.objects.get(code=form.cleaned_data['lang']).code if form.cleaned_data['component']: component = SubProject.objects.get( slug=form.cleaned_data['component'], project=obj).slug kwargs = {'project': obj.slug} if lang is not None: kwargs['lang'] = lang engage_url = get_site_url(reverse('engage', kwargs=kwargs)) engage_url_track = '{0}?utm_source=widget'.format(engage_url) widget_base_url = get_site_url( reverse('widgets', kwargs={'project': obj.slug})) widget_list = [] for widget_name in sorted(WIDGETS, key=widgets_sorter): widget_class = WIDGETS[widget_name] if not widget_class.show: continue color_list = [] for color in widget_class.colors: kwargs = { 'project': obj.slug, 'widget': widget_name, 'color': color, 'extension': widget_class.extension, } if lang is not None: kwargs['lang'] = lang if component is not None: kwargs['subproject'] = component color_url = reverse('widget-image', kwargs=kwargs) color_list.append({ 'name': color, 'url': get_site_url(color_url), }) widget_list.append({ 'name': widget_name, 'colors': color_list, }) return render( request, 'widgets.html', { 'engage_url': engage_url, 'engage_url_track': engage_url_track, 'widget_list': widget_list, 'widget_base_url': widget_base_url, 'object': obj, 'project': obj, 'image_src': widget_list[0]['colors'][0]['url'], 'form': form, })
def search(request, project=None, component=None, lang=None): """Perform site-wide search on units.""" if not check_rate_limit('search', request): search_form = SiteSearchForm() else: search_form = SiteSearchForm(request.GET) context = { 'search_form': search_form, } search_kwargs = {} if component: obj = get_component(request, project, component) context['component'] = obj context['project'] = obj.project search_kwargs = {'component': obj} elif project: obj = get_project(request, project) context['project'] = obj search_kwargs = {'project': obj} else: obj = None if lang: s_language = get_object_or_404(Language, code=lang) context['language'] = s_language search_kwargs = {'language': s_language} if search_form.is_valid(): # Filter results by ACL if component: units = Unit.objects.filter(translation__component=obj) elif project: units = Unit.objects.filter(translation__component__project=obj) else: allowed_projects = request.user.allowed_projects units = Unit.objects.filter( translation__component__project__in=allowed_projects) units = units.search(search_form.cleaned_data, **search_kwargs) if lang: units = units.filter(translation__language=context['language']) page, limit = get_page_limit(request, 50) paginator = Paginator(units, limit) try: units = paginator.page(page) except EmptyPage: # If page is out of range (e.g. 9999), deliver last page of # results. units = paginator.page(paginator.num_pages) context['page_obj'] = units context['title'] = _('Search for %s') % (search_form.cleaned_data['q']) context['query_string'] = search_form.urlencode() context['search_query'] = search_form.cleaned_data['q'] else: messages.error(request, _('Invalid search query!')) return render(request, 'search.html', context)
def delete_dictionary(request, project, lang): prj = get_project(request, project) lang = get_object_or_404(Language, code=lang) word = get_object_or_404(Dictionary, project=prj, language=lang, id=request.POST.get("id")) word.delete() return redirect("show_dictionary", project=prj.slug, lang=lang.code)
def data_project(request, project): obj = get_project(request, project) return render_to_response('data.html', RequestContext(request, { 'object': obj, 'hooks_docs': weblate.get_doc_url('api', 'hooks'), 'api_docs': weblate.get_doc_url('api', 'exports'), 'rss_docs': weblate.get_doc_url('api', 'rss'), }))
def show_project(request, project): obj = get_project(request, project) dict_langs = Language.objects.filter(dictionary__project=obj).annotate( Count('dictionary')) if request.method == 'POST' and can_edit_project(request.user, obj): settings_form = ProjectSettingsForm(request.POST, instance=obj) if settings_form.is_valid(): settings_form.save() messages.success(request, _('Settings saved')) return redirect(obj) else: messages.error( request, _('Invalid settings, please check the form for errors!')) else: settings_form = ProjectSettingsForm(instance=obj) last_changes = Change.objects.for_project(obj)[:10] language_stats = sort_unicode(get_per_language_stats(obj), lambda tup: force_text(tup[0])) language_stats = [(tup[0], translation_percent(tup[1], tup[2]), translation_percent(tup[3], tup[4])) for tup in language_stats] return render( request, 'project.html', { 'object': obj, 'project': obj, 'dicts': dict_langs, 'last_changes': last_changes, 'last_changes_url': urlencode({'project': obj.slug}), 'add_user_form': UserManageForm(), 'settings_form': settings_form, 'language_stats': language_stats, 'unit_count': Unit.objects.filter(translation__subproject__project=obj).count(), 'words_count': obj.get_total_words(), 'language_count': Language.objects.filter( translation__subproject__project=obj).distinct().count(), 'strings_count': obj.get_total(), 'source_words_count': obj.get_source_words(), })
def update_project(request, project): """API hook for updating git repos.""" if not settings.ENABLE_HOOKS: return HttpResponseNotAllowed([]) obj = get_project(request, project, True) if not obj.enable_hooks: return HttpResponseNotAllowed([]) perform_update(obj) return hook_response()
def show_engage(request, project, lang=None): # Get project object, skipping ACL obj = get_project(request, project, skip_acl=True) # Handle language parameter if lang is not None: language = Language.objects.try_get(code=lang) else: language = None if language: try_set_language(lang) stats_obj = obj.stats.get_single_language_stats(language) else: stats_obj = obj.stats percent = stats_obj.translated_percent languages = obj.get_language_count() context = { 'allow_index': True, 'object': obj, 'project': obj, 'languages': languages, 'total': obj.stats.source_strings, 'percent': percent, 'url': obj.get_absolute_url(), 'lang_url': obj.get_absolute_url() + '#languages', 'language': language, 'title': _('Get involved in {0}!').format(obj), } # Render text if language is None: status_text = _( '<a href="%(url)s">Translation project for %(project)s</a> ' 'currently contains %(total)s strings for translation and is ' '<a href="%(lang_url)s">being translated into %(languages)s ' 'languages</a>. Overall, these translations are %(percent)s%% ' 'complete.' ) else: # Translators: line of text in engagement page, please use your # language name instead of English status_text = _('<a href="%(url)s">Translation project for ' '%(project)s</a> into English currently contains ' '%(total)s strings for translation and is ' '%(percent)s%% complete.') if 'English' in status_text: status_text = status_text.replace('English', language.name) context['status_text'] = mark_safe(status_text % context) return render( request, 'engage.html', context )
def show_dictionaries(request, project): obj = get_project(request, project) dicts = Translation.objects.filter(subproject__project=obj).values_list("language", flat=True).distinct() return render( request, "dictionaries.html", {"title": _("Dictionaries"), "dicts": Language.objects.filter(id__in=dicts), "project": obj}, )
def update_project(request, project): ''' API hook for updating git repos. ''' if not appsettings.ENABLE_HOOKS: return HttpResponseNotAllowed([]) obj = get_project(request, project, True) perform_update(obj) return HttpResponse('update triggered')
def data_project(request, project): obj = get_project(request, project) return render( request, 'data.html', { 'object': obj, 'hooks_docs': weblate.get_doc_url('api', 'hooks'), 'api_docs': weblate.get_doc_url('api', 'exports'), 'rss_docs': weblate.get_doc_url('api', 'rss'), })
def git_status_project(request, project): obj = get_project(request, project) return render( request, 'js/git-status.html', { 'object': obj, } )
def data_project(request, project): obj = get_project(request, project) return render( request, 'data.html', { 'object': obj, 'project': obj, } )