Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract text from CLI to enable internationalisation #182

Merged
merged 62 commits into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
1413081
Adds abilility to swap out text for internationalisation reasons
CarlBeek Feb 12, 2021
d828bb5
Remove auto function detector
CarlBeek Feb 12, 2021
6e3a0a3
Adds option auto function & file detection
CarlBeek Feb 12, 2021
3924b16
adds `existing_mnemonic`
CarlBeek Feb 12, 2021
5fceda2
Move remainder of messages into json files
CarlBeek Feb 22, 2021
b8c94a8
Fix typo
CarlBeek Feb 22, 2021
1f9626a
Moves `intl/utils.py` to `utils/intl.py`
CarlBeek Feb 22, 2021
4f10175
Add `msg_mnemonic_presentation` back
hwwhww Feb 23, 2021
b7f4188
Use load_text in test_new_mnemonic.py
hwwhww Feb 24, 2021
d38323e
@hwwhww's content nitpicks
CarlBeek Mar 8, 2021
2b222cd
imports 8 languages from crowdin
wackerow Mar 22, 2021
61e0c15
imports 2 languages from Crowdin
wackerow Mar 22, 2021
b288277
Enables intl language selection for `existing_mnemonic` options
CarlBeek Apr 1, 2021
907bb2e
enables `load_text()` to have a `lang` parameter
CarlBeek Apr 1, 2021
e439443
Convert remaining click.Options to JITOptions
CarlBeek Apr 1, 2021
621ee4f
load_test bug fixes
CarlBeek Apr 1, 2021
7b393bd
Adds missing `func` args to `load_text`
CarlBeek Apr 1, 2021
df09118
tests fix
CarlBeek Apr 6, 2021
a84baf3
Merge in dev
CarlBeek Apr 7, 2021
871b689
Merge pull request #191 from wackerow/translation-crowdin-import
CarlBeek Apr 7, 2021
c61f197
Fix intl JSON tags
CarlBeek Apr 7, 2021
a92890a
Adds schema tests to intl json
CarlBeek Apr 7, 2021
ad0b06b
Fix mnemonic defaults
CarlBeek Apr 8, 2021
a2f9a2a
Language selection revamp
CarlBeek Apr 8, 2021
7c99880
Fuzzy language matching + hella callbacks
CarlBeek Apr 8, 2021
17a8840
Adds intl files to build
CarlBeek Apr 8, 2021
63968ec
Adds mising files to macos build
CarlBeek Apr 8, 2021
d607b06
Adds translations files to pysinatller binary
CarlBeek Apr 9, 2021
7bbf7b3
Bump pyinstaller -> 4.2
CarlBeek Apr 9, 2021
6c2c708
Adds importlib-metadata for python < 3.8
CarlBeek Apr 9, 2021
25c093d
Adding before commiting helps
CarlBeek Apr 9, 2021
921b67d
adds zipp to build requirements
CarlBeek Apr 9, 2021
c5ecde0
Translation argument fixes
CarlBeek Apr 12, 2021
0542c1d
Adds indexing to language options
CarlBeek Apr 12, 2021
80f21e2
Merge branch 'dev' into translation
CarlBeek Apr 12, 2021
bcc64dd
Linting fixes
CarlBeek Apr 12, 2021
7471ed6
Update Binary-script testing to new verbage
CarlBeek Apr 12, 2021
6affab3
Adds intl tests
CarlBeek Apr 13, 2021
a7facf4
zh-CN content touchup
CarlBeek Apr 13, 2021
5bf8e54
Revert all CLI arguments to hard-coded English
CarlBeek Apr 13, 2021
9f09a2c
Merge branch 'translation' of github.com:ethereum/eth2.0-deposit-cli …
CarlBeek Apr 13, 2021
3a06e92
Adds revert to "en" if option does not exist for intl language
CarlBeek Apr 14, 2021
58a82d7
Excludes Tk & Tcl binaries from Linux & Win binaries
CarlBeek Apr 14, 2021
da97e6f
Extract new eth1 withdrawl params into JSON files
CarlBeek Apr 14, 2021
06d7920
Move eth1 withdrawal warnings into intl json files
CarlBeek Apr 19, 2021
e15806b
Update README with intl language options
CarlBeek Apr 19, 2021
b31f814
Move int-range checking from click to custom
CarlBeek Apr 19, 2021
68bb20d
Move choice checking from click to custom
CarlBeek Apr 19, 2021
ff34c4b
Intl password confirmations
CarlBeek Apr 23, 2021
06470b0
Small param typo
CarlBeek Apr 26, 2021
902c279
clarify en password confirmation prompts
CarlBeek Apr 26, 2021
b46e702
confirmation refactor
CarlBeek Apr 27, 2021
bd99461
intl start_index confirmation
CarlBeek Apr 27, 2021
126ebe2
intl start_index confirmation
CarlBeek Apr 27, 2021
64faf7e
Fix tests by adding --non_interactive flag.
CarlBeek Apr 30, 2021
261fdd9
adds confirm to start index for non-english languages
CarlBeek Apr 30, 2021
0b8d1c4
disallow 0 deposits (must be 1 or greater)
CarlBeek Apr 30, 2021
7962d4c
Mnemonic password warnign via repeated input
CarlBeek May 3, 2021
d07aea6
More lambdas for laziness!
CarlBeek May 4, 2021
6f388d8
Inform user why input validation failed
CarlBeek May 4, 2021
12a6615
Standardise argument headers
CarlBeek May 5, 2021
afad2b6
Fixes/removes incorrect help text about folders
CarlBeek May 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 12 additions & 22 deletions eth2deposit/cli/existing_mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
)

