From 79024e76b324796702483b1fd844ddbb36f23801 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Mon, 7 Nov 2016 08:47:47 -0500 Subject: [PATCH] switch from incubated gettextutils to oslo.i18n Replace the incubated copy of gettextutils with oslo.i18n, following the best-practices guidelines in the library documentation for handling imports, module names, and exceptions to the hacking rules. Change-Id: Ie366afd8bda2a72c964d9ddf7dd53718002fb4d0 Story: 2000776 Signed-off-by: Doug Hellmann --- requirements.txt | 1 + storyboard/_i18n.py | 43 ++ storyboard/api/app.py | 2 +- storyboard/api/auth/__init__.py | 2 +- storyboard/api/auth/authorization_checks.py | 2 +- storyboard/api/v1/boards.py | 2 +- storyboard/api/v1/branches.py | 2 +- storyboard/api/v1/due_dates.py | 2 +- storyboard/api/v1/milestones.py | 2 +- storyboard/api/v1/project_groups.py | 2 +- storyboard/api/v1/projects.py | 2 +- storyboard/api/v1/stories.py | 2 +- storyboard/api/v1/subscription_events.py | 2 +- storyboard/api/v1/subscriptions.py | 2 +- storyboard/api/v1/tags.py | 2 +- storyboard/api/v1/tasks.py | 2 +- storyboard/api/v1/teams.py | 2 +- storyboard/api/v1/timeline.py | 2 +- storyboard/api/v1/user_preferences.py | 2 +- storyboard/api/v1/user_tokens.py | 2 +- storyboard/api/v1/users.py | 2 +- storyboard/api/v1/worklists.py | 2 +- storyboard/common/decorators.py | 2 +- storyboard/common/exception.py | 2 +- storyboard/db/api/base.py | 2 +- storyboard/db/api/boards.py | 2 +- storyboard/db/api/branches.py | 2 +- storyboard/db/api/due_dates.py | 2 +- storyboard/db/api/project_groups.py | 2 +- storyboard/db/api/stories.py | 2 +- storyboard/db/api/teams.py | 2 +- storyboard/db/api/worklists.py | 2 +- storyboard/db/migration/cli.py | 2 +- storyboard/db/projects_loader.py | 2 +- storyboard/db/superusers_loader.py | 2 +- .../notifications/connection_service.py | 2 +- storyboard/notifications/publisher.py | 2 +- storyboard/notifications/subscriber.py | 2 +- storyboard/openstack/common/gettextutils.py | 474 ------------------ storyboard/openstack/common/processutils.py | 2 +- storyboard/plugin/event_worker.py | 2 +- storyboard/plugin/user_preferences.py | 2 +- tox.ini | 3 + 43 files changed, 86 insertions(+), 513 deletions(-) create mode 100644 storyboard/_i18n.py delete mode 100644 storyboard/openstack/common/gettextutils.py diff --git a/requirements.txt b/requirements.txt index 689d4094..84aa15d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ PyMySQL>=0.6.2,!=0.6.4,<0.7.7 apscheduler>=3.0.1,<3.1.0 python_dateutil>=2.4.0 oslo.concurrency>=3.8.0 # Apache-2.0 +oslo.i18n>=2.1.0 # Apache-2.0 diff --git a/storyboard/_i18n.py b/storyboard/_i18n.py new file mode 100644 index 00000000..bcf8f529 --- /dev/null +++ b/storyboard/_i18n.py @@ -0,0 +1,43 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import oslo_i18n + +DOMAIN = "storyboard" + +_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# The contextual translation function using the name "_C" +# requires oslo.i18n >=2.1.0 +_C = _translators.contextual_form + +# The plural translation function using the name "_P" +# requires oslo.i18n >=2.1.0 +_P = _translators.plural_form + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical + + +def get_available_languages(): + return oslo_i18n.get_available_languages(DOMAIN) diff --git a/storyboard/api/app.py b/storyboard/api/app.py index d482392d..cf90accd 100644 --- a/storyboard/api/app.py +++ b/storyboard/api/app.py @@ -21,6 +21,7 @@ from oslo_log import log import pecan from wsgiref import simple_server +from storyboard._i18n import _LI from storyboard.api import config as api_config from storyboard.api.middleware.cors_middleware import CORSMiddleware from storyboard.api.middleware import session_hook @@ -30,7 +31,6 @@ from storyboard.api.middleware import validation_hook from storyboard.api.v1.search import impls as search_engine_impls from storyboard.api.v1.search import search_engine from storyboard.notifications.notification_hook import NotificationHook -from storyboard.openstack.common.gettextutils import _LI # noqa from storyboard.plugin.scheduler import initialize_scheduler from storyboard.plugin.user_preferences import initialize_user_preferences diff --git a/storyboard/api/auth/__init__.py b/storyboard/api/auth/__init__.py index 18359af1..6143df15 100644 --- a/storyboard/api/auth/__init__.py +++ b/storyboard/api/auth/__init__.py @@ -14,7 +14,7 @@ from oslo_config import cfg -from storyboard.openstack.common.gettextutils import _ # noqa +from storyboard._i18n import _ CONF = cfg.CONF diff --git a/storyboard/api/auth/authorization_checks.py b/storyboard/api/auth/authorization_checks.py index fff3cfba..32c053a9 100644 --- a/storyboard/api/auth/authorization_checks.py +++ b/storyboard/api/auth/authorization_checks.py @@ -16,9 +16,9 @@ from pecan import abort from pecan import request +from storyboard._i18n import _ from storyboard.db.api import access_tokens as token_api from storyboard.db.api import users as user_api -from storyboard.openstack.common.gettextutils import _ # noqa def _get_token(): diff --git a/storyboard/api/v1/boards.py b/storyboard/api/v1/boards.py index b8e5efdd..9dd1fdc1 100644 --- a/storyboard/api/v1/boards.py +++ b/storyboard/api/v1/boards.py @@ -25,6 +25,7 @@ import six from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import wmodels from storyboard.common import decorators @@ -33,7 +34,6 @@ from storyboard.db.api import boards as boards_api from storyboard.db.api import timeline_events as events_api from storyboard.db.api import users as users_api from storyboard.db.api import worklists as worklists_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/branches.py b/storyboard/api/v1/branches.py index e57e4591..b43d5374 100644 --- a/storyboard/api/v1/branches.py +++ b/storyboard/api/v1/branches.py @@ -25,6 +25,7 @@ import six from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1.search import search_engine from storyboard.api.v1 import validations @@ -32,7 +33,6 @@ from storyboard.api.v1 import wmodels from storyboard.common import decorators from storyboard.common import exception as exc from storyboard.db.api import branches as branches_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/due_dates.py b/storyboard/api/v1/due_dates.py index 3270a333..0e97f694 100644 --- a/storyboard/api/v1/due_dates.py +++ b/storyboard/api/v1/due_dates.py @@ -24,6 +24,7 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import wmodels from storyboard.common import decorators @@ -33,7 +34,6 @@ from storyboard.db.api import due_dates as due_dates_api from storyboard.db.api import stories as stories_api from storyboard.db.api import tasks as tasks_api from storyboard.db.api import worklists as worklists_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/milestones.py b/storyboard/api/v1/milestones.py index d50db6b7..004fc764 100644 --- a/storyboard/api/v1/milestones.py +++ b/storyboard/api/v1/milestones.py @@ -25,6 +25,7 @@ import six from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1.search import search_engine from storyboard.api.v1 import validations @@ -32,7 +33,6 @@ from storyboard.api.v1 import wmodels from storyboard.common import decorators from storyboard.common import exception as exc from storyboard.db.api import milestones as milestones_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/project_groups.py b/storyboard/api/v1/project_groups.py index 76ccf6d2..503a22ce 100644 --- a/storyboard/api/v1/project_groups.py +++ b/storyboard/api/v1/project_groups.py @@ -22,6 +22,7 @@ from pecan.secure import secure import wsme.types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ import storyboard.api.auth.authorization_checks as checks from storyboard.api.v1 import validations from storyboard.api.v1 import wmodels @@ -29,7 +30,6 @@ from storyboard.common import decorators import storyboard.common.exception as exc from storyboard.db.api import project_groups from storyboard.db.api import projects -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/projects.py b/storyboard/api/v1/projects.py index b6a5ff91..85b4f04b 100644 --- a/storyboard/api/v1/projects.py +++ b/storyboard/api/v1/projects.py @@ -21,6 +21,7 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1.search import search_engine from storyboard.api.v1 import validations @@ -28,7 +29,6 @@ from storyboard.api.v1 import wmodels from storyboard.common import decorators from storyboard.common import exception as exc from storyboard.db.api import projects as projects_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/stories.py b/storyboard/api/v1/stories.py index f7eb17a3..a205f565 100644 --- a/storyboard/api/v1/stories.py +++ b/storyboard/api/v1/stories.py @@ -26,6 +26,7 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1.search import search_engine from storyboard.api.v1.tags import TagsController @@ -39,7 +40,6 @@ from storyboard.common import exception as exc from storyboard.db.api import stories as stories_api from storyboard.db.api import timeline_events as events_api from storyboard.db.api import users as users_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/subscription_events.py b/storyboard/api/v1/subscription_events.py index b2526f76..c1670967 100644 --- a/storyboard/api/v1/subscription_events.py +++ b/storyboard/api/v1/subscription_events.py @@ -23,12 +23,12 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import base from storyboard.common import decorators from storyboard.db.api import subscription_events as subscription_events_api from storyboard.db.api import users as user_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/subscriptions.py b/storyboard/api/v1/subscriptions.py index 507c3e91..8c6d9190 100644 --- a/storyboard/api/v1/subscriptions.py +++ b/storyboard/api/v1/subscriptions.py @@ -22,12 +22,12 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import base from storyboard.common import decorators from storyboard.db.api import subscriptions as subscription_api from storyboard.db.api import users as user_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/tags.py b/storyboard/api/v1/tags.py index 0623621c..ffaf7482 100644 --- a/storyboard/api/v1/tags.py +++ b/storyboard/api/v1/tags.py @@ -20,13 +20,13 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import wmodels from storyboard.common import exception as exc from storyboard.db.api import stories as stories_api from storyboard.db.api import story_tags as tags_api from storyboard.db.api import timeline_events as events_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/tasks.py b/storyboard/api/v1/tasks.py index 72838c1e..401e832c 100644 --- a/storyboard/api/v1/tasks.py +++ b/storyboard/api/v1/tasks.py @@ -23,6 +23,7 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1.search import search_engine from storyboard.api.v1 import validations @@ -35,7 +36,6 @@ from storyboard.db.api import stories as stories_api from storyboard.db.api import story_types as story_types_api from storyboard.db.api import tasks as tasks_api from storyboard.db.api import timeline_events as events_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/teams.py b/storyboard/api/v1/teams.py index 3e092ad0..2850e8b2 100644 --- a/storyboard/api/v1/teams.py +++ b/storyboard/api/v1/teams.py @@ -22,6 +22,7 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import validations from storyboard.api.v1 import wmodels @@ -30,7 +31,6 @@ from storyboard.common import exception as exc from storyboard.db.api import base as api_base from storyboard.db.api import teams as teams_api from storyboard.db.api import users as users_api -from storyboard.openstack.common.gettextutils import _ # noqas CONF = cfg.CONF diff --git a/storyboard/api/v1/timeline.py b/storyboard/api/v1/timeline.py index 2d5edd48..23fc4957 100644 --- a/storyboard/api/v1/timeline.py +++ b/storyboard/api/v1/timeline.py @@ -22,6 +22,7 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1.search import search_engine from storyboard.api.v1 import wmodels @@ -31,7 +32,6 @@ from storyboard.common import exception as exc from storyboard.db.api import comments as comments_api from storyboard.db.api import stories as stories_api from storyboard.db.api import timeline_events as events_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/user_preferences.py b/storyboard/api/v1/user_preferences.py index 449e0d23..4ac751b2 100644 --- a/storyboard/api/v1/user_preferences.py +++ b/storyboard/api/v1/user_preferences.py @@ -22,11 +22,11 @@ from pecan.secure import secure from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import validations from storyboard.common import decorators import storyboard.db.api.users as user_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/user_tokens.py b/storyboard/api/v1/user_tokens.py index 9e33e12d..4b0632ee 100644 --- a/storyboard/api/v1/user_tokens.py +++ b/storyboard/api/v1/user_tokens.py @@ -26,12 +26,12 @@ import six from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks import storyboard.api.v1.wmodels as wmodels from storyboard.common import decorators import storyboard.db.api.user_tokens as token_api import storyboard.db.api.users as user_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/users.py b/storyboard/api/v1/users.py index fa68beac..d79aace2 100644 --- a/storyboard/api/v1/users.py +++ b/storyboard/api/v1/users.py @@ -24,6 +24,7 @@ import six from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1.search import search_engine from storyboard.api.v1.user_preferences import UserPreferencesController @@ -33,7 +34,6 @@ from storyboard.api.v1 import wmodels from storyboard.common import decorators from storyboard.common import exception as exc from storyboard.db.api import users as users_api -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/api/v1/worklists.py b/storyboard/api/v1/worklists.py index f2423905..3eb9f5ae 100644 --- a/storyboard/api/v1/worklists.py +++ b/storyboard/api/v1/worklists.py @@ -25,6 +25,7 @@ import six from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from storyboard._i18n import _ from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import wmodels from storyboard.common import decorators @@ -35,7 +36,6 @@ from storyboard.db.api import timeline_events as events_api from storyboard.db.api import users as users_api from storyboard.db.api import worklists as worklists_api from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF diff --git a/storyboard/common/decorators.py b/storyboard/common/decorators.py index cea7bce0..0e9b57da 100644 --- a/storyboard/common/decorators.py +++ b/storyboard/common/decorators.py @@ -21,8 +21,8 @@ from six.moves.urllib.parse import urlencode from six.moves.urllib.parse import urlparse from six.moves.urllib.parse import urlunparse +from storyboard._i18n import _ from storyboard.common import exception as exc -from storyboard.openstack.common.gettextutils import _ # noqa def db_exceptions(func): diff --git a/storyboard/common/exception.py b/storyboard/common/exception.py index bc585f29..24c3258e 100644 --- a/storyboard/common/exception.py +++ b/storyboard/common/exception.py @@ -18,7 +18,7 @@ from six.moves import http_client from six.moves.urllib.parse import urlparse from wsme.exc import ClientSideError -from storyboard.openstack.common.gettextutils import _ # noqa +from storyboard._i18n import _ LOG = log.getLogger(__name__) diff --git a/storyboard/db/api/base.py b/storyboard/db/api/base.py index 6eb4daa5..7253dec2 100644 --- a/storyboard/db/api/base.py +++ b/storyboard/db/api/base.py @@ -28,9 +28,9 @@ from sqlalchemy.orm import aliased from sqlalchemy.sql.expression import false, true import sqlalchemy.types as sqltypes +from storyboard._i18n import _ from storyboard.common import exception as exc from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa CONF = cfg.CONF LOG = log.getLogger(__name__) diff --git a/storyboard/db/api/boards.py b/storyboard/db/api/boards.py index 698840ee..06ce07b5 100644 --- a/storyboard/db/api/boards.py +++ b/storyboard/db/api/boards.py @@ -16,10 +16,10 @@ from sqlalchemy.orm import aliased, subqueryload from wsme.exc import ClientSideError +from storyboard._i18n import _ from storyboard.db.api import base as api_base from storyboard.db.api import users as users_api from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa def _board_get(id, session=None): diff --git a/storyboard/db/api/branches.py b/storyboard/db/api/branches.py index 263eaf95..59720d35 100644 --- a/storyboard/db/api/branches.py +++ b/storyboard/db/api/branches.py @@ -13,10 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from storyboard._i18n import _ from storyboard.common import exception as exc from storyboard.db.api import base as api_base from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa def branch_get(branch_id): diff --git a/storyboard/db/api/due_dates.py b/storyboard/db/api/due_dates.py index 9cfdf738..05a06a32 100644 --- a/storyboard/db/api/due_dates.py +++ b/storyboard/db/api/due_dates.py @@ -16,10 +16,10 @@ from sqlalchemy import func from wsme.exc import ClientSideError +from storyboard._i18n import _ from storyboard.db.api import base as api_base from storyboard.db.api import users as users_api from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa def _due_date_get(id, session=None): diff --git a/storyboard/db/api/project_groups.py b/storyboard/db/api/project_groups.py index 3c23abe2..671758d0 100644 --- a/storyboard/db/api/project_groups.py +++ b/storyboard/db/api/project_groups.py @@ -16,11 +16,11 @@ from sqlalchemy.orm import subqueryload from wsme.exc import ClientSideError +from storyboard._i18n import _ from storyboard.common import exception as exc from storyboard.db.api import base as api_base from storyboard.db.api import projects from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa def _entity_get(id, session=None): diff --git a/storyboard/db/api/stories.py b/storyboard/db/api/stories.py index 1bc63c99..369174e7 100644 --- a/storyboard/db/api/stories.py +++ b/storyboard/db/api/stories.py @@ -18,6 +18,7 @@ import pytz from sqlalchemy.orm import subqueryload +from storyboard._i18n import _ from storyboard.common import exception as exc from storyboard.db.api import base as api_base from storyboard.db.api import story_tags @@ -25,7 +26,6 @@ from storyboard.db.api import story_types from storyboard.db.api import teams as teams_api from storyboard.db.api import users as users_api from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa def story_get_simple(story_id, session=None, current_user=None, diff --git a/storyboard/db/api/teams.py b/storyboard/db/api/teams.py index c40abc44..34953832 100644 --- a/storyboard/db/api/teams.py +++ b/storyboard/db/api/teams.py @@ -16,11 +16,11 @@ from sqlalchemy.orm import subqueryload from wsme.exc import ClientSideError +from storyboard._i18n import _ from storyboard.common import exception as exc from storyboard.db.api import base as api_base from storyboard.db.api import users from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa def _entity_get(id, session=None): diff --git a/storyboard/db/api/worklists.py b/storyboard/db/api/worklists.py index ee7fc863..1ec3ee99 100644 --- a/storyboard/db/api/worklists.py +++ b/storyboard/db/api/worklists.py @@ -16,6 +16,7 @@ from sqlalchemy.orm import aliased from wsme.exc import ClientSideError +from storyboard._i18n import _ from storyboard.common import exception as exc from storyboard.db.api import base as api_base from storyboard.db.api import boards @@ -23,7 +24,6 @@ from storyboard.db.api import stories as stories_api from storyboard.db.api import tasks as tasks_api from storyboard.db.api import users as users_api from storyboard.db import models -from storyboard.openstack.common.gettextutils import _ # noqa def _worklist_get(id, session=None): diff --git a/storyboard/db/migration/cli.py b/storyboard/db/migration/cli.py index ec5dc227..d01cc4e1 100644 --- a/storyboard/db/migration/cli.py +++ b/storyboard/db/migration/cli.py @@ -25,9 +25,9 @@ from oslo_config import cfg from oslo_db import options import six +from storyboard._i18n import _ from storyboard.db import projects_loader from storyboard.db import superusers_loader -from storyboard.openstack.common.gettextutils import _ # noqa gettext.install('storyboard') diff --git a/storyboard/db/projects_loader.py b/storyboard/db/projects_loader.py index 3f413d5f..4a542a8a 100644 --- a/storyboard/db/projects_loader.py +++ b/storyboard/db/projects_loader.py @@ -21,13 +21,13 @@ from oslo_log import log import six from sqlalchemy.exc import SADeprecationWarning +from storyboard._i18n import _LW from storyboard.common.custom_types import NameType from storyboard.common.master_branch_helper import MasterBranchHelper from storyboard.db.api import base as db_api from storyboard.db.models import Branch from storyboard.db.models import Project from storyboard.db.models import ProjectGroup -from storyboard.openstack.common.gettextutils import _LW # noqa warnings.simplefilter("ignore", SADeprecationWarning) diff --git a/storyboard/db/superusers_loader.py b/storyboard/db/superusers_loader.py index 1e185daa..49a4e7d7 100644 --- a/storyboard/db/superusers_loader.py +++ b/storyboard/db/superusers_loader.py @@ -18,9 +18,9 @@ import yaml from sqlalchemy.exc import SADeprecationWarning +from storyboard._i18n import _ from storyboard.db.api import base as db_api from storyboard.db.models import User -from storyboard.openstack.common.gettextutils import _ # noqa warnings.simplefilter("ignore", SADeprecationWarning) diff --git a/storyboard/notifications/connection_service.py b/storyboard/notifications/connection_service.py index 136b63bc..d84a55e3 100644 --- a/storyboard/notifications/connection_service.py +++ b/storyboard/notifications/connection_service.py @@ -20,7 +20,7 @@ import pika from oslo_config import cfg from oslo_log import log -from storyboard.openstack.common.gettextutils import _, _LI # noqa +from storyboard._i18n import _, _LI CONF = cfg.CONF diff --git a/storyboard/notifications/publisher.py b/storyboard/notifications/publisher.py index d09c411b..1b5c8b06 100644 --- a/storyboard/notifications/publisher.py +++ b/storyboard/notifications/publisher.py @@ -21,7 +21,7 @@ from pika.exceptions import ConnectionClosed from storyboard.notifications.conf import NOTIFICATION_OPTS from storyboard.notifications.connection_service import ConnectionService -from storyboard.openstack.common.gettextutils import _, _LW, _LE # noqa +from storyboard._i18n import _, _LW, _LE CONF = cfg.CONF diff --git a/storyboard/notifications/subscriber.py b/storyboard/notifications/subscriber.py index dc0ca645..236cf6d6 100644 --- a/storyboard/notifications/subscriber.py +++ b/storyboard/notifications/subscriber.py @@ -23,7 +23,7 @@ from stevedore import enabled from storyboard.notifications.conf import NOTIFICATION_OPTS from storyboard.notifications.connection_service import ConnectionService -from storyboard.openstack.common.gettextutils import _, _LW # noqa +from storyboard._i18n import _, _LW CONF = cfg.CONF diff --git a/storyboard/openstack/common/gettextutils.py b/storyboard/openstack/common/gettextutils.py deleted file mode 100644 index 365ad2d3..00000000 --- a/storyboard/openstack/common/gettextutils.py +++ /dev/null @@ -1,474 +0,0 @@ -# Copyright 2012 Red Hat, Inc. -# Copyright 2013 IBM Corp. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -gettext for openstack-common modules. - -Usual usage in an openstack.common module: - - from storyboard.openstack.common.gettextutils import _ -""" - -import copy -import functools -import gettext -import locale -from logging import handlers -import os -import re - -from babel import localedata -import six - -_localedir = os.environ.get('storyboard'.upper() + '_LOCALEDIR') -_t = gettext.translation('storyboard', localedir=_localedir, fallback=True) - -# We use separate translation catalogs for each log level, so set up a -# mapping between the log level name and the translator. The domain -# for the log level is project_name + "-log-" + log_level so messages -# for each level end up in their own catalog. -_t_log_levels = dict( - (level, gettext.translation('storyboard' + '-log-' + level, - localedir=_localedir, - fallback=True)) - for level in ['info', 'warning', 'error', 'critical'] -) - -_AVAILABLE_LANGUAGES = {} -USE_LAZY = False - - -def enable_lazy(): - """Convenience function for configuring _() to use lazy gettext - - Call this at the start of execution to enable the gettextutils._ - function to use lazy gettext functionality. This is useful if - your project is importing _ directly instead of using the - gettextutils.install() way of importing the _ function. - """ - global USE_LAZY - USE_LAZY = True - - -def _(msg): - if USE_LAZY: - return Message(msg, domain='storyboard') - else: - if six.PY3: - return _t.gettext(msg) - return _t.ugettext(msg) - - -def _log_translation(msg, level): - """Build a single translation of a log message - """ - if USE_LAZY: - return Message(msg, domain='storyboard' + '-log-' + level) - else: - translator = _t_log_levels[level] - if six.PY3: - return translator.gettext(msg) - return translator.ugettext(msg) - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = functools.partial(_log_translation, level='info') -_LW = functools.partial(_log_translation, level='warning') -_LE = functools.partial(_log_translation, level='error') -_LC = functools.partial(_log_translation, level='critical') - - -def install(domain, lazy=False): - """Install a _() function using the given translation domain. - - Given a translation domain, install a _() function using gettext's - install() function. - - The main difference from gettext.install() is that we allow - overriding the default localedir (e.g. /usr/share/locale) using - a translation-domain-specific environment variable (e.g. - NOVA_LOCALEDIR). - - :param domain: the translation domain - :param lazy: indicates whether or not to install the lazy _() function. - The lazy _() introduces a way to do deferred translation - of messages by installing a _ that builds Message objects, - instead of strings, which can then be lazily translated into - any available locale. - """ - if lazy: - # NOTE(mrodden): Lazy gettext functionality. - # - # The following introduces a deferred way to do translations on - # messages in OpenStack. We override the standard _() function - # and % (format string) operation to build Message objects that can - # later be translated when we have more information. - def _lazy_gettext(msg): - """Create and return a Message object. - - Lazy gettext function for a given domain, it is a factory method - for a project/module to get a lazy gettext function for its own - translation domain (i.e. nova, glance, cinder, etc.) - - Message encapsulates a string so that we can translate - it later when needed. - """ - return Message(msg, domain=domain) - - from six import moves - moves.builtins.__dict__['_'] = _lazy_gettext - else: - localedir = '%s_LOCALEDIR' % domain.upper() - if six.PY3: - gettext.install(domain, - localedir=os.environ.get(localedir)) - else: - gettext.install(domain, - localedir=os.environ.get(localedir), - unicode=True) - - -class Message(six.text_type): - """A Message object is a unicode object that can be translated. - - Translation of Message is done explicitly using the translate() method. - For all non-translation intents and purposes, a Message is simply unicode, - and can be treated as such. - """ - - def __new__(cls, msgid, msgtext=None, params=None, - domain='storyboard', *args): - """Create a new Message object. - - In order for translation to work gettext requires a message ID, this - msgid will be used as the base unicode text. It is also possible - for the msgid and the base unicode text to be different by passing - the msgtext parameter. - """ - # If the base msgtext is not given, we use the default translation - # of the msgid (which is in English) just in case the system locale is - # not English, so that the base text will be in that locale by default. - if not msgtext: - msgtext = Message._translate_msgid(msgid, domain) - # We want to initialize the parent unicode with the actual object that - # would have been plain unicode if 'Message' was not enabled. - msg = super(Message, cls).__new__(cls, msgtext) - msg.msgid = msgid - msg.domain = domain - msg.params = params - return msg - - def translate(self, desired_locale=None): - """Translate this message to the desired locale. - - :param desired_locale: The desired locale to translate the message to, - if no locale is provided the message will be - translated to the system's default locale. - - :returns: the translated message in unicode - """ - - translated_message = Message._translate_msgid(self.msgid, - self.domain, - desired_locale) - if self.params is None: - # No need for more translation - return translated_message - - # This Message object may have been formatted with one or more - # Message objects as substitution arguments, given either as a single - # argument, part of a tuple, or as one or more values in a dictionary. - # When translating this Message we need to translate those Messages too - translated_params = _translate_args(self.params, desired_locale) - - translated_message = translated_message % translated_params - - return translated_message - - @staticmethod - def _translate_msgid(msgid, domain, desired_locale=None): - if not desired_locale: - system_locale = locale.getdefaultlocale() - # If the system locale is not available to the runtime use English - if not system_locale[0]: - desired_locale = 'en_US' - else: - desired_locale = system_locale[0] - - locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') - lang = gettext.translation(domain, - localedir=locale_dir, - languages=[desired_locale], - fallback=True) - if six.PY3: - translator = lang.gettext - else: - translator = lang.ugettext - - translated_message = translator(msgid) - return translated_message - - def __mod__(self, other): - # When we mod a Message we want the actual operation to be performed - # by the parent class (i.e. unicode()), the only thing we do here is - # save the original msgid and the parameters in case of a translation - params = self._sanitize_mod_params(other) - unicode_mod = super(Message, self).__mod__(params) - modded = Message(self.msgid, - msgtext=unicode_mod, - params=params, - domain=self.domain) - return modded - - def _sanitize_mod_params(self, other): - """Sanitize the object being modded with this Message. - - - Add support for modding 'None' so translation supports it - - Trim the modded object, which can be a large dictionary, to only - those keys that would actually be used in a translation - - Snapshot the object being modded, in case the message is - translated, it will be used as it was when the Message was created - """ - if other is None: - params = (other,) - elif isinstance(other, dict): - params = self._trim_dictionary_parameters(other) - else: - params = self._copy_param(other) - return params - - def _trim_dictionary_parameters(self, dict_param): - """Return a dict that only has matching entries in the msgid.""" - # NOTE(luisg): Here we trim down the dictionary passed as parameters - # to avoid carrying a lot of unnecessary weight around in the message - # object, for example if someone passes in Message() % locals() but - # only some params are used, and additionally we prevent errors for - # non-deepcopyable objects by unicoding() them. - - # Look for %(param) keys in msgid; - # Skip %% and deal with the case where % is first character on the line - keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', self.msgid) - - # If we don't find any %(param) keys but have a %s - if not keys and re.findall('(?:[^%]|^)%[a-z]', self.msgid): - # Apparently the full dictionary is the parameter - params = self._copy_param(dict_param) - else: - params = {} - # Save our existing parameters as defaults to protect - # ourselves from losing values if we are called through an - # (erroneous) chain that builds a valid Message with - # arguments, and then does something like "msg % kwds" - # where kwds is an empty dictionary. - src = {} - if isinstance(self.params, dict): - src.update(self.params) - src.update(dict_param) - for key in keys: - params[key] = self._copy_param(src[key]) - - return params - - def _copy_param(self, param): - try: - return copy.deepcopy(param) - except TypeError: - # Fallback to casting to unicode this will handle the - # python code-like objects that can't be deep-copied - return six.text_type(param) - - def __add__(self, other): - msg = _('Message objects do not support addition.') - raise TypeError(msg) - - def __radd__(self, other): - return self.__add__(other) - - def __str__(self): - # NOTE(luisg): Logging in python 2.6 tries to str() log records, - # and it expects specifically a UnicodeError in order to proceed. - msg = _('Message objects do not support str() because they may ' - 'contain non-ascii characters. ' - 'Please use unicode() or translate() instead.') - raise UnicodeError(msg) - - -def get_available_languages(domain): - """Lists the available languages for the given translation domain. - - :param domain: the domain to get languages for - """ - if domain in _AVAILABLE_LANGUAGES: - return copy.copy(_AVAILABLE_LANGUAGES[domain]) - - localedir = '%s_LOCALEDIR' % domain.upper() - find = lambda x: gettext.find(domain, - localedir=os.environ.get(localedir), - languages=[x]) - - # NOTE(mrodden): en_US should always be available (and first in case - # order matters) since our in-line message strings are en_US - language_list = ['en_US'] - # NOTE(luisg): Babel <1.0 used a function called list(), which was - # renamed to locale_identifiers() in >=1.0, the requirements master list - # requires >=0.9.6, uncapped, so defensively work with both. We can remove - # this check when the master list updates to >=1.0, and update all projects - list_identifiers = (getattr(localedata, 'list', None) or - getattr(localedata, 'locale_identifiers')) - locale_identifiers = list_identifiers() - - for i in locale_identifiers: - if find(i) is not None: - language_list.append(i) - - # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported - # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they - # are perfectly legitimate locales: - # https://github.com/mitsuhiko/babel/issues/37 - # In Babel 1.3 they fixed the bug and they support these locales, but - # they are still not explicitly "listed" by locale_identifiers(). - # That is why we add the locales here explicitly if necessary so that - # they are listed as supported. - aliases = {'zh': 'zh_CN', - 'zh_Hant_HK': 'zh_HK', - 'zh_Hant': 'zh_TW', - 'fil': 'tl_PH'} - for (locale, alias) in six.iteritems(aliases): - if locale in language_list and alias not in language_list: - language_list.append(alias) - - _AVAILABLE_LANGUAGES[domain] = language_list - return copy.copy(language_list) - - -def translate(obj, desired_locale=None): - """Gets the translated unicode representation of the given object. - - If the object is not translatable it is returned as-is. - If the locale is None the object is translated to the system locale. - - :param obj: the object to translate - :param desired_locale: the locale to translate the message to, if None the - default system locale will be used - :returns: the translated object in unicode, or the original object if - it could not be translated - """ - message = obj - if not isinstance(message, Message): - # If the object to translate is not already translatable, - # let's first get its unicode representation - message = six.text_type(obj) - if isinstance(message, Message): - # Even after unicoding() we still need to check if we are - # running with translatable unicode before translating - return message.translate(desired_locale) - return obj - - -def _translate_args(args, desired_locale=None): - """Translates all the translatable elements of the given arguments object. - - This method is used for translating the translatable values in method - arguments which include values of tuples or dictionaries. - If the object is not a tuple or a dictionary the object itself is - translated if it is translatable. - - If the locale is None the object is translated to the system locale. - - :param args: the args to translate - :param desired_locale: the locale to translate the args to, if None the - default system locale will be used - :returns: a new args object with the translated contents of the original - """ - if isinstance(args, tuple): - return tuple(translate(v, desired_locale) for v in args) - if isinstance(args, dict): - translated_dict = {} - for (k, v) in six.iteritems(args): - translated_v = translate(v, desired_locale) - translated_dict[k] = translated_v - return translated_dict - return translate(args, desired_locale) - - -class TranslationHandler(handlers.MemoryHandler): - """Handler that translates records before logging them. - - The TranslationHandler takes a locale and a target logging.Handler object - to forward LogRecord objects to after translating them. This handler - depends on Message objects being logged, instead of regular strings. - - The handler can be configured declaratively in the logging.conf as follows: - - [handlers] - keys = translatedlog, translator - - [handler_translatedlog] - class = handlers.WatchedFileHandler - args = ('/var/log/api-localized.log',) - formatter = context - - [handler_translator] - class = openstack.common.log.TranslationHandler - target = translatedlog - args = ('zh_CN',) - - If the specified locale is not available in the system, the handler will - log in the default locale. - """ - - def __init__(self, locale=None, target=None): - """Initialize a TranslationHandler - - :param locale: locale to use for translating messages - :param target: logging.Handler object to forward - LogRecord objects to after translation - """ - # NOTE(luisg): In order to allow this handler to be a wrapper for - # other handlers, such as a FileHandler, and still be able to - # configure it using logging.conf, this handler has to extend - # MemoryHandler because only the MemoryHandlers' logging.conf - # parsing is implemented such that it accepts a target handler. - handlers.MemoryHandler.__init__(self, capacity=0, target=target) - self.locale = locale - - def setFormatter(self, fmt): - self.target.setFormatter(fmt) - - def emit(self, record): - # We save the message from the original record to restore it - # after translation, so other handlers are not affected by this - original_msg = record.msg - original_args = record.args - - try: - self._translate_and_log_record(record) - finally: - record.msg = original_msg - record.args = original_args - - def _translate_and_log_record(self, record): - record.msg = translate(record.msg, self.locale) - - # In addition to translating the message, we also need to translate - # arguments that were passed to the log method that were not part - # of the main message e.g., log.info(_('Some message %s'), this_one)) - record.args = _translate_args(record.args, self.locale) - - self.target.emit(record) diff --git a/storyboard/openstack/common/processutils.py b/storyboard/openstack/common/processutils.py index bbeaa57c..085d479e 100644 --- a/storyboard/openstack/common/processutils.py +++ b/storyboard/openstack/common/processutils.py @@ -29,7 +29,7 @@ from eventlet import greenthread from oslo_log import log as logging import six -from storyboard.openstack.common.gettextutils import _ +from storyboard._i18n import _ LOG = logging.getLogger(__name__) diff --git a/storyboard/plugin/event_worker.py b/storyboard/plugin/event_worker.py index 2b472634..93572d2e 100644 --- a/storyboard/plugin/event_worker.py +++ b/storyboard/plugin/event_worker.py @@ -23,7 +23,7 @@ from oslo_log import log import storyboard.db.api.base as db_api from storyboard.notifications.notification_hook import class_mappings from storyboard.notifications.subscriber import subscribe -from storyboard.openstack.common.gettextutils import _LI, _LW # noqa +from storyboard._i18n import _LI, _LW from storyboard.plugin.base import PluginBase CONF = cfg.CONF diff --git a/storyboard/plugin/user_preferences.py b/storyboard/plugin/user_preferences.py index 9c14e22b..72078695 100644 --- a/storyboard/plugin/user_preferences.py +++ b/storyboard/plugin/user_preferences.py @@ -17,7 +17,7 @@ import abc from oslo_log import log import six -from storyboard.openstack.common.gettextutils import _LE # noqa +from storyboard._i18n import _LE from storyboard.plugin.base import PluginBase from storyboard.plugin.base import StoryboardPluginLoader diff --git a/tox.ini b/tox.ini index eed9f67b..f710da16 100644 --- a/tox.ini +++ b/tox.ini @@ -50,3 +50,6 @@ exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build # separately, outside of the requirements files. deps = bindep commands = bindep test + +[hacking] +import_exceptions = storyboard._i18n \ No newline at end of file