mirror of
https://github.com/valitydev/salt.git
synced 2024-11-08 01:18:58 +00:00
Merge pull request #7825 from s0undt3ch/features/i18n-documentation
i18n documentation updates
This commit is contained in:
commit
e4c85c61be
81
doc/.scripts/compile-translation-catalogs
Executable file
81
doc/.scripts/compile-translation-catalogs
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
:codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
|
||||
:copyright: © 2013 by the SaltStack Team, see AUTHORS for more details.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
|
||||
|
||||
compile-translation-catalogs
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Compile the existing translation catalogs.
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
import os
|
||||
import sys
|
||||
import fnmatch
|
||||
|
||||
# Import 3rd-party libs
|
||||
HAS_BABEL = False
|
||||
try:
|
||||
from babel.messages import mofile, pofile
|
||||
HAS_BABEL = True
|
||||
except ImportError:
|
||||
try:
|
||||
import polib
|
||||
except ImportError:
|
||||
print(
|
||||
'You need to install either babel or pofile in order to compile '
|
||||
'the message catalogs. One of:\n'
|
||||
' pip install babel\n'
|
||||
' pip install polib'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
DOC_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
LOCALES_DIR = os.path.join(DOC_DIR, 'locale')
|
||||
|
||||
|
||||
def main():
|
||||
'''
|
||||
Run the compile code
|
||||
'''
|
||||
|
||||
print('Gathering the translation catalogs to compile...'),
|
||||
sys.stdout.flush()
|
||||
entries = {}
|
||||
for locale in os.listdir(os.path.join(LOCALES_DIR)):
|
||||
if locale == 'pot':
|
||||
continue
|
||||
|
||||
locale_path = os.path.join(LOCALES_DIR, locale)
|
||||
entries[locale] = []
|
||||
|
||||
for dirpath, _, filenames in os.walk(locale_path):
|
||||
for filename in fnmatch.filter(filenames, '*.po'):
|
||||
entries[locale].append(os.path.join(dirpath, filename))
|
||||
print('DONE')
|
||||
|
||||
for locale, po_files in sorted(entries.items()):
|
||||
lc_messages_path = os.path.join(LOCALES_DIR, locale, 'LC_MESSAGES')
|
||||
print('\nCompiling the {0!r} locale:'.format(locale))
|
||||
for po_file in sorted(po_files):
|
||||
relpath = os.path.relpath(po_file, lc_messages_path)
|
||||
print ' {0}.po -> {0}.mo'.format(relpath.split('.po', 1)[0])
|
||||
if HAS_BABEL:
|
||||
catalog = pofile.read_po(open(po_file))
|
||||
mofile.write_mo(
|
||||
open(po_file.replace('.po', '.mo'), 'wb'), catalog
|
||||
)
|
||||
continue
|
||||
|
||||
catalog = polib.pofile(po_file)
|
||||
catalog.save_as_mofile(fpath=po_file.replace('.po', '.mo'))
|
||||
|
||||
print('Done')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
55
doc/.scripts/download-translation-catalog
Executable file
55
doc/.scripts/download-translation-catalog
Executable file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
:codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
|
||||
:copyright: © 2013 by the SaltStack Team, see AUTHORS for more details.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
|
||||
|
||||
download-translation-catalog
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Download a translation catalog from Transifex.
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Import 3rd-party libs
|
||||
try:
|
||||
import txclib.utils
|
||||
except ImportError:
|
||||
print(
|
||||
'The \'transifex-client\' library needs to be installed. '
|
||||
'Please execute one of \'pip install transifex-client\' or '
|
||||
'\'easy_install transifex-client\''
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
DOC_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
LOCALES_DIR = os.path.join(DOC_DIR, 'locale')
|
||||
|
||||
|
||||
def main():
|
||||
'''
|
||||
Run the compile code
|
||||
'''
|
||||
|
||||
os.chdir(DOC_DIR)
|
||||
tx_root = txclib.utils.find_dot_tx()
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print('You need to pass a locale to this script. For example: '
|
||||
'pt_PT, zh_CN, ru, etc...')
|
||||
sys.exit(1)
|
||||
|
||||
for locale in sys.argv[1:]:
|
||||
print('Download {0!r} translations catalog...'.format(locale))
|
||||
txclib.utils.exec_command('pull', ['-l', locale], tx_root)
|
||||
|
||||
print('Done')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
96
doc/.scripts/setup-transifex-config
Executable file
96
doc/.scripts/setup-transifex-config
Executable file
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
:codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
|
||||
:copyright: © 2013 by the SaltStack Team, see AUTHORS for more details.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
|
||||
|
||||
setup-transifex-config
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Setup the Transifex client configuration file
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
import os
|
||||
import sys
|
||||
import getpass
|
||||
import ConfigParser
|
||||
|
||||
HOST = 'https://www.transifex.com'
|
||||
RCFILE = os.path.abspath(os.path.expanduser('~/.transifexrc'))
|
||||
|
||||
|
||||
def main():
|
||||
'''
|
||||
Run the setup code
|
||||
'''
|
||||
print(
|
||||
'This script will setup a Transifex client configuration file, or, '
|
||||
'if it already exists, make some minimal checks to see if it\'s '
|
||||
'properly configured\n'
|
||||
)
|
||||
if not os.path.exists(RCFILE):
|
||||
while True:
|
||||
try:
|
||||
username = raw_input(
|
||||
'What is your username on Transifex.com? '
|
||||
)
|
||||
if username:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
sys.exit(1)
|
||||
while True:
|
||||
try:
|
||||
password = getpass.getpass(
|
||||
'What is your password on Transifex.com? '
|
||||
)
|
||||
if password:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
sys.exit(1)
|
||||
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.add_section(HOST)
|
||||
config.set(HOST, 'token', '')
|
||||
config.set(HOST, 'hostname', HOST)
|
||||
config.set(HOST, 'username', username)
|
||||
config.set(HOST, 'password', password)
|
||||
|
||||
config.write(open(RCFILE, 'w'))
|
||||
print('username and password stored in {0!r}'.format(RCFILE))
|
||||
|
||||
os.chmod(RCFILE, 0600)
|
||||
print('Secured the permissions on {0!r} to 0600'.format(RCFILE))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# The file exists, let's see if it's properly configured
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.read([RCFILE])
|
||||
|
||||
if not config.has_section(HOST):
|
||||
print('\'~/.transifexrc\' is not properly configured, it\'s missing '
|
||||
'the {0} section'.format(HOST))
|
||||
|
||||
for setting in ('username', 'password', 'hostname', 'token'):
|
||||
if not config.has_option(HOST, setting):
|
||||
print('\'~/.transifexrc\' is not properly configured, it\'s '
|
||||
'missing the {0} option'.format(setting))
|
||||
sys.exit(1)
|
||||
|
||||
if setting == 'token':
|
||||
# Token should be left empty
|
||||
continue
|
||||
|
||||
if not config.get(HOST, setting):
|
||||
print('\'~/.transifexrc\' is not properly configured, it\'s '
|
||||
'missing a value for the {0} option'.format(setting))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
208
doc/.scripts/update-transifex-source-translations
Executable file
208
doc/.scripts/update-transifex-source-translations
Executable file
@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
:codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
|
||||
:copyright: © 2013 by the SaltStack Team, see AUTHORS for more details.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
|
||||
|
||||
update-transifex-source-translations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Update the transifex sources configuration file and push the source
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import subprocess
|
||||
import ConfigParser
|
||||
|
||||
try:
|
||||
import txclib.utils
|
||||
except ImportError:
|
||||
print(
|
||||
'The \'transifex-client\' library needs to be installed. '
|
||||
'Please execute one of \'pip install transifex-client\' or '
|
||||
'\'easy_install transifex-client\''
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
DOC_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
|
||||
def main():
|
||||
'''
|
||||
Run the update code
|
||||
'''
|
||||
os.chdir(DOC_DIR)
|
||||
|
||||
print('Extracting translatable strings....')
|
||||
try:
|
||||
subprocess.check_call(['make', 'gettext'])
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print('An error occurred while extracting the translation '
|
||||
'strings: {0}'.format(exc))
|
||||
sys.exit(1)
|
||||
|
||||
locale_dir = os.path.join(DOC_DIR, 'locale')
|
||||
pot_dir = os.path.join(DOC_DIR, '_build', 'locale')
|
||||
tx_root = txclib.utils.find_dot_tx()
|
||||
tx_config = os.path.join(tx_root, '.tx', 'config')
|
||||
|
||||
if not tx_root:
|
||||
print('Unable to find the \'.tx/\' directory. Unable to continue')
|
||||
sys.exit(1)
|
||||
|
||||
# We do not want the txclib INFO or WARNING logging
|
||||
logging.getLogger('txclib').setLevel(logging.ERROR)
|
||||
|
||||
print('Gathering the translation template files...'),
|
||||
sys.stdout.flush()
|
||||
entries = []
|
||||
for dirpath, dirnames, filenames in os.walk(pot_dir):
|
||||
for filename in filenames:
|
||||
pot_file = os.path.join(dirpath, filename)
|
||||
base, ext = os.path.splitext(pot_file)
|
||||
if ext != ".pot":
|
||||
continue
|
||||
resource_path = os.path.relpath(base, pot_dir)
|
||||
try:
|
||||
import babel.messages.pofile
|
||||
if not len(babel.messages.pofile.read_po(open(pot_file))):
|
||||
# Empty pot file, continue
|
||||
continue
|
||||
except ImportError:
|
||||
# No babel package, let's keep on going
|
||||
pass
|
||||
|
||||
resource_name = resource_path.replace(
|
||||
'\\', '/').replace('/', '--').replace('.', '_')
|
||||
entries.append((resource_path, resource_name))
|
||||
print('Done')
|
||||
|
||||
# Let's load the resources already present in the configuration file
|
||||
cfg = ConfigParser.SafeConfigParser()
|
||||
cfg.read([tx_config])
|
||||
handled_resources = set(
|
||||
section for section in
|
||||
cfg.sections() if section.startswith('salt.')
|
||||
)
|
||||
|
||||
print('Updating the entries in \'.tx/config\'...')
|
||||
sys.stdout.flush()
|
||||
total_entries = len(entries)
|
||||
for idx, (resource_path, resource_name) in enumerate(sorted(entries)):
|
||||
print(
|
||||
'[{0:>{pad}}/{1}] Updating resource for '
|
||||
'{resource_path}.pot ({resource_name})'.format(
|
||||
idx + 1,
|
||||
total_entries,
|
||||
pad=len(str(total_entries)),
|
||||
locale_dir=locale_dir,
|
||||
resource_name=resource_name,
|
||||
resource_path=resource_path
|
||||
)
|
||||
),
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
txclib.utils.exec_command(
|
||||
'set',
|
||||
'--auto-local -r salt.{resource_name} '
|
||||
'{locale_dir}/<lang>/LC_MESSAGES/{resource_path}.po '
|
||||
'--source-lang en '
|
||||
'--source-file {pot_dir}/{resource_path}.pot '
|
||||
'--source-name {resource_path}.rst '
|
||||
'--execute'.format(
|
||||
resource_name=resource_name,
|
||||
resource_path=resource_path,
|
||||
locale_dir=locale_dir,
|
||||
pot_dir=pot_dir.rstrip('/')
|
||||
).split(),
|
||||
tx_root
|
||||
)
|
||||
print
|
||||
if 'salt.{0}'.format(resource_name) in handled_resources:
|
||||
handled_resources.remove('salt.{0}'.format(resource_name))
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
sys.exit(1)
|
||||
|
||||
if handled_resources:
|
||||
non_handled_resources = len(handled_resources)
|
||||
print('Removing old resources from configuration and upstream'
|
||||
'(if possible)')
|
||||
for idx, resource_name in enumerate(sorted(handled_resources)):
|
||||
print(
|
||||
'[{0:>{pad}}/{1}] Removing resource {resource_name!r}'.format(
|
||||
idx + 1,
|
||||
non_handled_resources,
|
||||
pad=len(str(non_handled_resources)),
|
||||
resource_name=resource_name,
|
||||
)
|
||||
),
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
txclib.utils.exec_command(
|
||||
'delete',
|
||||
['-r', resource_name],
|
||||
tx_root
|
||||
)
|
||||
except Exception, err:
|
||||
print err
|
||||
finally:
|
||||
if cfg.has_section(resource_name):
|
||||
cfg.remove_section(resource_name)
|
||||
print
|
||||
cfg.write(open(tx_config, 'w'))
|
||||
print
|
||||
|
||||
# Set the translations file type we're using
|
||||
txclib.utils.exec_command('set', ['-t', 'PO'], tx_root)
|
||||
|
||||
print
|
||||
print('Pushing translation template files...')
|
||||
for idx, (resource_path, resource_name) in enumerate(sorted(entries)):
|
||||
print(
|
||||
'[{0:>{pad}}/{1}] Pushing resource for '
|
||||
'{resource_path}.pot ({resource_name})'.format(
|
||||
idx + 1,
|
||||
total_entries,
|
||||
pad=len(str(total_entries)),
|
||||
locale_dir=locale_dir,
|
||||
resource_name=resource_name,
|
||||
resource_path=resource_path
|
||||
)
|
||||
),
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
txclib.utils.exec_command(
|
||||
'push',
|
||||
'--resource salt.{resource_name} '
|
||||
'--source '
|
||||
'--skip '
|
||||
'--no-interactive'.format(
|
||||
resource_name=resource_name,
|
||||
resource_path=resource_path,
|
||||
locale_dir=locale_dir
|
||||
).split(),
|
||||
tx_root
|
||||
)
|
||||
print
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
sys.exit(1)
|
||||
|
||||
if handled_resources:
|
||||
print('=' * 80)
|
||||
print('Don\'t forget to delete the following remote resources:')
|
||||
for resource_name in sorted(handled_resources):
|
||||
print(' {0}'.format(resource_name))
|
||||
print('=' * 80)
|
||||
|
||||
print 'DONE'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
5648
doc/.tx/config
5648
doc/.tx/config
File diff suppressed because it is too large
Load Diff
41
doc/Makefile
41
doc/Makefile
@ -35,7 +35,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(TRANSLATIONOPTS
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext translations
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext translations download-translations
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@ -66,24 +66,6 @@ clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
find locale/ -name *.mo -exec rm {} \;
|
||||
|
||||
translations:
|
||||
|
||||
@if [ "$(SPHINXLANG)" = "en" ] || [ "x$(SPHINXLANG)" = "x" ]; then \
|
||||
echo "No need to update translations. Skipping..."; \
|
||||
elif [ ! -d locale/$(SPHINXLANG) ]; then \
|
||||
echo "The locale directory for $(SPHINXLANG) does not exist"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Extracting translatable strings"; \
|
||||
make gettext; \
|
||||
\
|
||||
echo "Updating existing translation catalog for '$(SPHINXLANG)'"; \
|
||||
sphinx-intl update -p _build/locale -l $(SPHINXLANG); \
|
||||
\
|
||||
echo "Compiling exising message catalog for '$(SPHINXLANG)'"; \
|
||||
sphinx-intl build -l $(SPHINXLANG); \
|
||||
fi
|
||||
|
||||
html: translations
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@ -181,9 +163,9 @@ info: translations
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) locale/pot
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in locale/pot."
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale"
|
||||
|
||||
changes: translations
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@ -210,3 +192,20 @@ pseudoxml: translations
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
|
||||
|
||||
translations:
|
||||
|
||||
@if [ "$(SPHINXLANG)" = "en" ] || [ "x$(SPHINXLANG)" = "x" ]; then \
|
||||
echo "No need to update translations. Skipping..."; \
|
||||
elif [ ! -d locale/$(SPHINXLANG) ]; then \
|
||||
echo "The locale directory for $(SPHINXLANG) does not exist"; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "Compiling exising message catalog for '$(SPHINXLANG)'"; \
|
||||
.scripts/compile-translation-catalogs; \
|
||||
fi
|
||||
|
||||
download-translations:
|
||||
@echo "Downloading $(SPHINXLANG) translations"
|
||||
.scripts/download-translation-catalog $(SPHINXLANG)
|
||||
|
@ -32,6 +32,7 @@ Full Table of Contents
|
||||
topics/conventions/*
|
||||
topics/git/*
|
||||
topics/development/index
|
||||
topics/translating
|
||||
|
||||
ref/configuration/logging/*
|
||||
ref/configuration/logging/handlers/*
|
||||
|
@ -2,18 +2,59 @@ Translating Documentation
|
||||
=========================
|
||||
|
||||
If you wish to help translate the Salt documentation to your language, please
|
||||
head over to the `Transifex`_ website and `signup`_ for an account.
|
||||
head over to the `Transifex`_ website and `signup`__ for an account.
|
||||
|
||||
Once registered, head over to the `Salt Translation Project`_, and either click
|
||||
on **Request Language** if you can't find yours, or, select the language for
|
||||
which you wish to contribute and click **Join Team**.
|
||||
Once registered, head over to the `Salt Translation Project`__, and either
|
||||
click on **Request Language** if you can't find yours, or, select the language
|
||||
for which you wish to contribute and click **Join Team**.
|
||||
|
||||
`Transifex`_ provides some useful reading resources on their `support domain`_,
|
||||
namely, some useful articles `directed to translators`_.
|
||||
`Transifex`_ provides some useful reading resources on their `support
|
||||
domain`__, namely, some useful articles `directed to translators`__.
|
||||
|
||||
|
||||
.. __: https://www.transifex.com/signup/
|
||||
.. __: https://www.transifex.com/projects/p/salt/
|
||||
.. __: http://support.transifex.com/
|
||||
.. __: http://support.transifex.com/customer/portal/topics/414107-translators/articles
|
||||
|
||||
|
||||
Building A Localized Version of the Documentation
|
||||
-------------------------------------------------
|
||||
|
||||
While you're working on your translation on `Transifex`_, you might want to
|
||||
have a look at how it's rendering.
|
||||
|
||||
There's a little script which simplifies the download process of the
|
||||
translations(which isn't that complicated in the first place).
|
||||
So, let's assume you're translating ``pt_PT``, Portuguese(Portugal). To
|
||||
download the translations, execute from the ``doc/`` directory of your Salt
|
||||
checkout:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
make download-translations SPHINXLANG=pt_PT
|
||||
|
||||
|
||||
To download ``pt_PT``, Portuguese(Portugal) and ``nl``, Dutch, you can use the
|
||||
helper script directly:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
.scripts/download-translation-catalog pt_PT nl
|
||||
|
||||
|
||||
After the download process finishes, which might take a while, the next step is
|
||||
to build a localized version of the documentation.
|
||||
Following the ``pt_PT`` example above:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make html SPHINXLANG=pt_PT
|
||||
|
||||
|
||||
Open your browser, point it to the local documentation path and check the
|
||||
localized output you've just build.
|
||||
|
||||
|
||||
.. _`signup`: https://www.transifex.com/signup/
|
||||
.. _`Transifex`: https://www.transifex.com
|
||||
.. _`Salt Translation Project`: https://www.transifex.com/projects/p/salt/
|
||||
.. _`support domain`: http://support.transifex.com/
|
||||
.. _`directed to translators`: http://support.transifex.com/customer/portal/topics/414107-translators/articles
|
||||
|
Loading…
Reference in New Issue
Block a user