from eth2deposit.exceptions import ValidationError
from eth2deposit.intl.utils import load_text
from eth2deposit.key_handling.key_derivation.mnemonic import (
verify_mnemonic,
)
Expand All @@ -20,51 +21,40 @@ def validate_mnemonic(cts: click.Context, param: Any, mnemonic: str) -> str:
if verify_mnemonic(mnemonic, WORD_LISTS_PATH):
return mnemonic
else:
raise ValidationError('That is not a valid mnemonic, please check for typos.')
raise ValidationError(load_text('en', ['err_invalid_mnemonic']))


@click.command(
help='Generate (or recover) keys from an existing mnemonic',
help=load_text('en', ['arg_existing_mnemonic', 'help']),
)
@click.pass_context
@click.option(
'--mnemonic',
load_text('en', ['arg_mnemonic', 'argument']),
callback=validate_mnemonic,
help=('The mnemonic that you used to generate your keys. (It is recommended not to use this argument, and wait for '
'the CLI to ask you for your mnemonic as otherwise it will appear in your shell history.)'),
prompt='Please enter your mnemonic separated by spaces (" ")',
help=load_text('en', ['arg_mnemonic', 'help']),
prompt=load_text('en', ['arg_mnemonic', 'prompt']),
required=True,
type=str,
)
@click.password_option(
'--mnemonic-password',
load_text('en', ['arg_mnemonic_password', 'argument']),
default='',
help=('This is almost certainly not the argument you are looking for: it is for mnemonic passwords, not keystore '
'passwords. Providing a password here when you didn\'t use one initially, can result in lost keys (and '
'therefore funds)! Also note that if you used this tool to generate your mnemonic intially, then you did not '
'use a mnemonic password. However, if you are certain you used a password to "increase" the security of your '
'mnemonic, this is where you enter it.'),
help=load_text('en', ['arg_mnemonic_password', 'help']),
prompt=False,
)
@click.option(
'--validator_start_index',
load_text('en', ['arg_validator_start_index', 'argument']),
confirmation_prompt=True,
default=0,
help=('Enter the index (key number) you wish to start generating more keys from. '
'For example, if you\'ve generated 4 keys in the past, you\'d enter 4 here,'),
prompt=('Enter the index (key number) you wish to start generating more keys from. '
'For example, if you\'ve generated 4 keys in the past, you\'d enter 4 here,'),
help=load_text('en', ['arg_validator_start_index', 'help']),
prompt=load_text('en', ['arg_validator_start_index', 'prompt']),
type=click.IntRange(0, 2**32 - 1),
)
@generate_keys_arguments_decorator
def existing_mnemonic(ctx: click.Context, mnemonic: str, mnemonic_password: str, **kwargs: Any) -> None:
if mnemonic_password != '':
click.clear()
click.confirm(
('Are you absolutely certain that you used a mnemonic password? '
'(This is different from a keystore password!) '
'Using one when you are not supposed to can result in loss of funds!'),
abort=True)
click.confirm(load_text('en', ['msg_mnemonic_password_confirm']), abort=True)

