diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | awx/api/metadata.py | 19 | ||||
-rw-r--r-- | awx/api/serializers.py | 32 | ||||
-rw-r--r-- | awx/api/views.py | 47 | ||||
-rw-r--r-- | awx/main/access.py | 29 | ||||
-rw-r--r-- | awx/main/views.py | 19 | ||||
-rw-r--r-- | awx/settings/defaults.py | 99 | ||||
-rw-r--r-- | awx/sso/pipeline.py | 7 | ||||
-rw-r--r-- | awx/templates/rest_framework/api.html | 6 | ||||
-rw-r--r-- | awx/templates/rest_framework/base.html | 24 | ||||
-rw-r--r-- | awx/ui/templates/ui/index.html | 23 |
12 files changed, 182 insertions, 145 deletions
diff --git a/.gitignore b/.gitignore index afd8aa7187..ca9dd12298 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,7 @@ reports *.log.[0-9] *.results local/ +*.mo # AWX python libs populated by requirements.txt awx/lib/.deps_built @@ -493,6 +493,25 @@ test_tox: # Alias existing make target so old versions run against Jekins the same way test_jenkins : test_coverage +# l10n TASKS +# -------------------------------------- + +LANG = "en-us" +messages: + @if [ "$(VENV_BASE)" ]; then \ + . $(VENV_BASE)/tower/bin/activate; \ + fi; \ + $(PYTHON) manage.py makemessages -l $(LANG) + +languages: + @if [ "$(VENV_BASE)" ]; then \ + . $(VENV_BASE)/tower/bin/activate; \ + fi; \ + $(PYTHON) manage.py compilemessages + +# End l10n TASKS +# -------------------------------------- + # UI TASKS # -------------------------------------- @@ -506,7 +525,7 @@ ui-docker-machine: $(UI_DEPS_FLAG_FILE) ui-docker: $(UI_DEPS_FLAG_FILE) $(NPM_BIN) --prefix awx/ui run build-docker-cid -ui-release: $(UI_RELEASE_FLAG_FILE) +ui-release: languages $(UI_RELEASE_FLAG_FILE) $(UI_RELEASE_FLAG_FILE): $(UI_DEPS_FLAG_FILE) $(NPM_BIN) --prefix awx/ui run build-release diff --git a/awx/api/metadata.py b/awx/api/metadata.py index b329d83793..54850f285e 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -7,6 +7,7 @@ from collections import OrderedDict from django.core.exceptions import PermissionDenied from django.http import Http404 from django.utils.encoding import force_text, smart_text +from django.utils.translation import ugettext_lazy as _ # Django REST Framework from rest_framework import exceptions @@ -46,15 +47,15 @@ class Metadata(metadata.SimpleMetadata): serializer = getattr(field, 'parent', None) if serializer: field_help_text = { - 'id': 'Database ID for this {}.', - 'name': 'Name of this {}.', - 'description': 'Optional description of this {}.', - 'type': 'Data type for this {}.', - 'url': 'URL for this {}.', - 'related': 'Data structure with URLs of related resources.', - 'summary_fields': 'Data structure with name/description for related resources.', - 'created': 'Timestamp when this {} was created.', - 'modified': 'Timestamp when this {} was last modified.', + 'id': _('Database ID for this {}.'), + 'name': _('Name of this {}.'), + 'description': _('Optional description of this {}.'), + 'type': _('Data type for this {}.'), + 'url': _('URL for this {}.'), + 'related': _('Data structure with URLs of related resources.'), + 'summary_fields': _('Data structure with name/description for related resources.'), + 'created': _('Timestamp when this {} was created.'), + 'modified': _('Timestamp when this {} was last modified.'), } if field.field_name in field_help_text: if hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'): diff --git a/awx/api/serializers.py b/awx/api/serializers.py index ded5d454c3..7e507015b4 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -20,7 +20,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError from django.db import models -# from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text from django.utils.text import capfirst @@ -694,9 +694,9 @@ class UnifiedJobStdoutSerializer(UnifiedJobSerializer): class UserSerializer(BaseSerializer): password = serializers.CharField(required=False, default='', write_only=True, - help_text='Write-only field used to change the password.') + help_text=_('Write-only field used to change the password.')) ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True) - external_account = serializers.SerializerMethodField(help_text='Set if the account is managed by an external service') + external_account = serializers.SerializerMethodField(help_text=_('Set if the account is managed by an external service')) is_system_auditor = serializers.BooleanField(default=False) show_capabilities = ['edit', 'delete'] @@ -961,7 +961,7 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer): class ProjectPlaybooksSerializer(ProjectSerializer): - playbooks = serializers.ReadOnlyField(help_text='Array of playbooks available within this project.') + playbooks = serializers.ReadOnlyField(help_text=_('Array of playbooks available within this project.')) class Meta: model = Project @@ -1717,18 +1717,18 @@ class CredentialSerializerCreate(CredentialSerializer): user = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), required=False, default=None, write_only=True, allow_null=True, - help_text='Write-only field used to add user to owner role. If provided, ' - 'do not give either team or organization. Only valid for creation.') + help_text=_('Write-only field used to add user to owner role. If provided, ' + 'do not give either team or organization. Only valid for creation.')) team = serializers.PrimaryKeyRelatedField( queryset=Team.objects.all(), required=False, default=None, write_only=True, allow_null=True, - help_text='Write-only field used to add team to owner role. If provided, ' - 'do not give either user or organization. Only valid for creation.') + help_text=_('Write-only field used to add team to owner role. If provided, ' + 'do not give either user or organization. Only valid for creation.')) organization = serializers.PrimaryKeyRelatedField( queryset=Organization.objects.all(), required=False, default=None, write_only=True, allow_null=True, - help_text='Write-only field used to add organization to owner role. If provided, ' - 'do not give either team or team. Only valid for creation.') + help_text=_('Write-only field used to add organization to owner role. If provided, ' + 'do not give either team or team. Only valid for creation.')) class Meta: model = Credential @@ -2791,15 +2791,15 @@ class ActivityStreamSerializer(BaseSerializer): ret = super(ActivityStreamSerializer, self).get_fields() for key, field in ret.items(): if key == 'changes': - field.help_text = 'A summary of the new and changed values when an object is created, updated, or deleted' + field.help_text = _('A summary of the new and changed values when an object is created, updated, or deleted') if key == 'object1': - field.help_text = ('For create, update, and delete events this is the object type that was affected. ' - 'For associate and disassociate events this is the object type associated or disassociated with object2.') + field.help_text = _('For create, update, and delete events this is the object type that was affected. ' + 'For associate and disassociate events this is the object type associated or disassociated with object2.') if key == 'object2': - field.help_text = ('Unpopulated for create, update, and delete events. For associate and disassociate ' - 'events this is the object type that object1 is being associated with.') + field.help_text = _('Unpopulated for create, update, and delete events. For associate and disassociate ' + 'events this is the object type that object1 is being associated with.') if key == 'operation': - field.help_text = 'The action taken with respect to the given object(s).' + field.help_text = _('The action taken with respect to the given object(s).') return ret def get_changes(self, obj): diff --git a/awx/api/views.py b/awx/api/views.py index b5e6c5a45a..b22dc60b53 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -29,6 +29,7 @@ from django.template.loader import render_to_string from django.core.servers.basehttp import FileWrapper from django.http import HttpResponse from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext_lazy as _ # Django REST Framework @@ -86,14 +87,14 @@ class ApiRootView(APIView): authentication_classes = [] permission_classes = (AllowAny,) - view_name = 'REST API' + view_name = _('REST API') def get(self, request, format=None): ''' list supported API versions ''' current = reverse('api:api_v1_root_view', args=[]) data = dict( - description = 'Ansible Tower REST API', + description = _('Ansible Tower REST API'), current_version = current, available_versions = dict( v1 = current @@ -105,7 +106,7 @@ class ApiV1RootView(APIView): authentication_classes = [] permission_classes = (AllowAny,) - view_name = 'Version 1' + view_name = _('Version 1') def get(self, request, format=None): ''' list top level resources ''' @@ -156,7 +157,7 @@ class ApiV1PingView(APIView): """ permission_classes = (AllowAny,) authentication_classes = () - view_name = 'Ping' + view_name = _('Ping') new_in_210 = True def get(self, request, format=None): @@ -182,7 +183,7 @@ class ApiV1PingView(APIView): class ApiV1ConfigView(APIView): permission_classes = (IsAuthenticated,) - view_name = 'Configuration' + view_name = _('Configuration') def get(self, request, format=None): '''Return various sitewide configuration settings.''' @@ -278,7 +279,7 @@ class ApiV1ConfigView(APIView): class DashboardView(APIView): - view_name = "Dashboard" + view_name = _("Dashboard") new_in_14 = True def get(self, request, format=None): @@ -383,7 +384,7 @@ class DashboardView(APIView): class DashboardJobsGraphView(APIView): - view_name = "Dashboard Jobs Graphs" + view_name = _("Dashboard Jobs Graphs") new_in_200 = True def get(self, request, format=None): @@ -433,7 +434,7 @@ class DashboardJobsGraphView(APIView): class ScheduleList(ListAPIView): - view_name = "Schedules" + view_name = _("Schedules") model = Schedule serializer_class = ScheduleSerializer new_in_148 = True @@ -450,7 +451,7 @@ class ScheduleUnifiedJobsList(SubListAPIView): serializer_class = UnifiedJobSerializer parent_model = Schedule relationship = 'unifiedjob_set' - view_name = 'Schedule Jobs List' + view_name = _('Schedule Jobs List') new_in_148 = True class AuthView(APIView): @@ -998,7 +999,7 @@ class ProjectTeamsList(ListAPIView): class ProjectSchedulesList(SubListCreateAttachDetachAPIView): - view_name = "Project Schedules" + view_name = _("Project Schedules") model = Schedule serializer_class = ScheduleSerializer @@ -1162,7 +1163,7 @@ class UserMeList(ListAPIView): model = User serializer_class = UserSerializer - view_name = 'Me' + view_name = _('Me') def get_queryset(self): return self.model.objects.filter(pk=self.request.user.pk) @@ -2038,7 +2039,7 @@ class InventoryInventorySourcesList(SubListAPIView): serializer_class = InventorySourceSerializer parent_model = Inventory relationship = None # Not defined since using get_queryset(). - view_name = 'Inventory Source List' + view_name = _('Inventory Source List') new_in_14 = True def get_queryset(self): @@ -2071,7 +2072,7 @@ class InventorySourceDetail(RetrieveUpdateAPIView): class InventorySourceSchedulesList(SubListCreateAttachDetachAPIView): - view_name = "Inventory Source Schedules" + view_name = _("Inventory Source Schedules") model = Schedule serializer_class = ScheduleSerializer @@ -2292,7 +2293,7 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView): class JobTemplateSchedulesList(SubListCreateAttachDetachAPIView): - view_name = "Job Template Schedules" + view_name = _("Job Template Schedules") model = Schedule serializer_class = ScheduleSerializer @@ -2823,7 +2824,7 @@ class SystemJobTemplateLaunch(GenericAPIView): class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView): - view_name = "System Job Template Schedules" + view_name = _("System Job Template Schedules") model = Schedule serializer_class = ScheduleSerializer @@ -2991,7 +2992,7 @@ class BaseJobHostSummariesList(SubListAPIView): serializer_class = JobHostSummarySerializer parent_model = None # Subclasses must define this attribute. relationship = 'job_host_summaries' - view_name = 'Job Host Summaries List' + view_name = _('Job Host Summaries List') class HostJobHostSummariesList(BaseJobHostSummariesList): @@ -3026,7 +3027,7 @@ class JobEventChildrenList(SubListAPIView): serializer_class = JobEventSerializer parent_model = JobEvent relationship = 'children' - view_name = 'Job Event Children List' + view_name = _('Job Event Children List') class JobEventHostsList(SubListAPIView): @@ -3034,7 +3035,7 @@ class JobEventHostsList(SubListAPIView): serializer_class = HostSerializer parent_model = JobEvent relationship = 'hosts' - view_name = 'Job Event Hosts List' + view_name = _('Job Event Hosts List') class BaseJobEventsList(SubListAPIView): @@ -3042,7 +3043,7 @@ class BaseJobEventsList(SubListAPIView): serializer_class = JobEventSerializer parent_model = None # Subclasses must define this attribute. relationship = 'job_events' - view_name = 'Job Events List' + view_name = _('Job Events List') class HostJobEventsList(BaseJobEventsList): @@ -3074,7 +3075,7 @@ class JobJobEventsList(BaseJobEventsList): class JobJobPlaysList(BaseJobEventsList): parent_model = Job - view_name = 'Job Plays List' + view_name = _('Job Plays List') new_in_200 = True @paginated @@ -3149,7 +3150,7 @@ class JobJobTasksList(BaseJobEventsList): and their completion status. """ parent_model = Job - view_name = 'Job Play Tasks List' + view_name = _('Job Play Tasks List') new_in_200 = True @paginated @@ -3444,7 +3445,7 @@ class BaseAdHocCommandEventsList(SubListAPIView): serializer_class = AdHocCommandEventSerializer parent_model = None # Subclasses must define this attribute. relationship = 'ad_hoc_command_events' - view_name = 'Ad Hoc Command Events List' + view_name = _('Ad Hoc Command Events List') new_in_220 = True @@ -3660,7 +3661,7 @@ class NotificationTemplateDetail(RetrieveUpdateDestroyAPIView): class NotificationTemplateTest(GenericAPIView): - view_name = 'NotificationTemplate Test' + view_name = _('NotificationTemplate Test') model = NotificationTemplate serializer_class = EmptySerializer new_in_300 = True diff --git a/awx/main/access.py b/awx/main/access.py index d70ce55af1..891a8b97e3 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -11,6 +11,7 @@ from django.conf import settings from django.db.models import Q from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext_lazy as _ # Django REST Framework from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError @@ -199,24 +200,24 @@ class BaseAccess(object): validation_info['grace_period_remaining'] = 99999999 if check_expiration and validation_info.get('time_remaining', None) is None: - raise PermissionDenied("License is missing.") + raise PermissionDenied(_("License is missing.")) if check_expiration and validation_info.get("grace_period_remaining") <= 0: - raise PermissionDenied("License has expired.") + raise PermissionDenied(_("License has expired.")) free_instances = validation_info.get('free_instances', 0) available_instances = validation_info.get('available_instances', 0) if add_host and free_instances == 0: - raise PermissionDenied("License count of %s instances has been reached." % available_instances) + raise PermissionDenied(_("License count of %s instances has been reached.") % available_instances) elif add_host and free_instances < 0: - raise PermissionDenied("License count of %s instances has been exceeded." % available_instances) + raise PermissionDenied(_("License count of %s instances has been exceeded.") % available_instances) elif not add_host and free_instances < 0: - raise PermissionDenied("Host count exceeds available instances.") + raise PermissionDenied(_("Host count exceeds available instances.")) if feature is not None: if "features" in validation_info and not validation_info["features"].get(feature, False): - raise LicenseForbids("Feature %s is not enabled in the active license." % feature) + raise LicenseForbids(_("Feature %s is not enabled in the active license.") % feature) elif "features" not in validation_info: - raise LicenseForbids("Features not found in active license.") + raise LicenseForbids(_("Features not found in active license.")) def get_user_capabilities(self, obj, method_list=[], parent_obj=None): if obj is None: @@ -535,7 +536,7 @@ class HostAccess(BaseAccess): # Prevent moving a host to a different inventory. inventory_pk = get_pk_from_dict(data, 'inventory') if obj and inventory_pk and obj.inventory.pk != inventory_pk: - raise PermissionDenied('Unable to change inventory on a host.') + raise PermissionDenied(_('Unable to change inventory on a host.')) # Checks for admin or change permission on inventory, controls whether # the user can edit variable data. return obj and self.user in obj.inventory.admin_role @@ -547,7 +548,7 @@ class HostAccess(BaseAccess): return False # Prevent assignments between different inventories. if obj.inventory != sub_obj.inventory: - raise ParseError('Cannot associate two items from different inventories.') + raise ParseError(_('Cannot associate two items from different inventories.')) return True def can_delete(self, obj): @@ -581,7 +582,7 @@ class GroupAccess(BaseAccess): # Prevent moving a group to a different inventory. inventory_pk = get_pk_from_dict(data, 'inventory') if obj and inventory_pk and obj.inventory.pk != inventory_pk: - raise PermissionDenied('Unable to change inventory on a group.') + raise PermissionDenied(_('Unable to change inventory on a group.')) # Checks for admin or change permission on inventory, controls whether # the user can attach subgroups or edit variable data. return obj and self.user in obj.inventory.admin_role @@ -593,7 +594,7 @@ class GroupAccess(BaseAccess): return False # Prevent assignments between different inventories. if obj.inventory != sub_obj.inventory: - raise ParseError('Cannot associate two items from different inventories.') + raise ParseError(_('Cannot associate two items from different inventories.')) # Prevent group from being assigned as its own (grand)child. if type(obj) == type(sub_obj): parent_pks = set(obj.all_parents.values_list('pk', flat=True)) @@ -805,7 +806,7 @@ class TeamAccess(BaseAccess): # Prevent moving a team to a different organization. org_pk = get_pk_from_dict(data, 'organization') if obj and org_pk and obj.organization.pk != org_pk: - raise PermissionDenied('Unable to change organization on a team.') + raise PermissionDenied(_('Unable to change organization on a team.')) if self.user.is_superuser: return True return self.user in obj.admin_role @@ -818,9 +819,9 @@ class TeamAccess(BaseAccess): of a resource role to the team.""" if isinstance(sub_obj, Role): if sub_obj.content_object is None: - raise PermissionDenied("The {} role cannot be assigned to a team".format(sub_obj.name)) + raise PermissionDenied(_("The {} role cannot be assigned to a team").format(sub_obj.name)) elif isinstance(sub_obj.content_object, User): - raise PermissionDenied("The admin_role for a User cannot be assigned to a team") + raise PermissionDenied(_("The admin_role for a User cannot be assigned to a team")) if isinstance(sub_obj.content_object, ResourceMixin): role_access = RoleAccess(self.user) diff --git a/awx/main/views.py b/awx/main/views.py index a1036a96e6..f476f81cfd 100644 --- a/awx/main/views.py +++ b/awx/main/views.py @@ -4,6 +4,7 @@ # Django from django.shortcuts import render from django.utils.html import format_html +from django.utils.translation import ugettext_lazy as _ # Django REST Framework from rest_framework import exceptions, permissions, views @@ -16,7 +17,7 @@ class ApiErrorView(views.APIView): metadata_class = None allowed_methods = ('GET', 'HEAD') exception_class = exceptions.APIException - view_name = 'API Error' + view_name = _('API Error') def get_view_name(self): return self.view_name @@ -45,31 +46,31 @@ def handle_error(request, status=404, **kwargs): def handle_400(request): kwargs = { - 'name': 'Bad Request', - 'content': 'The request could not be understood by the server.', + 'name': _('Bad Request'), + 'content': _('The request could not be understood by the server.'), } return handle_error(request, 400, **kwargs) def handle_403(request): kwargs = { - 'name': 'Forbidden', - 'content': 'You don\'t have permission to access the requested resource.', + 'name': _('Forbidden'), + 'content': _('You don\'t have permission to access the requested resource.'), } return handle_error(request, 403, **kwargs) def handle_404(request): kwargs = { - 'name': 'Not Found', - 'content': 'The requested resource could not be found.', + 'name': _('Not Found'), + 'content': _('The requested resource could not be found.'), } return handle_error(request, 404, **kwargs) def handle_500(request): kwargs = { - 'name': 'Server Error', - 'content': 'A server error has occurred.', + 'name': _('Server Error'), + 'content': _('A server error has occurred.'), } return handle_error(request, 500, **kwargs) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 630b5ee6ee..3d64651dea 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -18,6 +18,9 @@ for setting in dir(global_settings): if setting == setting.upper(): setattr(this_module, setting, getattr(global_settings, setting)) +# gettext +from django.utils.translation import ugettext_lazy as _ + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -118,6 +121,11 @@ LOG_ROOT = os.path.join(BASE_DIR) # The heartbeat file for the tower scheduler SCHEDULE_METADATA_LOCATION = os.path.join(BASE_DIR, '.tower_cycle') +# Django gettext files path: locale/<lang-code>/LC_MESSAGES/django.po, django.mo +LOCALE_PATHS = ( + os.path.join(BASE_DIR, 'locale'), +) + # Maximum number of the same job that can be waiting to run when launching from scheduler # Note: This setting may be overridden by database settings. SCHEDULE_MAX_JOBS = 10 @@ -155,8 +163,9 @@ TEMPLATE_CONTEXT_PROCESSORS = ( # NOQA ) MIDDLEWARE_CLASSES = ( # NOQA - 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', @@ -560,12 +569,12 @@ AD_HOC_COMMANDS = [ # instead (based on docs from: # http://docs.rackspace.com/loadbalancers/api/v1.0/clb-devguide/content/Service_Access_Endpoints-d1e517.html) RAX_REGION_CHOICES = [ - ('ORD', 'Chicago'), - ('DFW', 'Dallas/Ft. Worth'), - ('IAD', 'Northern Virginia'), - ('LON', 'London'), - ('SYD', 'Sydney'), - ('HKG', 'Hong Kong'), + ('ORD', _('Chicago')), + ('DFW', _('Dallas/Ft. Worth')), + ('IAD', _('Northern Virginia')), + ('LON', _('London')), + ('SYD', _('Sydney')), + ('HKG', _('Hong Kong')), ] # Inventory variable name/values for determining if host is active/enabled. @@ -592,18 +601,18 @@ INV_ENV_VARIABLE_BLACKLIST = ("HOME", "USER", "_", "TERM") # list of names here. The available region IDs will be pulled from boto. # http://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region EC2_REGION_NAMES = { - 'us-east-1': 'US East (Northern Virginia)', - 'us-west-2': 'US West (Oregon)', - 'us-west-1': 'US West (Northern California)', - 'eu-central-1': 'EU (Frankfurt)', - 'eu-west-1': 'EU (Ireland)', - 'ap-southeast-1': 'Asia Pacific (Singapore)', - 'ap-southeast-2': 'Asia Pacific (Sydney)', - 'ap-northeast-1': 'Asia Pacific (Tokyo)', - 'ap-northeast-2': 'Asia Pacific (Seoul)', - 'sa-east-1': 'South America (Sao Paulo)', - 'us-gov-west-1': 'US West (GovCloud)', - 'cn-north-1': 'China (Beijing)', + 'us-east-1': _('US East (Northern Virginia)'), + 'us-west-2': _('US West (Oregon)'), + 'us-west-1': _('US West (Northern California)'), + 'eu-central-1': _('EU (Frankfurt)'), + 'eu-west-1': _('EU (Ireland)'), + 'ap-southeast-1': _('Asia Pacific (Singapore)'), + 'ap-southeast-2': _('Asia Pacific (Sydney)'), + 'ap-northeast-1': _('Asia Pacific (Tokyo)'), + 'ap-northeast-2': _('Asia Pacific (Seoul)'), + 'sa-east-1': _('South America (Sao Paulo)'), + 'us-gov-west-1': _('US West (GovCloud)'), + 'cn-north-1': _('China (Beijing)'), } EC2_REGIONS_BLACKLIST = [ @@ -652,19 +661,19 @@ VMWARE_EXCLUDE_EMPTY_GROUPS = True # provide a list here. # Source: https://developers.google.com/compute/docs/zones GCE_REGION_CHOICES = [ - ('us-east1-b', 'US East (B)'), - ('us-east1-c', 'US East (C)'), - ('us-east1-d', 'US East (D)'), - ('us-central1-a', 'US Central (A)'), - ('us-central1-b', 'US Central (B)'), - ('us-central1-c', 'US Central (C)'), - ('us-central1-f', 'US Central (F)'), - ('europe-west1-b', 'Europe West (B)'), - ('europe-west1-c', 'Europe West (C)'), - ('europe-west1-d', 'Europe West (D)'), - ('asia-east1-a', 'Asia East (A)'), - ('asia-east1-b', 'Asia East (B)'), - ('asia-east1-c', 'Asia East (C)'), + ('us-east1-b', _('US East (B)')), + ('us-east1-c', _('US East (C)')), + ('us-east1-d', _('US East (D)')), + ('us-central1-a', _('US Central (A)')), + ('us-central1-b', _('US Central (B)')), + ('us-central1-c', _('US Central (C)')), + ('us-central1-f', _('US Central (F)')), + ('europe-west1-b', _('Europe West (B)')), + ('europe-west1-c', _('Europe West (C)')), + ('europe-west1-d', _('Europe West (D)')), + ('asia-east1-a', _('Asia East (A)')), + ('asia-east1-b', _('Asia East (B)')), + ('asia-east1-c', _('Asia East (C)')), ] GCE_REGIONS_BLACKLIST = [] @@ -688,19 +697,19 @@ GCE_INSTANCE_ID_VAR = None # It's not possible to get zones in Azure without authenticating, so we # provide a list here. AZURE_REGION_CHOICES = [ - ('Central_US', 'US Central'), - ('East_US_1', 'US East'), - ('East_US_2', 'US East 2'), - ('North_Central_US', 'US North Central'), - ('South_Central_US', 'US South Central'), - ('West_US', 'US West'), - ('North_Europe', 'Europe North'), - ('West_Europe', 'Europe West'), - ('East_Asia_Pacific', 'Asia Pacific East'), - ('Southest_Asia_Pacific', 'Asia Pacific Southeast'), - ('East_Japan', 'Japan East'), - ('West_Japan', 'Japan West'), - ('South_Brazil', 'Brazil South'), + ('Central_US', _('US Central')), + ('East_US_1', _('US East')), + ('East_US_2', _('US East 2')), + ('North_Central_US', _('US North Central')), + ('South_Central_US', _('US South Central')), + ('West_US', _('US West')), + ('North_Europe', _('Europe North')), + ('West_Europe', _('Europe West')), + ('East_Asia_Pacific', _('Asia Pacific East')), + ('Southest_Asia_Pacific', _('Asia Pacific Southeast')), + ('East_Japan', _('Japan East')), + ('West_Japan', _('Japan West')), + ('South_Brazil', _('Brazil South')), ] AZURE_REGIONS_BLACKLIST = [] diff --git a/awx/sso/pipeline.py b/awx/sso/pipeline.py index 738a9b3b0c..2a16eb25b0 100644 --- a/awx/sso/pipeline.py +++ b/awx/sso/pipeline.py @@ -7,6 +7,9 @@ import re # Python Social Auth from social.exceptions import AuthException +# Django +from django.utils.translation import ugettext_lazy as _ + # Tower from awx.conf.license import feature_enabled @@ -18,13 +21,13 @@ class AuthNotFound(AuthException): super(AuthNotFound, self).__init__(backend, *args, **kwargs) def __str__(self): - return 'An account cannot be found for {0}'.format(self.email_or_uid) + return _('An account cannot be found for {0}').format(self.email_or_uid) class AuthInactive(AuthException): def __str__(self): - return 'Your account is inactive' + return _('Your account is inactive') def check_user_found_or_created(backend, details, user=None, *args, **kwargs): diff --git a/awx/templates/rest_framework/api.html b/awx/templates/rest_framework/api.html index c40d81ff63..746521f542 100644 --- a/awx/templates/rest_framework/api.html +++ b/awx/templates/rest_framework/api.html @@ -36,9 +36,9 @@ {% if user.is_authenticated %} <li><a href="{% url 'api:user_me_list' %}" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Logged in as {{ user }}{% if user.get_full_name %} ({{ user.get_full_name }}){% endif %}"><span class="glyphicon glyphicon-user"></span> <span class="visible-xs-inline">Logged in as </span>{{ user }}{% if user.get_full_name %}<span class="visible-xs-inline"> ({{ user.get_full_name }})</span>{% endif %}</a></li> {% endif %} - <li><a href="//docs.ansible.com/ansible-tower/{{short_tower_version}}/html/towerapi/index.html" target="_blank" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Ansible Tower API Guide"><span class="glyphicon glyphicon-question-sign"></span><span class="visible-xs-inline"> Ansible Tower API Guide</span></a></li> - <li><a href="/" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Back to Ansible Tower"><span class="glyphicon glyphicon-circle-arrow-left"></span><span class="visible-xs-inline"> Back to Ansible Tower</span></a></li> - <li class="hidden-xs"><a href="#" class="resize" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="Resize"><span class="glyphicon glyphicon-resize-full"></span></a></li> + <li><a href="//docs.ansible.com/ansible-tower/{{short_tower_version}}/html/towerapi/index.html" target="_blank" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Ansible Tower API Guide' %}"><span class="glyphicon glyphicon-question-sign"></span><span class="visible-xs-inline">{% trans 'Ansible Tower API Guide' %}</span></a></li> + <li><a href="/" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Back to Ansible Tower' %}"><span class="glyphicon glyphicon-circle-arrow-left"></span><span class="visible-xs-inline">{% trans 'Back to Ansible Tower' %}</span></a></li> + <li class="hidden-xs"><a href="#" class="resize" data-toggle="tooltip" data-placement="bottom" data-delay="1000" title="{% trans 'Resize' %}"><span class="glyphicon glyphicon-resize-full"></span></a></li> </ul> </div> </div> diff --git a/awx/templates/rest_framework/base.html b/awx/templates/rest_framework/base.html index 6ed3cd456f..a6c4169ebd 100644 --- a/awx/templates/rest_framework/base.html +++ b/awx/templates/rest_framework/base.html @@ -1,8 +1,8 @@ +<!DOCTYPE html> {# Copy of base.html from rest_framework with minor Ansible Tower change. #} {% load staticfiles %} {% load rest_framework %} {% load i18n %} -<!DOCTYPE html> <html> <head> {% block head %} @@ -75,21 +75,21 @@ <fieldset> {% if api_settings.URL_FORMAT_OVERRIDE %} <div class="btn-group format-selection"> - <a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a> + <a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="{% blocktrans %}Make a GET request on the {{ name }} resource{% endblocktrans %}">GET</a> - <button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request"> + <button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="{% trans 'Specify a format for the GET request' %}"> <span class="caret"></span> </button> <ul class="dropdown-menu"> {% for format in available_formats %} <li> - <a class="js-tooltip format-option" href="{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}" rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a> + <a class="js-tooltip format-option" href="{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}" rel="nofollow" title="{% blocktrans %}Make a GET request on the {{ name }} resource with the format set to `{{ format }}`{% endblocktrans %}">{{ format }}</a> </li> {% endfor %} </ul> </div> {% else %} - <a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a> + <a class="btn btn-primary js-tooltip" href="{{ request.get_full_path }}" rel="nofollow" title="{% blocktrans %}Make a GET request on the {{ name }} resource{% endblocktrans %}">GET</a> {% endif %} </fieldset> </form> @@ -97,13 +97,13 @@ {% if options_form %} <form class="button-form" action="{{ request.get_full_path }}" data-method="OPTIONS"> - <button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button> + <button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make an OPTIONS request on the {{ name }} resource{% endblocktrans %}">OPTIONS</button> </form> {% endif %} {% if delete_form %} <form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE"> - <button class="btn btn-danger js-tooltip" title="Make a DELETE request on the {{ name }} resource">DELETE</button> + <button class="btn btn-danger js-tooltip" title="{% blocktrans %}Make a DELETE request on the {{ name }} resource{% endblocktrans %}">DELETE</button> </form> {% endif %} @@ -169,7 +169,7 @@ {% csrf_token %} {{ post_form }} <div class="form-actions"> - <button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button> + <button class="btn btn-primary" title="{% blocktrans %}Make a POST request on the {{ name }} resource{% endblocktrans %}">POST</button> </div> </fieldset> </form> @@ -183,7 +183,7 @@ <fieldset> {% include "rest_framework/raw_data_form.html" %} <div class="form-actions"> - <button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button> + <button class="btn btn-primary" title="{% blocktrans %}Make a POST request on the {{ name }} resource{% endblocktrans %}">POST</button> </div> </fieldset> </form> @@ -213,7 +213,7 @@ <fieldset> {{ put_form }} <div class="form-actions"> - <button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button> + <button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button> </div> </fieldset> </form> @@ -227,10 +227,10 @@ {% include "rest_framework/raw_data_form.html" %} <div class="form-actions"> {% if raw_data_put_form %} - <button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button> + <button class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PUT request on the {{ name }} resource{% endblocktrans %}">PUT</button> {% endif %} {% if raw_data_patch_form %} - <button data-method="PATCH" class="btn btn-primary js-tooltip" title="Make a PATCH request on the {{ name }} resource">PATCH</button> + <button data-method="PATCH" class="btn btn-primary js-tooltip" title="{% blocktrans %}Make a PATCH request on the {{ name }} resource{% endblocktrans %}">PATCH</button> {% endif %} </div> </fieldset> diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index d5777c9619..2eb6c0f599 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -1,9 +1,10 @@ <!DOCTYPE html> +{% load i18n %} <html lang="en" ng-app="Tower"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> - <title>Ansible Tower</title> + <title>{% trans 'Ansible Tower' %}</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="{{ STATIC_URL }}assets/custom-theme/jquery-ui-1.10.3.custom.min.css" /> <link rel="stylesheet" href="{{ STATIC_URL }}assets/ansible-bootstrap.min.css" /> @@ -49,7 +50,7 @@ <!-- Password Dialog --> <div id="password-modal" style="display: none;"></div> - <div id="idle-modal" style="display:none">Your session will expire in <span id="remaining_seconds" class="IdleModal-remainingSeconds">60</span> seconds, would you like to continue?</div> + <div id="idle-modal" style="display:none">{% blocktrans %}Your session will expire in <span id="remaining_seconds" class="IdleModal-remainingSeconds">60</span> seconds, would you like to continue?{% endblocktrans %}</div> <stream-detail-modal></stream-detail-modal> @@ -155,27 +156,27 @@ <div id="prompt-for-days" style="display:none"> <form name="prompt_for_days_form" id="prompt_for_days_form" class="MgmtCards-promptText"> - Set how many days of data should be retained. <br> + {% trans 'Set how many days of data should be retained.' %}<br> <input type="integer" id="days_to_keep" name="days_to_keep" ng-model="days_to_keep" ng-required="true" class="form-control Form-textInput" min=0 max=9999 style="margin-top:10px;" integer> <div class="error" ng-show="prompt_for_days_form.days_to_keep.$dirty && (prompt_for_days_form.days_to_keep.$error.number || prompt_for_days_form.days_to_keep.$error.integer || prompt_for_days_form.days_to_keep.$error.required || prompt_for_days_form.days_to_keep.$error.min || - prompt_for_days_form.days_to_keep.$error.max)">Please enter an integer<span ng-show="prompt_for_days_form.days_to_keep.$dirty && prompt_for_days_form.days_to_keep.$error.min"> that is not negative</span><span ng-show="prompt_for_days_form.days_to_keep.$dirty && prompt_for_days_form.days_to_keep.$error.max"> that is lower than 9999</span>.</div> + prompt_for_days_form.days_to_keep.$error.max)">{% blocktrans %}Please enter an integer<span ng-show="prompt_for_days_form.days_to_keep.$dirty && prompt_for_days_form.days_to_keep.$error.min"> that is not negative</span><span ng-show="prompt_for_days_form.days_to_keep.$dirty && prompt_for_days_form.days_to_keep.$error.max"> that is lower than 9999</span>.{% endblocktrans %}</div> </form> </div> <div id="prompt-for-days-facts" style="display:none"> <form name="prompt_for_days_facts_form" id="prompt_for_days_facts_form" class="MgmtCards-promptText"> - <div style="padding-bottom:15px;">For facts collected older than the time period specified, + <div style="padding-bottom:15px;">{% blocktrans %}For facts collected older than the time period specified, save one fact scan (snapshot) per time window (frequency). For example, facts older than 30 days are purged, while one weekly fact scan is kept.<br> <br> - CAUTION: Setting both numerical variables to "0" will delete all facts.<br><br> + CAUTION: Setting both numerical variables to "0" will delete all facts.<br><br>{% endblocktrans %} </div> <div class="form-group"> <label for="description"> <span class="label-text"> - Select a time period after which to remove old facts + {% trans 'Select a time period after which to remove old facts' %} </span> </label> <div class="row"> @@ -189,12 +190,12 @@ <div class="error" ng-show="prompt_for_days_facts_form.keep_amount.$dirty && (prompt_for_days_facts_form.keep_amount.$error.number || prompt_for_days_facts_form.keep_amount.$error.integer || prompt_for_days_facts_form.keep_amount.$error.required || prompt_for_days_facts_form.keep_amount.$error.min || - prompt_for_days_facts_form.keep_amount.$error.max)">Please enter an integer<span ng-show="prompt_for_days_facts_form.keep_amount.$dirty && prompt_for_days_facts_form.keep_amount.$error.min"> that is not negative</span><span ng-show="prompt_for_days_facts_form.keep_amount.$dirty && prompt_for_days_facts_form.keep_amount.$error.max"> that is lower than 9999</span>.</div> + prompt_for_days_facts_form.keep_amount.$error.max)">{% blocktrans %}Please enter an integer<span ng-show="prompt_for_days_facts_form.keep_amount.$dirty && prompt_for_days_facts_form.keep_amount.$error.min"> that is not negative</span><span ng-show="prompt_for_days_facts_form.keep_amount.$dirty && prompt_for_days_facts_form.keep_amount.$error.max"> that is lower than 9999</span>.{% endblocktrans %}</div> </div> <div class="form-group "> <label for="description"> <span class="label-text"> - Select a frequency for snapshot retention + {% trans 'Select a frequency for snapshot retention' %} </span> </label> <div class="row"> @@ -209,14 +210,14 @@ <div class="error" ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && (prompt_for_days_facts_form.granularity_keep_amount.$error.number || prompt_for_days_facts_form.granularity_keep_amount.$error.integer || prompt_for_days_facts_form.granularity_keep_amount.$error.required || prompt_for_days_facts_form.granularity_keep_amount.$error.min || - prompt_for_days_facts_form.granularity_keep_amount.$error.max)">Please enter an integer<span ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && prompt_for_days_facts_form.granularity_keep_amount.$error.min"> that is not negative</span><span ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && prompt_for_days_facts_form.granularity_keep_amount.$error.max"> that is lower than 9999</span>.</div> + prompt_for_days_facts_form.granularity_keep_amount.$error.max)">{% blocktrans %}Please enter an integer<span ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && prompt_for_days_facts_form.granularity_keep_amount.$error.min"> that is not negative</span><span ng-show="prompt_for_days_facts_form.granularity_keep_amount.$dirty && prompt_for_days_facts_form.granularity_keep_amount.$error.max"> that is lower than 9999</span>.{% endblocktrans %}</div> </div> </form> </div> <div class="overlay"></div> - <div class="spinny"><i class="fa fa-cog fa-spin fa-2x"></i> <p>working...</p></div> + <div class="spinny"><i class="fa fa-cog fa-spin fa-2x"></i> <p>{% trans 'working...' %}</p></div> </div> <tower-footer></tower-footer> </body> |