Skip to content

Commit 074ec87

Browse files
committed
Merged i18n into master
2 parents 32341dc + 0743ceb commit 074ec87

22 files changed

+1609
-65
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,15 @@ For the mediator payment, a mandatory multisig address is created. To spend the
8080

8181
Like with the primary payment, a user is prompted for a signed mediator payment if they are a job creator accepting a delivery or they are the mediator resolving a dispute.
8282

83-
## Development and Testing
83+
## Developer Notes
84+
85+
We have [a quick Vagrant-based setup](https://github.com/ReinProject/devsetup) that gives you a virtual machine with the python-rein client, Causway server and its Bitcoin Core (testnet) node all configured to work together. Testing usually involves creating users and walking through jobs so a virtual machine that has all components going, even allowing payments to be sent is very helpful.
86+
87+
To generate or update pot files for translation, run the following from the root of the repo:
88+
89+
xgettext.pl rein/cli.py rein/lib/*.py
90+
91+
## Testing
8492

8593
We have [a quick Vagrant-based setup](https://github.com/ReinProject/devsetup) that gives you a virtual machine with the python-rein client, Causway server and its Bitcoin Core (testnet) node all configured to work together. Testing usually involves creating users and walking through jobs so a virtual machine that has all components going, even allowing payments to be sent is very helpful.
8694

rein/cli.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .lib.util import unique
2727
from .lib.io import safe_get
2828
from .lib.script import build_2_of_3, build_mandatory_multisig, check_redeem_scripts
29+
from .lib.localization import init_localization
2930
from .lib.transaction import partial_spend_p2sh, spend_p2sh, spend_p2sh_mediator, partial_spend_p2sh_mediator, partial_spend_p2sh_mediator_2
3031
from .lib.rating import add_rating, get_user_jobs
3132

@@ -45,17 +46,20 @@
4546
from .lib.mediator import Mediator
4647

4748
rein = config.Config()
49+
init_localization()
50+
4851
import bitcoin
4952
from bitcoin.wallet import P2PKHBitcoinAddress
5053
from bitcoin.core import x
5154
if (rein.testnet): bitcoin.SelectParams('testnet')
55+
init_localization()
5256

5357

5458
@click.group()
5559
@click.option('--debug/--no-debug', default=False)
5660
@click.pass_context
5761
def cli(ctx, debug):
58-
"""
62+
_("""
5963
Rein is a decentralized professional services market and Python-rein is a client
6064
that provides a user interface. Use this program from your local browser or command
6165
line to create an account, post a job, bid, etc.
@@ -79,7 +83,7 @@ def cli(ctx, debug):
7983
$ rein resolve - mediator posts decision
8084
8185
For more info and the setup guide visit: http://reinproject.org
82-
"""
86+
""")
8387
if debug:
8488
click.echo("Debuggin'")
8589
pass
@@ -88,23 +92,24 @@ def cli(ctx, debug):
8892
@cli.command()
8993
@click.option('--multi/--no-multi', default=False, help="add even if an identity exists")
9094
def setup(multi):
91-
"""
95+
_("""
9296
Setup or import an identity.
9397
9498
You will choose a name or handle for your account, include public contact information,
9599
and a delegate Bitcoin address/private key that the program will use to sign documents
96100
on your behalf. An enrollment document will be created and you will need to sign it
97101
with your master Bitcoin private key.
98-
"""
102+
""")
99103
log = rein.get_log()
100104
if multi:
101105
rein.set_multiuser()
102106
log.info('entering setup')
103107
if multi or rein.has_no_account():
104-
click.echo("\n" + highlight("Welcome to Rein.", True, True) + "\n\n"
105-
"Do you want to import a backup or create a new account?\n\n"
106-
"1 - Create new account\n2 - Import backup\n")
108+
click.echo("\n" + highlight(_("Welcome to Rein."), True, True) + "\n\n" +
109+
_("Do you want to import a backup or create a new account?\n\n") +
110+
_("1 - Create new account\n2 - Import backup\n"))
107111
choice = click.prompt(highlight("Choice", True, True), type=int, default=1)
112+
108113
if choice == 1:
109114
create_account(rein)
110115
log.info('account created')

rein/lib/forms.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616

1717
def validate_privkey(form, field):
1818
if not privkey_to_address(field.data):
19-
raise ValidationError("Not a valid private key.")
19+
raise ValidationError(_("Not a valid private key."))
2020

2121
def validate_address(form, field):
2222
if not check_bitcoin_address(field.data):
23-
raise ValidationError("Invalid address")
23+
raise ValidationError(_("Invalid address"))
2424

2525
def validate_en(form, field):
2626
message = field.data.replace("\r\n","\n")
2727
if not validate_enrollment(message):
28-
raise ValidationError("Invalid signature")
28+
raise ValidationError(_("Invalid signature"))
2929

3030
def validate_mediator_fee(form, field):
3131
try:
@@ -38,55 +38,55 @@ def avoid_self_rating(form, field):
3838
raise ValidationError('You may not rate yourself!')
3939

4040
class SetupForm(Form):
41-
name = TextField('Name / Handle', validators = [Required()])
42-
contact = TextField('Email / Bitmessage', validators = [Required()])
43-
maddr = TextField('Master Bitcoin address', validators = [Required(), validate_address])
44-
daddr = TextField('Delegate Bitcoin address', validators = [Required(), validate_address])
45-
dkey = PasswordField('Delegate Bitcoin private Key', validators = [Required(), validate_privkey])
46-
will_mediate = RadioField('Register as a mediator?', choices = [('1','Yes'), ('0', 'No')])
47-
mediator_fee = TextField('Mediator Fee', validators = [validate_mediator_fee]) # TODO make required only if Yes above
41+
name = TextField(_('Name / Handle'), validators = [Required()])
42+
contact = TextField(_('Email / Bitmessage'), validators = [Required()])
43+
maddr = TextField(_('Master Bitcoin address'), validators = [Required(), validate_address])
44+
daddr = TextField(_('Delegate Bitcoin address'), validators = [Required(), validate_address])
45+
dkey = PasswordField(_('Delegate Bitcoin private Key'), validators = [Required(), validate_privkey])
46+
will_mediate = RadioField(_('Register as a mediator?'), choices = [('1','Yes'), ('0', 'No')])
47+
mediator_fee = TextField(_('Mediator Fee'), validators = [validate_mediator_fee]) # TODO make required only if Yes above
4848

4949
class SignForm(Form):
5050
identity_id = HiddenInput("identity_id")
51-
signed = TextAreaField('Signed enrollment', validators = [Required(), validate_en])
51+
signed = TextAreaField(_('Signed enrollment'), validators = [Required(), validate_en])
5252

5353
class JobPostForm(Form):
54-
job_name = TextField('Job name', validators = [Required()])
55-
description = TextAreaField('Description', validators = [Required()])
56-
tags = TextField('Tags', validators = [Required()])
57-
expire_days = TextField('Expiration (days)', validators = [Required()])
58-
mediator_maddr = RadioField('Choose mediator')
54+
job_name = TextField(_('Job name'), validators = [Required()])
55+
description = TextAreaField(_('Description'), validators = [Required()])
56+
tags = TextField(_('Tags'), validators = [Required()])
57+
expire_days = TextField(_('Expiration (days)'), validators = [Required()])
58+
mediator_maddr = RadioField(_('Choose mediator'))
5959

6060
class BidForm(Form):
61-
description = TextAreaField('Description', validators = [Required()])
62-
bid_amount = TextAreaField('Bid amount', validators = [Required()])
63-
job_id = RadioField('Choose Job to bid on')
61+
description = TextAreaField(_('Description'), validators = [Required()])
62+
bid_amount = TextAreaField(_('Bid amount'), validators = [Required()])
63+
job_id = RadioField(_('Choose Job to bid on'))
6464

6565
class JobOfferForm(Form):
66-
bid_id = RadioField('Choose bid')
66+
bid_id = RadioField(_('Choose bid'))
6767

6868
class DeliverForm(Form):
69-
deliverable = TextAreaField('Deliverables', validators = [Required()])
70-
job_id = RadioField('Choose job associated with deliverables')
69+
deliverable = TextAreaField(_('Deliverables'), validators = [Required()])
70+
job_id = RadioField(_('Choose job associated with deliverables'))
7171

7272
class DisputeForm(Form):
73-
dispute_detail = TextAreaField('Dispute detail', validators = [Required()])
74-
order_id = RadioField('Choose job')
73+
dispute_detail = TextAreaField(_('Dispute detail'), validators = [Required()])
74+
order_id = RadioField(_('Choose job'))
7575

7676
class AcceptForm(Form):
77-
deliverable_id = RadioField('Deliverables')
77+
deliverable_id = RadioField(_('Deliverables'))
7878

7979
class ResolveForm(Form):
80-
resolution = TextAreaField('Resolution', validators = [Required()])
81-
client_payment_amount = TextField('Client payment amount in BTC (remainder sent to worker)', validators = [Required()])
82-
dispute_id = RadioField('Disputes')
80+
resolution = TextAreaField(_('Resolution'), validators = [Required()])
81+
client_payment_amount = TextField(_('Client payment amount in BTC (remainder sent to worker)'), validators = [Required()])
82+
dispute_id = RadioField(_('Disputes'))
8383

8484
class AcceptResolutionForm(Form):
85-
resolution_id = RadioField('Resolution')
85+
resolution_id = RadioField(_('Resolution'))
8686

8787
class RatingForm(Form):
88-
job_id = TextField('Job id', validators = [Required()], default='')
89-
user_id = TextField('User SIN', validators = [Required(), avoid_self_rating], default='')
90-
rated_by_id = TextField('Rated by SIN', validators = [Required()], default='')
91-
rating = TextField('Rating', validators=[Required()], default=0, widget=HiddenInput())
92-
comments = TextAreaField('Comments', validators = [], default='')
88+
job_id = TextField(_('Job ID'), validators = [Required()], default='')
89+
user_id = TextField(_('User SIN'), validators = [Required(), avoid_self_rating], default='')
90+
rated_by_id = TextField(_('Rated by SIN'), validators = [Required()], default='')
91+
rating = TextField(_('Rating'), validators=[Required()], default=0, widget=HiddenInput())
92+
comments = TextAreaField(_('Comments'), validators = [], default='')

rein/lib/io.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def safe_get(log, url):
1313
try:
1414
answer = requests.get(url=url, proxies=rein.proxies)
1515
except requests.ConnectionError:
16-
click.echo('Error connecting to server.')
16+
click.echo(_('Error connecting to server.'))
1717
log.error('server connect error ' + url)
1818
return None
1919

rein/lib/localization.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import gettext
2+
import locale
3+
import logging
4+
import os
5+
import rein.locale
6+
7+
def init_localization():
8+
'''prepare l10n'''
9+
# Extract system locale identifier
10+
loc = locale.getdefaultlocale()[0]
11+
file_name = "messages-%s.mo" % loc.split('_')[0]
12+
file_path = os.path.join(os.path.dirname(os.path.abspath(rein.locale.__file__)), file_name)
13+
print(file_path)
14+
15+
try:
16+
logging.debug("Opening message file %s for locale %s", file_name, loc[0])
17+
trans = gettext.GNUTranslations(open(file_path, "rb" ))
18+
19+
except IOError:
20+
logging.debug("Locale not found. Using default messages")
21+
trans = gettext.NullTranslations()
22+
23+
trans.install()
24+
25+
if __name__ == '__main__':
26+
init_localization()

rein/lib/market.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def sign_and_store_document(rein, doc_type, document, signature_address=None, si
5353
click.echo("\n%s\n" % document)
5454
done = False
5555
while not done:
56-
filename = click.prompt("File containing signed document", type=str, default=doc_type + '.sig.txt')
56+
filename = click.prompt(_("File containing signed document"), type=str, default=doc_type + '.sig.txt')
5757
if os.path.isfile(filename):
5858
done = True
5959
f = open(filename, 'r')

rein/lib/maybe/dochashcash.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import scrypt
2+
import time
3+
from bitcoin.core import x, b2x
4+
5+
if __name__ == '__main__':
6+
text = """-----BEGIN BITCOIN SIGNED MESSAGE-----
7+
- ----BEGIN BITCOIN SIGNED MESSAGE-----
8+
- ----BEGIN BITCOIN SIGNED MESSAGE-----
9+
Name/Handle: Knightdk
10+
Contact: knightdk on Bitcointalk.org
11+
Master signing address: 16mT7jrpkjnJBD7a3TM2awyxHub58H6r6Z
12+
Delegate signing address: N/A
13+
Willing to mediate: Y
14+
Mediation public key: 04594f2582c859c4f65084ee7fe8e9ec2d695bb988a3f53db48eaaff6ff3a0282b2be0c79"""\
15+
"""fefca01277404d0fdc3a923e8ed02efd6ab96980f3e229a81fbe032e9
16+
- ----BEGIN SIGNATURE-----
17+
16mT7jrpkjnJBD7a3TM2awyxHub58H6r6Z
18+
GxHE6iJH2aMpsRk7cszvXsLieDawzArpt7XDdOPhVFD5KVqIvKve1fwUKeN6ct4bld41XLdrZ7Dvaj7x1Oiw0uo=
19+
- ----END BITCOIN SIGNED MESSAGE-----
20+
- ----BEGIN SIGNATURE-----
21+
1BbgnPQYeXAt39ifLNUWP1RBktpzGLmRZS
22+
IGJcg+MoqpBQNtptelyZfC2zBKk5SZQQjtf4pHSxb0yZH6kn/9Dhd1TWFfXUXsWmZq78xYye4lKi1aQUeNQ2ZFs=
23+
- ----END BITCOIN SIGNED MESSAGE-----
24+
-----BEGIN SIGNATURE-----
25+
1DVK9Rdi2wcpcfEkep7FSNUui7fzadmxsW
26+
IMcU7MvLl7T+hY0mmMw6mblLstnXd9Ly36z7uYMqv7ZZEuZQOvuXN2GjYU0Nq4So9GKQRkQwIis7EiN6luTMcOY=
27+
-----END BITCOIN SIGNED MESSAGE-----"""
28+
29+
start = time.time()
30+
count = 1
31+
32+
h1 = scrypt.hash(text, 'random salt', 32768, 8, 1, 16)
33+
while 1:
34+
h1 = scrypt.hash(h1, 'random salt', 32768, 8, 1, 16)
35+
count += 1
36+
if h1 < x('04ffffffffffffffffffffffffffffff'):
37+
print(b2x(h1), time.time() - start, count)

rein/lib/order.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,78 @@
11
from sqlalchemy import Column, Integer, String, Boolean, and_
22
from sqlalchemy.ext.declarative import declarative_base
3+
from localization import init_localization
34
try:
45
from document import Document
56
except:
67
pass
78

89
Base = declarative_base()
10+
init_localization()
911

1012
STATE = {
1113
'job_posting': {
1214
'pre': [],
1315
'next': ['bid'],
1416
'endpoint': '/post',
15-
'past_tense': 'posted',
16-
'description': "Funds for each job in Rein are stored in two multisig addresses. One address\n" \
17-
"is for the primary payment that will go to the worker on completion. The\n" \
18-
"second address pays the mediator to be available to resolve a dispute\n" \
19-
"if necessary. The second address should be funded according to the percentage\n" \
20-
"specified by the mediator and is in addition to the primary payment. The\n" \
21-
"listing below shows available mediators and the fee they charge. You should\n" \
22-
"consider the fee as well as any reputational data you are able to find when\n" \
23-
"choosing a mediator. Your choice may affect the number and quality of bids\n"
17+
'past_tense': _('posted'),
18+
'description': _("""Funds for each job in Rein are stored in two multisig addresses. One address
19+
is for the primary payment that will go to the worker on completion. The
20+
second address pays the mediator to be available to resolve a dispute
21+
if necessary. The second address should be funded according to the percentage
22+
specified by the mediator and is in addition to the primary payment. The
23+
listing below shows available mediators and the fee they charge. You should
24+
consider the fee as well as any reputational data you are able to find when
25+
choosing a mediator. Your choice may affect the number and quality of bids"""),
2426
},
2527
'bid': {
2628
'pre': ['job_posting'],
2729
'next': ['offer'],
2830
'endpoint': None,
29-
'past_tense': 'bid submitted'
31+
'past_tense': _('bid submitted'),
3032
},
3133
'offer': {
3234
'pre': ['bid'],
3335
'next': ['delivery', 'creatordispute', 'workerdispute'],
3436
'endpoint': '/offer',
35-
'past_tense': 'job awarded',
37+
'past_tense': _('job awarded'),
3638
},
3739
'delivery': {
3840
'pre': ['offer'],
3941
'next': ['accept', 'creatordispute', 'workerdispute'],
4042
'endpoint': '/deliver',
41-
'past_tense': 'deliverables submitted'
43+
'past_tense': _('deliverables submitted'),
4244
},
4345
'creatordispute': {
4446
'pre': ['offer', 'delivery'],
4547
'next': ['resolve', 'workerdispute'],
4648
'endpoint': '/dispute',
47-
'past_tense':'disputed by job creator'
49+
'past_tense': _('disputed by job creator'),
4850
},
4951
'workerdispute': {
5052
'pre': ['offer', 'delivery', 'accept'],
5153
'next': ['resolve', 'creatordispute'],
5254
'endpoint': '/dispute',
53-
'past_tense': 'disputed by worker'
55+
'past_tense': _('disputed by worker'),
5456
},
5557
'accept': {
5658
'pre': ['delivery'],
5759
'next': ['workerdispute', 'complete'],
5860
'endpoint': '/accept',
59-
'past_tense': 'complete, work accepted',
61+
'past_tense': _('complete, work accepted'),
6062
},
6163
'resolve': {
6264
'pre': ['creatordispute', 'workerdispute'],
6365
'next': ['acceptresolution'],
6466
'endpoint': '/resolve',
65-
'past_tense': 'dispute resolved'
67+
'past_tense': _('complete, dispute resolved'),
6668
},
6769
'acceptresolution': {
6870
'pre': ['resolve'],
6971
'next': ['complete'],
7072
'endpoint': '/acceptresolution',
71-
'past_tense': 'complete, resolution accepted'
73+
'past_tense': _('complete, resolution accepted'),
7274
}
73-
}
75+
}
7476

7577
class Order(Base):
7678
__tablename__ = 'order'

rein/lib/toolbox.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ def yes_or_no(question):
66
if len(reply) > 0 and reply[0] == 'n':
77
return False
88
else:
9-
return yes_or_no("Please enter ")
9+
return yes_or_no(_("Please enter "))

rein/lib/validate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
def filter_out_expired(rein, user, urls, jobs):
1212
live = []
1313
times = {}
14-
click.echo('Verifying block times...')
14+
click.echo(_('Verifying block times...'))
1515
with click.progressbar(jobs) as bar:
1616
for j in bar:
1717
if 'Block hash' not in j:
@@ -165,7 +165,7 @@ def remote_query(rein, user, urls, log, query_type, distinct):
165165
sel_url = "{0}query?owner={1}&query={2}&testnet={3}"
166166
data = safe_get(log, sel_url.format(url, user.maddr, query_type, rein.testnet))
167167
if data is None or query_type not in data or len(data[query_type]) == 0:
168-
click.echo('None found')
168+
click.echo(_('None found'))
169169
continue
170170
res += filter_and_parse_valid_sigs(rein, data[query_type])
171171
return unique(res, distinct)

0 commit comments

Comments
 (0)