ctx.obj = {'mnemonic': mnemonic, 'mnemonic_password': mnemonic_password}
ctx.forward(generate_keys)
47 changes: 24 additions & 23 deletions eth2deposit/cli/generate_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
CredentialList,
)
from eth2deposit.exceptions import ValidationError
from eth2deposit.intl.utils import (
load_text,
)
from eth2deposit.utils.validation import (
verify_deposit_data_json,
validate_password_strength,
Expand Down Expand Up @@ -36,23 +39,23 @@ def validate_password(cts: click.Context, param: Any, password: str) -> str:
try:
validate_password_strength(password)
except ValidationError as e:
click.echo(f'Error: {e} Please retype.')
click.echo(e)
else:
is_valid_password = True

while not is_valid_password:
password = get_password(text='Type the password that secures your validator keystore(s)')
password = get_password(load_text('en', ['msg_password_prompt']))
try:
validate_password_strength(password)
except ValidationError as e:
click.echo(f'Error: {e} Please retype.')
click.echo(e)
else:
# Confirm password
password_confirmation = get_password(text='Repeat for confirmation')
password_confirmation = get_password(load_text('en', ['msg_password_confirm']))
if password == password_confirmation:
is_valid_password = True
else:
click.echo('Error: the two entered values do not match. Please retype again.')
click.echo(load_text('en', ['err_password_mismatch']))

return password

CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -64,32 +67,30 @@ def generate_keys_arguments_decorator(function: Callable[..., Any]) -> Callable[
'''
decorators = [
click.option(
'--num_validators',
prompt='Please choose how many validators you wish to run',
help='The number of validators keys you want to generate (you can always generate more later)',
load_text('en', ['num_validators', 'argument']),
help=load_text('en', ['num_validators', 'help']),
prompt=load_text('en', ['num_validators', 'prompt']),
required=True,
type=click.IntRange(0, 2**32 - 1),
),
click.option(
'--folder',
load_text('en', ['folder', 'argument']),
default=os.getcwd(),
help='The folder to place the generated keystores and deposit_data.json in',
help=load_text('en', ['folder', 'help']),
type=click.Path(exists=True, file_okay=False, dir_okay=True),
),
click.option(
'--chain',
load_text('en', ['chain', 'argument']),
default=MAINNET,
help='The version of eth2 you are targeting. use "mainnet" if you are depositing ETH',
prompt='Please choose the (mainnet or testnet) network/chain name',
help=load_text('en', ['chain', 'help']),
prompt=load_text('en', ['chain', 'prompt']),
type=click.Choice(ALL_CHAINS.keys(), case_sensitive=False),
),
click.password_option(
'--keystore_password',
load_text('en', ['keystore_password', 'argument']),
callback=validate_password,
help=('The password that will secure your keystores. You will need to re-enter this to decrypt them when '
'you setup your eth2 validators. (It is reccomened not to use this argument, and wait for the CLI '
'to ask you for your mnemonic as otherwise it will appear in your shell history.)'),
prompt='Type the password that secures your validator keystore(s)',
help=load_text('en', ['keystore_password', 'help']),
prompt=load_text('en', ['keystore_password', 'prompt']),
),
]
for decorator in reversed(decorators):
Expand All @@ -110,7 +111,7 @@ def generate_keys(ctx: click.Context, validator_start_index: int,
os.mkdir(folder)
click.clear()
click.echo(RHINO_0)
click.echo('Creating your keys.')
click.echo(load_text('en', ['msg_key_creation']))
credentials = CredentialList.from_mnemonic(
mnemonic=mnemonic,
mnemonic_password=mnemonic_password,
Expand All @@ -122,8 +123,8 @@ def generate_keys(ctx: click.Context, validator_start_index: int,
keystore_filefolders = credentials.export_keystores(password=keystore_password, folder=folder)
deposits_file = credentials.export_deposit_data_json(folder=folder)
if not credentials.verify_keystores(keystore_filefolders=keystore_filefolders, password=keystore_password):
raise ValidationError("Failed to verify the keystores.")
raise ValidationError(load_text('en', ['err_verify_keystores']))
if not verify_deposit_data_json(deposits_file):
raise ValidationError("Failed to verify the deposit data JSON files.")
click.echo('\nSuccess!\nYour keys can be found at: %s' % folder)
click.pause('\n\nPress any key.')
raise ValidationError(load_text('en', ['err_verify_deposit']))
click.echo(load_text('en', ['msg_creation_success']) + folder)
click.pause(load_text('en', ['msg_pause']))
Empty file added eth2deposit/intl/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions eth2deposit/intl/en/cli/existing_mnemonic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"validate_mnemonic": {
"err_invalid_mnemonic": "That is not a valid mnemonic, please check for typos."
},
"<module>": {
"arg_existing_mnemonic": {
"help": "Generate (or recover) keys from an existing mnemonic"
},
"arg_mnemonic": {
"argument": "--mnemonic",
"help": "The mnemonic that you used to generate your keys. (It is recommended not to use this argument, and wait for the CLI to ask you for your mnemonic as otherwise it will appear in your shell history.)",
"prompt": "Please enter your mnemonic separated by spaces (\" \")"
},
"arg_mnemonic_password": {
"argument": "--mnemonic-password",
"help": "This is almost certainly not the argument you are looking for: it is for mnemonic passwords, not keystore passwords. Providing a password here when you didn't use one initially, can result in lost keys (and therefore funds)! Also note that if you used this tool to generate your mnemonic intially, then you did not use a mnemonic password. However, if you are certain you used a password to \"increase\" the security of your mnemonic, this is where you enter it."
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
},
"arg_validator_start_index": {
"argument": "--validator_start_index",
"help": "Enter the index (key number) you wish to start generating more keys from. For example, if you've generated 4 keys in the past, you'd enter 4 here.",
"prompt": "Enter the index (key number) you wish to start generating more keys from. For example, if you've generated 4 keys in the past, you'd enter 4 here."
}
},
"existing_mnemonic": {
"msg_mnemonic_password_confirm": "Are you absolutely certain that you used a mnemonic password? (This is different from a keystore password!) Using one when you are not supposed to can result in loss of funds!"
}
}
35 changes: 35 additions & 0 deletions eth2deposit/intl/en/cli/generate_keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"validate_password": {
"msg_password_prompt": "Type the password that secures your validator keystore(s)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forwarding on a comment by @samajammin on initial intl audit (#180) that I agree with... consider making this clearer that the user is about to create a new password, vs typing an existing password, eg:
Create a password that will secure your validator keystore(s)
"Type the password that secures" kinda implies there is already password currently securing it.

"msg_password_confirm": "Repeat for confirmation",
"err_password_mismatch": "Error: the two entered values do not match. Please type again."
},
"generate_keys_arguments_decorator": {
"num_validators": {
"argument": "--num_validators",
"help": "The number of validators keys you want to generate (you can always generate more later)",
"prompt": "Please choose how many validators you wish to run"
},
"folder": {
"argument": "--folder",
"help": "The number of validators keys you want to generate (you can always generate more later)"
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
},
"chain": {
"argument": "--chain",
"help": "The version of eth2 you are targeting. Use \"mainnet\" if you are depositing ETH",
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
"prompt": "Please choose the (mainnet or testnet) network/chain name"
},
"keystore_password": {
"argument": "--keystore_password",
"help": "The version of eth2 you are targeting. Use \"mainnet\" if you are depositing ETH",
"prompt": "The password that will secure your keystores. You will need to re-enter this to decrypt them when you setup your eth2 validators. (It is reccomened not to use this argument, and wait for the CLI to ask you for your mnemonic as otherwise it will appear in your shell history.)"
}
},
"generate_keys": {
"msg_key_creation": "Creating your keys.",
"msg_creation_success": "\nSuccess!\nYour keys can be found at: ",
"msg_pause": "\n\nPress any key.",
"err_verify_keystores": "Failed to verify the keystores.",
"err_verify_deposit": "Failed to verify the deposit data JSON files."
}
}
42 changes: 42 additions & 0 deletions eth2deposit/intl/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import inspect
from functools import reduce
import json
from typing import (
Any,
Dict,
List,
)
import os

from eth2deposit.utils.constants import INTL_CONETENT_PATH


def _get_from_dict(dataDict: Dict[str, Any], mapList: List[str]) -> str:
'''
Iterate nested dictionary
'''
return reduce(dict.get, mapList, dataDict) # type: ignore


def load_text(lang: str, params: List[str], file_path: str='', func: str='') -> str:
'''
Determine and return the appropriate internationalisation text for a given `lang` and `params`
'''
if file_path == '':
# Auto-detect file-path based on call stack
file_path = inspect.stack()[1].filename
file_path = file_path[:-3] + '.json' # replace .py with .json

if func == '':
# Auto-detect function based on call stack
func = inspect.stack()[1].function

# Determine path to json text
file_path_list = os.path.normpath(file_path).split(os.path.sep)
rel_path_list = file_path_list[file_path_list.index('eth2deposit') + 1:]
json_path = os.path.join(INTL_CONETENT_PATH, lang, *rel_path_list)

# browse json until text is found
with open(json_path) as f:
text_dict = json.load(f)
return _get_from_dict(text_dict, [func] + params)
1 change: 1 addition & 0 deletions eth2deposit/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@


# File/folder constants
INTL_CONETENT_PATH = os.path.join('eth2deposit', 'intl')
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
WORD_LISTS_PATH = os.path.join('eth2deposit', 'key_handling', 'key_derivation', 'word_lists')
DEFAULT_VALIDATOR_KEYS_FOLDER_NAME = 'validator_keys'

Expand Down
2 changes: 1 addition & 1 deletion eth2deposit/utils/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ def validate_deposit(deposit_data_dict: Dict[str, Any]) -> bool:

def validate_password_strength(password: str) -> None:
if len(password) < 8:
raise ValidationError(f"The password length should be at least 8. Got {len(password)}.")
raise ValidationError(f"The password length should be at least 8. Got {len(password)}. Please retype")