From 46f25e56782e17eea91cc80c6211a8ae0da7d6c6 Mon Sep 17 00:00:00 2001 From: Philip Wafula Date: Tue, 4 Jan 2022 16:16:00 +0000 Subject: [PATCH] USSD Hardening and Cleanups --- .../cic-ussd/cic_ussd/account/guardianship.py | 22 + apps/cic-ussd/cic_ussd/account/statement.py | 15 - apps/cic-ussd/cic_ussd/account/tokens.py | 14 - apps/cic-ussd/cic_ussd/cache.py | 3 +- apps/cic-ussd/cic_ussd/db/models/account.py | 6 +- apps/cic-ussd/cic_ussd/db/ussd_menu.json | 172 ++-- apps/cic-ussd/cic_ussd/processor/menu.py | 138 +-- apps/cic-ussd/cic_ussd/processor/ussd.py | 71 +- apps/cic-ussd/cic_ussd/processor/util.py | 58 +- .../runnable/daemons/cic_user_tasker.py | 5 + .../runnable/daemons/cic_user_ussd_server.py | 29 +- .../cic_ussd/state_machine/logic/account.py | 45 +- .../cic_ussd/state_machine/logic/language.py | 95 ++ .../cic_ussd/state_machine/logic/pin_guard.py | 23 +- .../cic_ussd/state_machine/logic/tokens.py | 2 +- .../cic_ussd/tasks/callback_handler.py | 15 +- apps/cic-ussd/cic_ussd/translation.py | 47 + apps/cic-ussd/config/app.ini | 3 + apps/cic-ussd/config/translations.ini | 7 + apps/cic-ussd/requirements.txt | 3 +- .../states/account_management_states.json | 4 +- apps/cic-ussd/states/signup_states.json | 2 + .../tests/cic_ussd/processor/test_menu.py | 32 +- .../tests/cic_ussd/processor/test_ussd.py | 4 +- .../tests/cic_ussd/processor/test_util.py | 2 +- .../tests/cic_ussd/test_notifications.py | 4 +- .../tests/cic_ussd/test_translation.py | 4 +- .../language_setting_transitions.json | 131 ++- .../transitions/signup_transitions.json | 26 - apps/cic-ussd/var/lib/locale/helpers.en.yml | 36 - apps/cic-ussd/var/lib/locale/helpers.sw.yml | 36 - apps/cic-ussd/var/lib/locale/sms.en.yml | 11 - apps/cic-ussd/var/lib/locale/sms.sw.yml | 11 - apps/cic-ussd/var/lib/locale/ussd.en.yml | 317 ------- apps/cic-ussd/var/lib/locale/ussd.sw.yml | 316 ------- apps/cic-ussd/var/lib/sys/guardians.txt | 0 apps/cic-ussd/var/lib/sys/helpers.csv | 19 + apps/cic-ussd/var/lib/sys/languages.json | 9 + apps/cic-ussd/var/lib/sys/sms.csv | 7 + apps/cic-ussd/var/lib/sys/ussd.csv | 862 ++++++++++++++++++ 40 files changed, 1582 insertions(+), 1024 deletions(-) create mode 100644 apps/cic-ussd/cic_ussd/account/guardianship.py create mode 100644 apps/cic-ussd/cic_ussd/state_machine/logic/language.py delete mode 100644 apps/cic-ussd/var/lib/locale/helpers.en.yml delete mode 100644 apps/cic-ussd/var/lib/locale/helpers.sw.yml delete mode 100644 apps/cic-ussd/var/lib/locale/sms.en.yml delete mode 100644 apps/cic-ussd/var/lib/locale/sms.sw.yml delete mode 100644 apps/cic-ussd/var/lib/locale/ussd.en.yml delete mode 100644 apps/cic-ussd/var/lib/locale/ussd.sw.yml create mode 100644 apps/cic-ussd/var/lib/sys/guardians.txt create mode 100644 apps/cic-ussd/var/lib/sys/helpers.csv create mode 100644 apps/cic-ussd/var/lib/sys/languages.json create mode 100644 apps/cic-ussd/var/lib/sys/sms.csv create mode 100644 apps/cic-ussd/var/lib/sys/ussd.csv diff --git a/apps/cic-ussd/cic_ussd/account/guardianship.py b/apps/cic-ussd/cic_ussd/account/guardianship.py new file mode 100644 index 00000000..32bcfaff --- /dev/null +++ b/apps/cic-ussd/cic_ussd/account/guardianship.py @@ -0,0 +1,22 @@ +# standard imports + +# external imports + +# local imports + +class Guardianship: + guardians: list = [] + + @classmethod + def load_system_guardians(cls, guardians_file: str): + with open(guardians_file, 'r') as system_guardians: + cls.guardians = [line.strip() for line in system_guardians] + + def is_system_guardian(self, phone_number: str): + """ + :param phone_number: + :type phone_number: + :return: + :rtype: + """ + return phone_number in self.guardians diff --git a/apps/cic-ussd/cic_ussd/account/statement.py b/apps/cic-ussd/cic_ussd/account/statement.py index df0e1b02..ce8e156b 100644 --- a/apps/cic-ussd/cic_ussd/account/statement.py +++ b/apps/cic-ussd/cic_ussd/account/statement.py @@ -13,7 +13,6 @@ from cic_types.condiments import MetadataPointer from cic_ussd.account.chain import Chain from cic_ussd.account.transaction import from_wei from cic_ussd.cache import cache_data_key, get_cached_data -from cic_ussd.translation import translation_for logg = logging.getLogger(__name__) @@ -97,17 +96,3 @@ def query_statement(blockchain_address: str, limit: int = 9): callback_param=blockchain_address ) cic_eth_api.list(address=blockchain_address, limit=limit) - - -def statement_transaction_set(preferred_language: str, transaction_reprs: list): - """ - :param preferred_language: - :type preferred_language: - :param transaction_reprs: - :type transaction_reprs: - :return: - :rtype: - """ - if not transaction_reprs: - return translation_for('helpers.no_transaction_history', preferred_language) - return ''.join(f'{transaction_repr}\n' for transaction_repr in transaction_reprs) diff --git a/apps/cic-ussd/cic_ussd/account/tokens.py b/apps/cic-ussd/cic_ussd/account/tokens.py index 4ffeb57b..288b4d9c 100644 --- a/apps/cic-ussd/cic_ussd/account/tokens.py +++ b/apps/cic-ussd/cic_ussd/account/tokens.py @@ -15,7 +15,6 @@ from cic_ussd.cache import cache_data, cache_data_key, get_cached_data from cic_ussd.error import CachedDataNotFoundError, SeppukuError from cic_ussd.metadata.tokens import query_token_info, query_token_metadata from cic_ussd.processor.util import wait_for_cache -from cic_ussd.translation import translation_for logg = logging.getLogger(__file__) @@ -326,16 +325,3 @@ def set_active_token(blockchain_address: str, token_symbol: str): cache_data(key=key, data=token_symbol) -def token_list_set(preferred_language: str, token_data_reprs: list): - """ - :param preferred_language: - :type preferred_language: - :param token_data_reprs: - :type token_data_reprs: - :return: - :rtype: - """ - if not token_data_reprs: - return translation_for('helpers.no_tokens_list', preferred_language) - return ''.join(f'{token_data_repr}\n' for token_data_repr in token_data_reprs) - diff --git a/apps/cic-ussd/cic_ussd/cache.py b/apps/cic-ussd/cic_ussd/cache.py index 2faa46d9..9976d9ad 100644 --- a/apps/cic-ussd/cic_ussd/cache.py +++ b/apps/cic-ussd/cic_ussd/cache.py @@ -55,5 +55,6 @@ def cache_data_key(identifier: Union[list, bytes], salt: MetadataPointer): hash_object.update(identity) else: hash_object.update(identifier) - hash_object.update(salt.value.encode(encoding="utf-8")) + if salt != MetadataPointer.NONE: + hash_object.update(salt.value.encode(encoding="utf-8")) return hash_object.digest().hex() diff --git a/apps/cic-ussd/cic_ussd/db/models/account.py b/apps/cic-ussd/cic_ussd/db/models/account.py index 505f822b..1d09e2ea 100644 --- a/apps/cic-ussd/cic_ussd/db/models/account.py +++ b/apps/cic-ussd/cic_ussd/db/models/account.py @@ -171,7 +171,7 @@ class Account(SessionBase): return check_password_hash(password, self.password_hash) -def create(chain_str: str, phone_number: str, session: Session): +def create(chain_str: str, phone_number: str, session: Session, preferred_language: str): """ :param chain_str: :type chain_str: @@ -179,12 +179,14 @@ def create(chain_str: str, phone_number: str, session: Session): :type phone_number: :param session: :type session: + :param preferred_language: + :type preferred_language: :return: :rtype: """ api = Api(callback_task='cic_ussd.tasks.callback_handler.account_creation_callback', callback_queue='cic-ussd', - callback_param='', + callback_param=preferred_language, chain_str=chain_str) task_uuid = api.create_account().id TaskTracker.add(session=session, task_uuid=task_uuid) diff --git a/apps/cic-ussd/cic_ussd/db/ussd_menu.json b/apps/cic-ussd/cic_ussd/db/ussd_menu.json index a176f952..1419eacf 100644 --- a/apps/cic-ussd/cic_ussd/db/ussd_menu.json +++ b/apps/cic-ussd/cic_ussd/db/ussd_menu.json @@ -2,417 +2,441 @@ "ussd_menu": { "1": { "description": "Entry point for users to select their preferred language.", - "display_key": "ussd.kenya.initial_language_selection", + "display_key": "ussd.initial_language_selection", "name": "initial_language_selection", "parent": null }, "2": { "description": "Entry point for users to enter a pin to secure their account.", - "display_key": "ussd.kenya.initial_pin_entry", + "display_key": "ussd.initial_pin_entry", "name": "initial_pin_entry", "parent": null }, "3": { "description": "Pin confirmation entry menu.", - "display_key": "ussd.kenya.initial_pin_confirmation", + "display_key": "ussd.initial_pin_confirmation", "name": "initial_pin_confirmation", "parent": "initial_pin_entry" }, "4": { "description": "The signup process has been initiated and the account is being created.", - "display_key": "ussd.kenya.account_creation_prompt", + "display_key": "ussd.account_creation_prompt", "name": "account_creation_prompt", "parent": null }, "5": { "description": "Entry point for activated users.", - "display_key": "ussd.kenya.start", + "display_key": "ussd.start", "name": "start", "parent": null }, "6": { "description": "Given name entry menu.", - "display_key": "ussd.kenya.enter_given_name", + "display_key": "ussd.enter_given_name", "name": "enter_given_name", "parent": "metadata_management" }, "7": { "description": "Family name entry menu.", - "display_key": "ussd.kenya.enter_family_name", + "display_key": "ussd.enter_family_name", "name": "enter_family_name", "parent": "metadata_management" }, "8": { "description": "Gender entry menu.", - "display_key": "ussd.kenya.enter_gender", + "display_key": "ussd.enter_gender", "name": "enter_gender", "parent": "metadata_management" }, "9": { "description": "Age entry menu.", - "display_key": "ussd.kenya.enter_gender", + "display_key": "ussd.enter_gender", "name": "enter_gender", "parent": "metadata_management" }, "10": { "description": "Location entry menu.", - "display_key": "ussd.kenya.enter_location", + "display_key": "ussd.enter_location", "name": "enter_location", "parent": "metadata_management" }, "11": { "description": "Products entry menu.", - "display_key": "ussd.kenya.enter_products", + "display_key": "ussd.enter_products", "name": "enter_products", "parent": "metadata_management" }, "12": { "description": "Entry point for activated users.", - "display_key": "ussd.kenya.start", + "display_key": "ussd.start", "name": "start", "parent": null }, "13": { "description": "Send Token recipient entry.", - "display_key": "ussd.kenya.enter_transaction_recipient", + "display_key": "ussd.enter_transaction_recipient", "name": "enter_transaction_recipient", "parent": "start" }, "14": { "description": "Send Token amount prompt menu.", - "display_key": "ussd.kenya.enter_transaction_amount", + "display_key": "ussd.enter_transaction_amount", "name": "enter_transaction_amount", "parent": "start" }, "15": { "description": "Pin entry for authorization to send token.", - "display_key": "ussd.kenya.transaction_pin_authorization", + "display_key": "ussd.transaction_pin_authorization", "name": "transaction_pin_authorization", "parent": "start" }, "16": { "description": "Manage account menu.", - "display_key": "ussd.kenya.account_management", + "display_key": "ussd.account_management", "name": "account_management", "parent": "start" }, "17": { "description": "Manage metadata menu.", - "display_key": "ussd.kenya.metadata_management", + "display_key": "ussd.metadata_management", "name": "metadata_management", "parent": "start" }, "18": { "description": "Manage user's preferred language menu.", - "display_key": "ussd.kenya.select_preferred_language", + "display_key": "ussd.select_preferred_language", "name": "select_preferred_language", "parent": "account_management" }, "19": { "description": "Retrieve mini-statement menu.", - "display_key": "ussd.kenya.mini_statement_pin_authorization", + "display_key": "ussd.mini_statement_pin_authorization", "name": "mini_statement_pin_authorization", "parent": "account_management" }, "20": { "description": "Manage user's pin menu.", - "display_key": "ussd.kenya.enter_current_pin", + "display_key": "ussd.enter_current_pin", "name": "enter_current_pin", "parent": "account_management" }, "21": { "description": "New pin entry menu.", - "display_key": "ussd.kenya.enter_new_pin", + "display_key": "ussd.enter_new_pin", "name": "enter_new_pin", "parent": "account_management" }, "22": { "description": "Pin entry menu.", - "display_key": "ussd.kenya.display_metadata_pin_authorization", + "display_key": "ussd.display_metadata_pin_authorization", "name": "display_metadata_pin_authorization", "parent": "start" }, "23": { "description": "Exit menu.", - "display_key": "ussd.kenya.exit", + "display_key": "ussd.exit", "name": "exit", "parent": null }, "24": { "description": "Invalid menu option.", - "display_key": "ussd.kenya.exit_invalid_menu_option", + "display_key": "ussd.exit_invalid_menu_option", "name": "exit_invalid_menu_option", "parent": null }, "25": { "description": "Pin policy violation.", - "display_key": "ussd.kenya.exit_invalid_pin", + "display_key": "ussd.exit_invalid_pin", "name": "exit_invalid_pin", "parent": null }, "26": { "description": "Pin mismatch. New pin and the new pin confirmation do not match", - "display_key": "ussd.kenya.exit_pin_mismatch", + "display_key": "ussd.exit_pin_mismatch", "name": "exit_pin_mismatch", "parent": null }, "27": { "description": "Ussd pin blocked Menu", - "display_key": "ussd.kenya.exit_pin_blocked", + "display_key": "ussd.exit_pin_blocked", "name": "exit_pin_blocked", "parent": null }, "28": { "description": "Key params missing in request.", - "display_key": "ussd.kenya.exit_invalid_request", + "display_key": "ussd.exit_invalid_request", "name": "exit_invalid_request", "parent": null }, "29": { "description": "The user did not select a choice.", - "display_key": "ussd.kenya.exit_invalid_input", + "display_key": "ussd.exit_invalid_input", "name": "exit_invalid_input", "parent": null }, "30": { "description": "Exit following unsuccessful transaction due to insufficient account balance.", - "display_key": "ussd.kenya.exit_insufficient_balance", + "display_key": "ussd.exit_insufficient_balance", "name": "exit_insufficient_balance", "parent": null }, "31": { "description": "Exit following a successful transaction.", - "display_key": "ussd.kenya.exit_successful_transaction", + "display_key": "ussd.exit_successful_transaction", "name": "exit_successful_transaction", "parent": null }, "32": { "description": "End of a menu flow.", - "display_key": "ussd.kenya.complete", + "display_key": "ussd.complete", "name": "complete", "parent": null }, "33": { "description": "Pin entry menu to view account balances.", - "display_key": "ussd.kenya.account_balances_pin_authorization", + "display_key": "ussd.account_balances_pin_authorization", "name": "account_balances_pin_authorization", "parent": "account_management" }, "34": { "description": "Pin entry menu to view account statement.", - "display_key": "ussd.kenya.account_statement_pin_authorization", + "display_key": "ussd.account_statement_pin_authorization", "name": "account_statement_pin_authorization", "parent": "account_management" }, "35": { "description": "Menu to display account balances.", - "display_key": "ussd.kenya.account_balances", + "display_key": "ussd.account_balances", "name": "account_balances", "parent": "account_management" }, "36": { "description": "Menu to display first set of transactions in statement.", - "display_key": "ussd.kenya.first_transaction_set", + "display_key": "ussd.first_transaction_set", "name": "first_transaction_set", - "parent": null + "parent": "account_management" }, "37": { "description": "Menu to display middle set of transactions in statement.", - "display_key": "ussd.kenya.middle_transaction_set", + "display_key": "ussd.middle_transaction_set", "name": "middle_transaction_set", "parent": null }, "38": { "description": "Menu to display last set of transactions in statement.", - "display_key": "ussd.kenya.last_transaction_set", + "display_key": "ussd.last_transaction_set", "name": "last_transaction_set", "parent": null }, "39": { "description": "Menu to instruct users to call the office.", - "display_key": "ussd.kenya.help", + "display_key": "ussd.help", "name": "help", "parent": null }, "40": { "description": "Menu to display a user's entire profile", - "display_key": "ussd.kenya.display_user_metadata", + "display_key": "ussd.display_user_metadata", "name": "display_user_metadata", "parent": "metadata_management" }, "41": { "description": "The recipient is not in the system", - "display_key": "ussd.kenya.exit_invalid_recipient", + "display_key": "ussd.exit_invalid_recipient", "name": "exit_invalid_recipient", "parent": null }, "42": { "description": "Pin entry menu for changing name data.", - "display_key": "ussd.kenya.name_edit_pin_authorization", + "display_key": "ussd.name_edit_pin_authorization", "name": "name_edit_pin_authorization", "parent": "metadata_management" }, "43": { "description": "Pin entry menu for changing gender data.", - "display_key": "ussd.kenya.gender_edit_pin_authorization", + "display_key": "ussd.gender_edit_pin_authorization", "name": "gender_edit_pin_authorization", "parent": "metadata_management" }, "44": { "description": "Pin entry menu for changing location data.", - "display_key": "ussd.kenya.location_edit_pin_authorization", + "display_key": "ussd.location_edit_pin_authorization", "name": "location_edit_pin_authorization", "parent": "metadata_management" }, "45": { "description": "Pin entry menu for changing products data.", - "display_key": "ussd.kenya.products_edit_pin_authorization", + "display_key": "ussd.products_edit_pin_authorization", "name": "products_edit_pin_authorization", "parent": "metadata_management" }, "46": { "description": "Pin confirmation for pin change.", - "display_key": "ussd.kenya.new_pin_confirmation", + "display_key": "ussd.new_pin_confirmation", "name": "new_pin_confirmation", "parent": "metadata_management" }, "47": { "description": "Year of birth entry menu.", - "display_key": "ussd.kenya.enter_date_of_birth", + "display_key": "ussd.enter_date_of_birth", "name": "enter_date_of_birth", "parent": "metadata_management" }, "48": { "description": "Pin entry menu for changing year of birth data.", - "display_key": "ussd.kenya.dob_edit_pin_authorization", + "display_key": "ussd.dob_edit_pin_authorization", "name": "dob_edit_pin_authorization", "parent": "metadata_management" }, "49": { "description": "Menu to display first set of tokens in the account's token list.", - "display_key": "ussd.kenya.first_account_tokens_set", + "display_key": "ussd.first_account_tokens_set", "name": "first_account_tokens_set", - "parent": null + "parent": "start" }, "50": { "description": "Menu to display middle set of tokens in the account's token list.", - "display_key": "ussd.kenya.middle_account_tokens_set", + "display_key": "ussd.middle_account_tokens_set", "name": "middle_account_tokens_set", "parent": null }, "51": { "description": "Menu to display last set of tokens in the account's token list.", - "display_key": "ussd.kenya.last_account_tokens_set", + "display_key": "ussd.last_account_tokens_set", "name": "last_account_tokens_set", "parent": null }, "52": { "description": "Pin entry menu for setting an active token.", - "display_key": "ussd.kenya.token_selection_pin_authorization", + "display_key": "ussd.token_selection_pin_authorization", "name": "token_selection_pin_authorization", - "parent": null + "parent": "first_account_tokens_set" }, "53": { "description": "Exit following a successful active token setting.", - "display_key": "ussd.kenya.exit_successful_token_selection", + "display_key": "ussd.exit_successful_token_selection", "name": "exit_successful_token_selection", "parent": null }, "54": { "description": "Pin management menu for operations related to an account's pin.", - "display_key": "ussd.kenya.pin_management", + "display_key": "ussd.pin_management", "name": "pin_management", "parent": "start" }, "55": { "description": "Phone number entry for account whose pin is being reset.", - "display_key": "ussd.kenya.reset_guarded_pin", + "display_key": "ussd.reset_guarded_pin", "name": "reset_guarded_pin", "parent": "pin_management" }, "56": { "description": "Pin entry for initiating request to reset an account's pin.", - "display_key": "ussd.kenya.reset_guarded_pin_authorization", + "display_key": "ussd.reset_guarded_pin_authorization", "name": "reset_guarded_pin_authorization", "parent": "pin_management" }, "57": { "description": "Exit menu following successful pin reset initiation.", - "display_key": "ussd.kenya.exit_pin_reset_initiated_success", + "display_key": "ussd.exit_pin_reset_initiated_success", "name": "exit_pin_reset_initiated_success", "parent": "pin_management" }, "58": { "description": "Exit menu in the event that an account is not a set guardian.", - "display_key": "ussd.kenya.exit_not_authorized_for_pin_reset", + "display_key": "ussd.exit_not_authorized_for_pin_reset", "name": "exit_not_authorized_for_pin_reset", "parent": "pin_management" }, "59": { "description": "Pin guard menu for handling guardianship operations.", - "display_key": "ussd.kenya.guard_pin", + "display_key": "ussd.guard_pin", "name": "guard_pin", "parent": "pin_management" }, "60": { "description": "Pin entry to display a list of set guardians.", - "display_key": "ussd.kenya.guardian_list_pin_authorization", + "display_key": "ussd.guardian_list_pin_authorization", "name": "guardian_list_pin_authorization", "parent": "guard_pin" }, "61": { "description": "Menu to display list of set guardians.", - "display_key": "ussd.kenya.guardian_list", + "display_key": "ussd.guardian_list", "name": "guardian_list", "parent": "guard_pin" }, "62": { "description": "Phone number entry to add an account as a guardian to reset pin.", - "display_key": "ussd.kenya.add_guardian", + "display_key": "ussd.add_guardian", "name": "add_guardian", "parent": "guard_pin" }, "63": { "description": "Pin entry to confirm addition of an account as a guardian.", - "display_key": "ussd.kenya.add_guardian_pin_authorization", + "display_key": "ussd.add_guardian_pin_authorization", "name": "add_guardian_pin_authorization", "parent": "guard_pin" }, "64": { "description": "Exit menu when an account is successfully added as pin reset guardian.", - "display_key": "ussd.kenya.exit_guardian_addition_success", + "display_key": "ussd.exit_guardian_addition_success", "name": "exit_guardian_addition_success", "parent": "guard_pin" }, "65": { "description": "Phone number entry to remove an account as a guardian to reset pin.", - "display_key": "ussd.kenya.remove_guardian", + "display_key": "ussd.remove_guardian", "name": "remove_guardian", "parent": "guard_pin" }, "66": { "description": "Pin entry to confirm removal of an account as a guardian.", - "display_key": "ussd.kenya.remove_guardian_pin_authorization", + "display_key": "ussd.remove_guardian_pin_authorization", "name": "remove_guardian_pin_authorization", "parent": "guard_pin" }, "67": { "description": "Exit menu when an account is successfully removed as pin reset guardian.", - "display_key": "ussd.kenya.exit_guardian_removal_success", + "display_key": "ussd.exit_guardian_removal_success", "name": "exit_guardian_removal_success", "parent": "guard_pin" }, "68": { - "description": "Exit menu when invalid phone number entry for guardian addition. ", - "display_key": "ussd.kenya.exit_invalid_guardian_addition", + "description": "Exit menu when invalid phone number entry for guardian addition.", + "display_key": "ussd.exit_invalid_guardian_addition", "name": "exit_invalid_guardian_addition", "parent": "guard_pin" }, "69": { - "description": "Exit menu when invalid phone number entry for guardian removal. ", - "display_key": "ussd.kenya.exit_invalid_guardian_removal", + "description": "Exit menu when invalid phone number entry for guardian removal.", + "display_key": "ussd.exit_invalid_guardian_removal", "name": "exit_invalid_guardian_removal", "parent": "guard_pin" + }, + "70": { + "description": "Menu to display middle set of languages to select.", + "display_key": "ussd.initial_middle_language_set", + "name": "initial_middle_language_set", + "parent": null + }, + "71": { + "description": "Menu to display last set of languages to select.", + "display_key": "ussd.initial_last_language_set", + "name": "initial_last_language_set", + "parent": null + }, + "72": { + "description": "Menu to display middle set of languages to select.", + "display_key": "ussd.middle_language_set", + "name": "middle_language_set", + "parent": null + }, + "73": { + "description": "Menu to display last set of languages to select.", + "display_key": "ussd.last_language_set", + "name": "last_language_set", + "parent": null } } } \ No newline at end of file diff --git a/apps/cic-ussd/cic_ussd/processor/menu.py b/apps/cic-ussd/cic_ussd/processor/menu.py index 5346a35d..14101733 100644 --- a/apps/cic-ussd/cic_ussd/processor/menu.py +++ b/apps/cic-ussd/cic_ussd/processor/menu.py @@ -19,34 +19,33 @@ from cic_ussd.account.metadata import get_cached_preferred_language from cic_ussd.account.statement import ( get_cached_statement, parse_statement_transactions, - query_statement, - statement_transaction_set -) + query_statement) from cic_ussd.account.tokens import (create_account_tokens_list, get_active_token_symbol, get_cached_token_data, get_cached_token_symbol_list, get_cached_token_data_list, - parse_token_list, - token_list_set) + parse_token_list) from cic_ussd.account.transaction import from_wei, to_wei -from cic_ussd.cache import cache_data_key, cache_data +from cic_ussd.cache import cache_data_key, cache_data, get_cached_data from cic_ussd.db.models.account import Account from cic_ussd.metadata import PersonMetadata from cic_ussd.phone_number import Support -from cic_ussd.processor.util import parse_person_metadata +from cic_ussd.processor.util import parse_person_metadata, ussd_menu_list, wait_for_session_data from cic_ussd.session.ussd_session import save_session_data +from cic_ussd.state_machine.logic.language import preferred_langauge_from_selection from cic_ussd.translation import translation_for from sqlalchemy.orm.session import Session -logg = logging.getLogger(__name__) +logg = logging.getLogger(__file__) class MenuProcessor: def __init__(self, account: Account, display_key: str, menu_name: str, session: Session, ussd_session: dict): self.account = account self.display_key = display_key - self.identifier = bytes.fromhex(self.account.blockchain_address) + if account: + self.identifier = bytes.fromhex(self.account.blockchain_address) self.menu_name = menu_name self.session = session self.ussd_session = ussd_session @@ -89,36 +88,29 @@ class MenuProcessor: :rtype: """ cached_statement = get_cached_statement(self.account.blockchain_address) - transaction_sets = [] - if cached_statement: - statement = json.loads(cached_statement) - statement_transactions = parse_statement_transactions(statement) - transaction_sets = [statement_transactions[tx:tx + 3] for tx in range(0, len(statement_transactions), 3)] + preferred_language = get_cached_preferred_language(self.account.blockchain_address) if not preferred_language: preferred_language = i18n.config.get('fallback') - no_transaction_history = statement_transaction_set(preferred_language, transaction_sets) - first_transaction_set = no_transaction_history - middle_transaction_set = no_transaction_history - last_transaction_set = no_transaction_history - if transaction_sets: - first_transaction_set = statement_transaction_set(preferred_language, transaction_sets[0]) - if len(transaction_sets) >= 2: - middle_transaction_set = statement_transaction_set(preferred_language, transaction_sets[1]) - if len(transaction_sets) >= 3: - last_transaction_set = statement_transaction_set(preferred_language, transaction_sets[2]) - if self.display_key == 'ussd.kenya.first_transaction_set': + statement_list = [] + if cached_statement: + statement_list = parse_statement_transactions(statement=json.loads(cached_statement)) + + fallback = translation_for('helpers.no_transaction_history', preferred_language) + transaction_sets = ussd_menu_list(fallback=fallback, menu_list=statement_list, split=3) + + if self.display_key == 'ussd.first_transaction_set': return translation_for( - self.display_key, preferred_language, first_transaction_set=first_transaction_set + self.display_key, preferred_language, first_transaction_set=transaction_sets[0] ) - if self.display_key == 'ussd.kenya.middle_transaction_set': + if self.display_key == 'ussd.middle_transaction_set': return translation_for( - self.display_key, preferred_language, middle_transaction_set=middle_transaction_set + self.display_key, preferred_language, middle_transaction_set=transaction_sets[1] ) - if self.display_key == 'ussd.kenya.last_transaction_set': + if self.display_key == 'ussd.last_transaction_set': return translation_for( - self.display_key, preferred_language, last_transaction_set=last_transaction_set + self.display_key, preferred_language, last_transaction_set=transaction_sets[2] ) def add_guardian_pin_authorization(self): @@ -129,7 +121,7 @@ class MenuProcessor: preferred_language = get_cached_preferred_language(self.account.blockchain_address) if not preferred_language: preferred_language = i18n.config.get('fallback') - set_guardians = self.account.get_guardians() + set_guardians = self.account.get_guardians()[:3] if set_guardians: guardians_list = '' guardians_list_header = translation_for('helpers.guardians_list_header', preferred_language) @@ -145,36 +137,30 @@ class MenuProcessor: def account_tokens(self) -> str: cached_token_data_list = get_cached_token_data_list(self.account.blockchain_address) token_data_list = parse_token_list(cached_token_data_list) - token_list_sets = [token_data_list[tds:tds + 3] for tds in range(0, len(token_data_list), 3)] + preferred_language = get_cached_preferred_language(self.account.blockchain_address) if not preferred_language: preferred_language = i18n.config.get('fallback') - no_token_list = token_list_set(preferred_language, []) - first_account_tokens_set = no_token_list - middle_account_tokens_set = no_token_list - last_account_tokens_set = no_token_list - if token_list_sets: - data = { - 'account_tokens_list': cached_token_data_list - } - save_session_data(data=data, queue='cic-ussd', session=self.session, ussd_session=self.ussd_session) - first_account_tokens_set = token_list_set(preferred_language, token_list_sets[0]) - if len(token_list_sets) >= 2: - middle_account_tokens_set = token_list_set(preferred_language, token_list_sets[1]) - if len(token_list_sets) >= 3: - last_account_tokens_set = token_list_set(preferred_language, token_list_sets[2]) - if self.display_key == 'ussd.kenya.first_account_tokens_set': + fallback = translation_for('helpers.no_tokens_list', preferred_language) + token_list_sets = ussd_menu_list(fallback=fallback, menu_list=token_data_list, split=3) + + data = { + 'account_tokens_list': cached_token_data_list + } + save_session_data(data=data, queue='cic-ussd', session=self.session, ussd_session=self.ussd_session) + + if self.display_key == 'ussd.first_account_tokens_set': return translation_for( - self.display_key, preferred_language, first_account_tokens_set=first_account_tokens_set + self.display_key, preferred_language, first_account_tokens_set=token_list_sets[0] ) - if self.display_key == 'ussd.kenya.middle_account_tokens_set': + if self.display_key == 'ussd.middle_account_tokens_set': return translation_for( - self.display_key, preferred_language, middle_account_tokens_set=middle_account_tokens_set + self.display_key, preferred_language, middle_account_tokens_set=token_list_sets[1] ) - if self.display_key == 'ussd.kenya.last_account_tokens_set': + if self.display_key == 'ussd.last_account_tokens_set': return translation_for( - self.display_key, preferred_language, last_account_tokens_set=last_account_tokens_set + self.display_key, preferred_language, last_account_tokens_set=token_list_sets[2] ) def help(self) -> str: @@ -222,7 +208,7 @@ class MenuProcessor: remaining_attempts = 3 remaining_attempts -= self.account.failed_pin_attempts retry_pin_entry = translation_for( - 'ussd.kenya.retry_pin_entry', preferred_language, remaining_attempts=remaining_attempts + 'ussd.retry_pin_entry', preferred_language, remaining_attempts=remaining_attempts ) return translation_for( f'{self.display_key}.retry', preferred_language, retry_pin_entry=retry_pin_entry @@ -238,6 +224,38 @@ class MenuProcessor: guardian = Account.get_by_phone_number(guardian_phone_number, self.session) return guardian.standard_metadata_id() + def language(self): + key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE) + cached_system_languages = get_cached_data(key) + language_list: list = json.loads(cached_system_languages) + + if self.account: + preferred_language = get_cached_preferred_language(self.account.blockchain_address) + else: + preferred_language = i18n.config.get('fallback') + + fallback = translation_for('helpers.no_language_list', preferred_language) + language_list_sets = ussd_menu_list(fallback=fallback, menu_list=language_list, split=3) + + if self.display_key in ['ussd.initial_language_selection', 'ussd.select_preferred_language']: + return translation_for( + self.display_key, preferred_language, first_language_set=language_list_sets[0] + ) + + if 'middle_language_set' in self.display_key: + return translation_for( + self.display_key, preferred_language, middle_language_set=language_list_sets[1] + ) + + if 'last_language_set' in self.display_key: + return translation_for( + self.display_key, preferred_language, last_language_set=language_list_sets[2] + ) + + def account_creation_prompt(self): + preferred_language = preferred_langauge_from_selection(self.ussd_session.get('user_input')) + return translation_for(self.display_key, preferred_language) + def reset_guarded_pin_authorization(self): guarded_account_information = self.guarded_account_metadata() return self.pin_authorization(guarded_account_information=guarded_account_information) @@ -381,8 +399,9 @@ class MenuProcessor: ) def exit_invalid_menu_option(self): - preferred_language = get_cached_preferred_language(self.account.blockchain_address) - if not preferred_language: + if self.account: + preferred_language = get_cached_preferred_language(self.account.blockchain_address) + else: preferred_language = i18n.config.get('fallback') return translation_for(self.display_key, preferred_language, support_phone=Support.phone_number) @@ -390,7 +409,7 @@ class MenuProcessor: preferred_language = get_cached_preferred_language(self.account.blockchain_address) if not preferred_language: preferred_language = i18n.config.get('fallback') - return translation_for('ussd.kenya.exit_pin_blocked', preferred_language, support_phone=Support.phone_number) + return translation_for('ussd.exit_pin_blocked', preferred_language, support_phone=Support.phone_number) def exit_successful_token_selection(self) -> str: selected_token = self.ussd_session.get('data').get('selected_token') @@ -445,6 +464,9 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio """ menu_processor = MenuProcessor(account, display_key, menu_name, session, ussd_session) + if menu_name == 'account_creation_prompt': + return menu_processor.account_creation_prompt() + if menu_name == 'start': return menu_processor.start_menu() @@ -502,6 +524,9 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio if 'account_tokens_set' in menu_name: return menu_processor.account_tokens() + if 'language' in menu_name: + return menu_processor.language() + if menu_name == 'display_user_metadata': return menu_processor.person_metadata() @@ -515,5 +540,4 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio return menu_processor.exit_successful_token_selection() preferred_language = get_cached_preferred_language(account.blockchain_address) - return translation_for(display_key, preferred_language) diff --git a/apps/cic-ussd/cic_ussd/processor/ussd.py b/apps/cic-ussd/cic_ussd/processor/ussd.py index 8e383d9b..a07bf4df 100644 --- a/apps/cic-ussd/cic_ussd/processor/ussd.py +++ b/apps/cic-ussd/cic_ussd/processor/ussd.py @@ -8,7 +8,7 @@ from sqlalchemy.orm.session import Session from tinydb.table import Document # local imports -from cic_ussd.db.models.account import Account, create +from cic_ussd.db.models.account import Account from cic_ussd.db.models.base import SessionBase from cic_ussd.db.models.ussd_session import UssdSession from cic_ussd.menu.ussd_menu import UssdMenu @@ -16,7 +16,6 @@ from cic_ussd.processor.menu import response from cic_ussd.processor.util import latest_input, resume_last_ussd_session from cic_ussd.session.ussd_session import create_or_update_session, persist_ussd_session from cic_ussd.state_machine import UssdStateMachine -from cic_ussd.translation import translation_for from cic_ussd.validator import is_valid_response @@ -36,9 +35,6 @@ def handle_menu(account: Account, session: Session) -> Document: last_ussd_session = UssdSession.last_ussd_session(account.phone_number, session) if last_ussd_session: return resume_last_ussd_session(last_ussd_session.state) - - elif not account.has_preferred_language(): - return UssdMenu.find_by_name('initial_language_selection') else: return UssdMenu.find_by_name('initial_pin_entry') @@ -71,16 +67,13 @@ def get_menu(account: Account, return UssdMenu.find_by_name(state) -def handle_menu_operations(chain_str: str, - external_session_id: str, +def handle_menu_operations(external_session_id: str, phone_number: str, queue: str, service_code: str, session, user_input: str): """ - :param chain_str: - :type chain_str: :param external_session_id: :type external_session_id: :param phone_number: @@ -100,10 +93,38 @@ def handle_menu_operations(chain_str: str, account: Account = Account.get_by_phone_number(phone_number, session) if account: return handle_account_menu_operations(account, external_session_id, queue, session, service_code, user_input) - create(chain_str, phone_number, session) - menu = UssdMenu.find_by_name('account_creation_prompt') - preferred_language = i18n.config.get('fallback') - create_or_update_session( + else: + return handle_no_account_menu_operations( + account, external_session_id, phone_number, queue, session, service_code, user_input) + + +def handle_no_account_menu_operations(account: Optional[Account], + external_session_id: str, + phone_number: str, + queue: str, + session: Session, + service_code: str, + user_input: str): + """ + :param account: + :type account: + :param external_session_id: + :type external_session_id: + :param phone_number: + :type phone_number: + :param queue: + :type queue: + :param session: + :type session: + :param service_code: + :type service_code: + :param user_input: + :type user_input: + :return: + :rtype: + """ + menu = UssdMenu.find_by_name('initial_language_selection') + ussd_session = create_or_update_session( external_session_id=external_session_id, msisdn=phone_number, service_code=service_code, @@ -111,7 +132,20 @@ def handle_menu_operations(chain_str: str, session=session, user_input=user_input) persist_ussd_session(external_session_id, queue) - return translation_for('ussd.kenya.account_creation_prompt', preferred_language) + last_ussd_session: UssdSession = UssdSession.last_ussd_session(phone_number, session) + if last_ussd_session: + if not user_input: + menu = resume_last_ussd_session(last_ussd_session.state) + else: + session = SessionBase.bind_session(session) + state = next_state(account, session, user_input, last_ussd_session.to_json()) + menu = UssdMenu.find_by_name(state) + + return response(account=account, + display_key=menu.get('display_key'), + menu_name=menu.get('name'), + session=session, + ussd_session=ussd_session.to_json()) def handle_account_menu_operations(account: Account, @@ -152,15 +186,12 @@ def handle_account_menu_operations(account: Account, if last_ussd_session: ussd_session = create_or_update_session( external_session_id, phone_number, service_code, user_input, menu.get('name'), session, - last_ussd_session.data - ) + last_ussd_session.data) else: ussd_session = create_or_update_session( - external_session_id, phone_number, service_code, user_input, menu.get('name'), session, None - ) + external_session_id, phone_number, service_code, user_input, menu.get('name'), session, {}) menu_response = response( - account, menu.get('display_key'), menu.get('name'), session, ussd_session.to_json() - ) + account, menu.get('display_key'), menu.get('name'), session, ussd_session.to_json()) if not is_valid_response(menu_response): raise ValueError(f'Invalid response: {response}') persist_ussd_session(external_session_id, queue) diff --git a/apps/cic-ussd/cic_ussd/processor/util.py b/apps/cic-ussd/cic_ussd/processor/util.py index b2baf261..28c50c63 100644 --- a/apps/cic-ussd/cic_ussd/processor/util.py +++ b/apps/cic-ussd/cic_ussd/processor/util.py @@ -3,7 +3,7 @@ import datetime import json import logging import time -from typing import Union +from typing import List, Union # external imports from cic_types.condiments import MetadataPointer @@ -21,9 +21,7 @@ logg = logging.getLogger(__file__) def latest_input(user_input: str) -> str: """ :param user_input: - :type user_input: :return: - :rtype: """ return user_input.split('*')[-1] @@ -85,6 +83,27 @@ def resume_last_ussd_session(last_state: str) -> Document: return UssdMenu.find_by_name(last_state) +def ussd_menu_list(fallback: str, menu_list: list, split: int = 3) -> List[str]: + """ + :param fallback: + :type fallback: + :param menu_list: + :type menu_list: + :param split: + :type split: + :return: + :rtype: + """ + menu_list_sets = [menu_list[item:item + split] for item in range(0, len(menu_list), split)] + menu_list_reprs = [] + for i in range(split): + try: + menu_list_reprs.append(''.join(f'{list_set_item}\n' for list_set_item in menu_list_sets[i]).rstrip('\n')) + except IndexError: + menu_list_reprs.append(fallback) + return menu_list_reprs + + def wait_for_cache(identifier: Union[list, bytes], resource_name: str, salt: MetadataPointer, interval: int = 1, max_retry: int = 5): """ :param identifier: @@ -132,17 +151,28 @@ def wait_for_session_data(resource_name: str, session_data_key: str, ussd_sessio :return: :rtype: """ - session_data = ussd_session.get('data').get(session_data_key) - counter = 0 - while session_data is None: - logg.debug(f'Waiting for: {resource_name}. Checking after: {interval} ...') + data = ussd_session.get('data') + data_poller = 0 + while not data: + logg.debug(f'Waiting for data object on ussd session: {ussd_session.get("external_session_id")}') + logg.debug(f'Data poller at: {data_poller}. Checking again after: {interval} secs...') time.sleep(interval) - counter += 1 - session_data = ussd_session.get('data').get(session_data_key) - if session_data is not None: - logg.debug(f'{resource_name} now available.') + data_poller += 1 + if data: + logg.debug(f'Data object found, proceeding to poll for: {session_data_key}') break - else: - if counter == max_retry: - logg.debug(f'Could not find: {resource_name} within: {max_retry}') + if data: + session_data_poller = 0 + session_data = data.get(session_data_key) + while not session_data_key: + logg.debug( + f'Session data poller at: {data_poller} with max retry at: {max_retry}. Checking again after: {interval} secs...') + time.sleep(interval) + session_data_poller += 1 + + if session_data: + logg.debug(f'{resource_name} now available.') break + + elif session_data_poller >= max_retry: + logg.debug(f'Could not find data object within: {max_retry}') diff --git a/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_tasker.py b/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_tasker.py index 14c577c1..95ff193b 100644 --- a/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_tasker.py +++ b/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_tasker.py @@ -20,6 +20,7 @@ from cic_ussd.db import dsn_from_config from cic_ussd.db.models.base import SessionBase from cic_ussd.phone_number import Support from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession +from cic_ussd.translation import generate_locale_files from cic_ussd.validator import validate_presence logging.basicConfig(level=logging.WARNING) @@ -83,6 +84,10 @@ if key_file_path: validate_presence(path=key_file_path) Signer.key_file_path = key_file_path +generate_locale_files(locale_dir=config.get('LOCALE_PATH'), + schema_file_path=config.get('SCHEMA_FILE_PATH'), + translation_builder_path=config.get('LOCALE_FILE_BUILDERS')) + # set up translations i18n.load_path.append(config.get('LOCALE_PATH')) i18n.set('fallback', config.get('LOCALE_FALLBACK')) diff --git a/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_ussd_server.py b/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_ussd_server.py index 8e3f4268..6a53efbc 100644 --- a/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_ussd_server.py +++ b/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_ussd_server.py @@ -18,6 +18,7 @@ from cic_types.ext.metadata.signer import Signer # local imports from cic_ussd.account.chain import Chain +from cic_ussd.account.guardianship import Guardianship from cic_ussd.account.tokens import query_default_token from cic_ussd.cache import cache_data, cache_data_key, Cache from cic_ussd.db import dsn_from_config @@ -33,7 +34,7 @@ from cic_ussd.processor.ussd import handle_menu_operations from cic_ussd.runnable.server_base import exportable_parser, logg from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession from cic_ussd.state_machine import UssdStateMachine -from cic_ussd.translation import translation_for +from cic_ussd.translation import generate_locale_files, Languages, translation_for from cic_ussd.validator import check_ip, check_request_content_length, validate_phone_number, validate_presence args = exportable_parser.parse_args() @@ -56,10 +57,6 @@ SessionBase.connect(data_source_name, pool_size=int(config.get('DATABASE_POOL_SIZE')), debug=config.true('DATABASE_DEBUG')) -# set up translations -i18n.load_path.append(config.get('LOCALE_PATH')) -i18n.set('fallback', config.get('LOCALE_FALLBACK')) - # set Fernet key PasswordEncoder.set_key(config.get('APP_PASSWORD_PEPPER')) @@ -121,6 +118,22 @@ valid_service_codes = config.get('USSD_SERVICE_CODE').split(",") E164Format.region = config.get('E164_REGION') Support.phone_number = config.get('OFFICE_SUPPORT_PHONE') +validate_presence(config.get('SYSTEM_GUARDIANS_FILE')) +Guardianship.load_system_guardians(config.get('SYSTEM_GUARDIANS_FILE')) + +generate_locale_files(locale_dir=config.get('LOCALE_PATH'), + schema_file_path=config.get('SCHEMA_FILE_PATH'), + translation_builder_path=config.get('LOCALE_FILE_BUILDERS')) + +# set up translations +i18n.load_path.append(config.get('LOCALE_PATH')) +i18n.set('fallback', config.get('LOCALE_FALLBACK')) + +validate_presence(config.get('LANGUAGES_FILE')) +Languages.load_languages_dict(config.get('LANGUAGES_FILE')) +languages = Languages() +languages.cache_system_languages() + def application(env, start_response): """Loads python code for application to be accessible over web server @@ -175,7 +188,7 @@ def application(env, start_response): if service_code not in valid_service_codes: response = translation_for( - 'ussd.kenya.invalid_service_code', + 'ussd.invalid_service_code', i18n.config.get('fallback'), valid_service_code=valid_service_codes[0] ) @@ -189,9 +202,7 @@ def application(env, start_response): return [] logg.debug('session {} started for {}'.format(external_session_id, phone_number)) - response = handle_menu_operations( - chain_str, external_session_id, phone_number, args.q, service_code, session, user_input - ) + response = handle_menu_operations(external_session_id, phone_number, args.q, service_code, session, user_input) response_bytes, headers = with_content_headers(headers, response) start_response('200 OK,', headers) session.commit() diff --git a/apps/cic-ussd/cic_ussd/state_machine/logic/account.py b/apps/cic-ussd/cic_ussd/state_machine/logic/account.py index 13bc13c0..53e64882 100644 --- a/apps/cic-ussd/cic_ussd/state_machine/logic/account.py +++ b/apps/cic-ussd/cic_ussd/state_machine/logic/account.py @@ -11,46 +11,20 @@ from cic_types.models.person import get_contact_data_from_vcard, generate_vcard_ # local imports from cic_ussd.account.chain import Chain -from cic_ussd.account.maps import gender, language +from cic_ussd.account.maps import gender from cic_ussd.account.metadata import get_cached_preferred_language -from cic_ussd.db.models.account import Account +from cic_ussd.db.models.account import Account, create from cic_ussd.db.models.base import SessionBase from cic_ussd.error import MetadataNotFoundError from cic_ussd.metadata import PersonMetadata from cic_ussd.session.ussd_session import save_session_data +from cic_ussd.state_machine.logic.language import preferred_langauge_from_selection from cic_ussd.translation import translation_for from sqlalchemy.orm.session import Session logg = logging.getLogger(__file__) -def change_preferred_language(state_machine_data: Tuple[str, dict, Account, Session]): - """ - :param state_machine_data: - :type state_machine_data: - :return: - :rtype: - """ - user_input, ussd_session, account, session = state_machine_data - r_user_input = language().get(user_input) - session = SessionBase.bind_session(session) - account.preferred_language = r_user_input - session.add(account) - session.flush() - SessionBase.release_session(session) - - preferences_data = { - 'preferred_language': r_user_input - } - - s = celery.signature( - 'cic_ussd.tasks.metadata.add_preferences_metadata', - [account.blockchain_address, preferences_data], - queue='cic-ussd' - ) - return s.apply_async() - - def update_account_status_to_active(state_machine_data: Tuple[str, dict, Account, Session]): """This function sets user's account to active. :param state_machine_data: A tuple containing user input, a ussd session and user object. @@ -245,3 +219,16 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account, S [blockchain_address, parsed_person_metadata] ) s_edit_person_metadata.apply_async(queue='cic-ussd') + + +def process_account_creation(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + preferred_language = preferred_langauge_from_selection(user_input=user_input) + chain_str = Chain.spec.__str__() + create(chain_str, ussd_session.get('msisdn'), session, preferred_language) diff --git a/apps/cic-ussd/cic_ussd/state_machine/logic/language.py b/apps/cic-ussd/cic_ussd/state_machine/logic/language.py new file mode 100644 index 00000000..0fb66ec1 --- /dev/null +++ b/apps/cic-ussd/cic_ussd/state_machine/logic/language.py @@ -0,0 +1,95 @@ +# standard imports +import json +from typing import Tuple + +# external imports +import celery +import i18n +from cic_types.condiments import MetadataPointer +from sqlalchemy.orm.session import Session + +# local imports +from cic_ussd.cache import cache_data_key, get_cached_data +from cic_ussd.db.models.account import Account +from cic_ussd.processor.util import wait_for_cache, wait_for_session_data +from cic_ussd.session.ussd_session import save_session_data +from cic_ussd.translation import Languages + + +def is_valid_language_selection(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + + key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE) + cached_system_languages = get_cached_data(key) + language_list = json.loads(cached_system_languages) + + if not language_list: + wait_for_cache(identifier='system:languages'.encode('utf-8'), resource_name='Languages list', salt=MetadataPointer.NONE) + + if user_input in ['00', '11', '22']: + return False + user_input = int(user_input) + return user_input <= len(language_list) + + +def change_preferred_language(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + process_language_selection(state_machine_data=state_machine_data) + user_input, ussd_session, account, session = state_machine_data + wait_for_session_data(resource_name='Preferred language', session_data_key='preferred_language', ussd_session=ussd_session) + preferred_language = ussd_session.get('data').get('preferred_language') + preferences_data = { + 'preferred_language': preferred_language + } + + s = celery.signature( + 'cic_ussd.tasks.metadata.add_preferences_metadata', + [account.blockchain_address, preferences_data], + queue='cic-ussd' + ) + return s.apply_async() + + +def process_language_selection(state_machine_data: Tuple[str, dict, Account, Session]): + """ + :param state_machine_data: + :type state_machine_data: + :return: + :rtype: + """ + user_input, ussd_session, account, session = state_machine_data + preferred_language = preferred_langauge_from_selection(user_input=user_input) + data = { + 'preferred_language': preferred_language + } + save_session_data(queue='cic-ussd', session=session, data=data, ussd_session=ussd_session) + + +def preferred_langauge_from_selection(user_input: str): + """ + :param user_input: + :type user_input: + :return: + :rtype: + """ + key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE) + cached_system_languages = get_cached_data(key) + language_list = json.loads(cached_system_languages) + user_input = int(user_input) + selected_language = language_list[user_input - 1] + preferred_language = i18n.config.get('fallback') + for key, value in Languages.languages_dict.items(): + if selected_language[3:] == value: + preferred_language = key + return preferred_language diff --git a/apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py b/apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py index 4e87e473..a1e3d0da 100644 --- a/apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py +++ b/apps/cic-ussd/cic_ussd/state_machine/logic/pin_guard.py @@ -9,9 +9,11 @@ from phonenumbers.phonenumberutil import NumberParseException from sqlalchemy.orm.session import Session # local imports +from cic_ussd.account.guardianship import Guardianship from cic_ussd.account.metadata import get_cached_preferred_language from cic_ussd.db.models.account import Account from cic_ussd.db.models.base import SessionBase +from cic_ussd.notifications import Notifier from cic_ussd.phone_number import process_phone_number, E164Format from cic_ussd.session.ussd_session import save_session_data from cic_ussd.translation import translation_for @@ -82,6 +84,8 @@ def is_valid_guardian_addition(state_machine_data: Tuple[str, dict, Account, Ses preferred_language = i18n.config.get('fallback') is_valid_account = Account.get_by_phone_number(phone_number, session) is not None + guardianship = Guardianship() + is_system_guardian = guardianship.is_system_guardian(phone_number) is_initiator = phone_number == account.phone_number is_existent_guardian = phone_number in account.get_guardians() @@ -100,7 +104,7 @@ def is_valid_guardian_addition(state_machine_data: Tuple[str, dict, Account, Ses session_data['failure_reason'] = failure_reason save_session_data('cic-ussd', session, session_data, ussd_session) - return phone_number is not None and is_valid_account and not is_existent_guardian and not is_initiator + return phone_number is not None and is_valid_account and not is_existent_guardian and not is_initiator and not is_system_guardian def add_pin_guardian(state_machine_data: Tuple[str, dict, Account, Session]): @@ -130,6 +134,9 @@ def is_set_pin_guardian(account: Account, checked_number: str, preferred_languag is_set_guardian = checked_number in set_guardians is_initiator = checked_number == account.phone_number + guardianship = Guardianship() + is_system_guardian = guardianship.is_system_guardian(checked_number) + if not is_set_guardian: failure_reason = translation_for('helpers.error.is_not_existent_guardian', preferred_language) @@ -141,7 +148,7 @@ def is_set_pin_guardian(account: Account, checked_number: str, preferred_languag session_data['failure_reason'] = failure_reason save_session_data('cic-ussd', session, session_data, ussd_session) - return is_set_guardian and not is_initiator + return (is_set_guardian or is_system_guardian) and not is_initiator def is_dialers_pin_guardian(state_machine_data: Tuple[str, dict, Account, Session]): @@ -193,8 +200,20 @@ def initiate_pin_reset(state_machine_data: Tuple[str, dict, Account, Session]): save_session_data('cic-ussd', session, session_data, ussd_session) guarded_account_phone_number = session_data.get('guarded_account_phone_number') guarded_account = Account.get_by_phone_number(guarded_account_phone_number, session) + if quorum_count >= guarded_account.guardian_quora: guarded_account.reset_pin(session) logg.debug(f'Reset initiated for: {guarded_account.phone_number}') session_data['quorum_count'] = 0 save_session_data('cic-ussd', session, session_data, ussd_session) + + preferred_language = get_cached_preferred_language(guarded_account.blockchain_address) + if not preferred_language: + preferred_language = i18n.config.get('fallback') + + notifier = Notifier() + notifier.send_sms_notification( + key='sms.pin_reset_initiated', + phone_number=guarded_account.phone_number, + preferred_language=preferred_language, + pin_initiator=account.standard_metadata_id()) diff --git a/apps/cic-ussd/cic_ussd/state_machine/logic/tokens.py b/apps/cic-ussd/cic_ussd/state_machine/logic/tokens.py index 7f4c2398..f8887813 100644 --- a/apps/cic-ussd/cic_ussd/state_machine/logic/tokens.py +++ b/apps/cic-ussd/cic_ussd/state_machine/logic/tokens.py @@ -23,7 +23,7 @@ def is_valid_token_selection(state_machine_data: Tuple[str, dict, Account, Sessi account_tokens_list = session_data.get('account_tokens_list') if not account_tokens_list: wait_for_session_data('Account token list', session_data_key='account_tokens_list', ussd_session=ussd_session) - if user_input not in ['00', '22']: + if user_input not in ['00', '11', '22']: try: user_input = int(user_input) return user_input <= len(account_tokens_list) diff --git a/apps/cic-ussd/cic_ussd/tasks/callback_handler.py b/apps/cic-ussd/cic_ussd/tasks/callback_handler.py index 08bd56fa..827e1b46 100644 --- a/apps/cic-ussd/cic_ussd/tasks/callback_handler.py +++ b/apps/cic-ussd/cic_ussd/tasks/callback_handler.py @@ -32,14 +32,14 @@ celery_app = celery.current_app @celery_app.task(bind=True, base=CriticalSQLAlchemyTask) -def account_creation_callback(self, result: str, url: str, status_code: int): +def account_creation_callback(self, result: str, param: str, status_code: int): """This function defines a task that creates a user and :param self: Reference providing access to the callback task instance. :type self: celery.Task :param result: The blockchain address for the created account :type result: str - :param url: URL provided to callback task in cic-eth should http be used for callback. - :type url: str + :param param: URL provided to callback task in cic-eth should http be used for callback. + :type param: str :param status_code: The status of the task to create an account :type status_code: int """ @@ -69,6 +69,15 @@ def account_creation_callback(self, result: str, url: str, status_code: int): set_active_token(blockchain_address=result, token_symbol=token_symbol) queue = self.request.delivery_info.get('routing_key') + preferences_data = {"preferred_language": param} + # temporarily caching selected language + key = cache_data_key(bytes.fromhex(result), MetadataPointer.PREFERENCES) + cache_data(key, json.dumps(preferences_data)) + s_preferences_metadata = celery.signature( + 'cic_ussd.tasks.metadata.add_preferences_metadata', [result, preferences_data], queue=queue + ) + s_preferences_metadata.apply_async() + s_phone_pointer = celery.signature( 'cic_ussd.tasks.metadata.add_phone_pointer', [result, phone_number], queue=queue ) diff --git a/apps/cic-ussd/cic_ussd/translation.py b/apps/cic-ussd/cic_ussd/translation.py index e512a5b3..b85edacc 100644 --- a/apps/cic-ussd/cic_ussd/translation.py +++ b/apps/cic-ussd/cic_ussd/translation.py @@ -1,9 +1,56 @@ """ This module is responsible for translation of ussd menu text based on a user's set preferred language. """ +# standard imports +import json + import i18n +import os +from pathlib import Path from typing import Optional +# external imports +from cic_translations.processor import generate_translation_files, parse_csv +from cic_types.condiments import MetadataPointer + +# local imports +from cic_ussd.cache import cache_data, cache_data_key +from cic_ussd.validator import validate_presence + + +def generate_locale_files(locale_dir: str, schema_file_path: str, translation_builder_path: str): + """""" + translation_builder_files = os.listdir(translation_builder_path) + for file in translation_builder_files: + props = Path(file) + if props.suffix == '.csv': + parsed_csv = parse_csv(os.path.join(translation_builder_path, file)) + generate_translation_files( + parsed_csv=parsed_csv, + schema_file_path=schema_file_path, + translation_file_type=props.stem, + translation_file_path=locale_dir + ) + + +class Languages: + languages_dict: dict = None + + @classmethod + def load_languages_dict(cls, languages_file: str): + with open(languages_file, "r") as languages_file: + cls.languages_dict = json.load(languages_file) + + def cache_system_languages(self): + system_languages: list = list(self.languages_dict.values()) + languages_list = [] + for i in range(len(system_languages)): + language = f'{i + 1}. {system_languages[i]}' + languages_list.append(language) + + key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE) + cache_data(key, json.dumps(languages_list)) + def translation_for(key: str, preferred_language: Optional[str] = None, **kwargs) -> str: """ diff --git a/apps/cic-ussd/config/app.ini b/apps/cic-ussd/config/app.ini index 4c5e19db..ebd5613c 100644 --- a/apps/cic-ussd/config/app.ini +++ b/apps/cic-ussd/config/app.ini @@ -11,3 +11,6 @@ transitions=transitions/ host = port = ssl = + +[system] +guardians_file = var/lib/sys/guardians.txt diff --git a/apps/cic-ussd/config/translations.ini b/apps/cic-ussd/config/translations.ini index f32c64ed..5cf2ba19 100644 --- a/apps/cic-ussd/config/translations.ini +++ b/apps/cic-ussd/config/translations.ini @@ -1,3 +1,10 @@ [locale] fallback=sw path=var/lib/locale/ +file_builders=var/lib/sys/ + +[schema] +file_path = /usr/local/lib/python3.8/site-packages/cic_translations/data/schema + +[languages] +file = var/lib/sys/languages.json diff --git a/apps/cic-ussd/requirements.txt b/apps/cic-ussd/requirements.txt index a7254139..83b70b5c 100644 --- a/apps/cic-ussd/requirements.txt +++ b/apps/cic-ussd/requirements.txt @@ -4,8 +4,9 @@ billiard==3.6.4.0 bcrypt==3.2.0 celery==4.4.7 cffi==1.14.6 -cic-eth~=0.12.6 +cic-eth~=0.12.7 cic-notify~=0.4.0a12 +cic-translations~=0.0.3 cic-types~=0.2.1a8 confini~=0.5.2 cic-eth-aux-erc20-demurrage-token~=0.0.3 diff --git a/apps/cic-ussd/states/account_management_states.json b/apps/cic-ussd/states/account_management_states.json index ea9027a2..de71959a 100644 --- a/apps/cic-ussd/states/account_management_states.json +++ b/apps/cic-ussd/states/account_management_states.json @@ -13,5 +13,7 @@ "products_edit_pin_authorization", "account_balances_pin_authorization", "account_statement_pin_authorization", - "account_balances" + "account_balances", + "middle_language_set", + "last_language_set" ] \ No newline at end of file diff --git a/apps/cic-ussd/states/signup_states.json b/apps/cic-ussd/states/signup_states.json index 1e6722d2..cd1a5732 100644 --- a/apps/cic-ussd/states/signup_states.json +++ b/apps/cic-ussd/states/signup_states.json @@ -2,6 +2,8 @@ "start", "scan_data", "initial_language_selection", + "initial_middle_language_set", + "initial_last_language_set", "initial_pin_entry", "initial_pin_confirmation", "change_preferred_language" diff --git a/apps/cic-ussd/tests/cic_ussd/processor/test_menu.py b/apps/cic-ussd/tests/cic_ussd/processor/test_menu.py index f5ec7f19..75b695a1 100644 --- a/apps/cic-ussd/tests/cic_ussd/processor/test_menu.py +++ b/apps/cic-ussd/tests/cic_ussd/processor/test_menu.py @@ -47,11 +47,11 @@ def test_menu_processor(activated_account, preferred_language = get_cached_preferred_language(activated_account.blockchain_address) available_balance = get_cached_available_balance(activated_account.blockchain_address) token_symbol = get_default_token_symbol() - with_available_balance = 'ussd.kenya.account_balances.available_balance' - with_fees = 'ussd.kenya.account_balances.with_fees' + with_available_balance = 'ussd.account_balances.available_balance' + with_fees = 'ussd.account_balances.with_fees' ussd_menu = UssdMenu.find_by_name('account_balances') name = ussd_menu.get('name') - resp = response(activated_account, 'ussd.kenya.account_balances', name, init_database, generic_ussd_session) + resp = response(activated_account, 'ussd.account_balances', name, init_database, generic_ussd_session) assert resp == translation_for(with_available_balance, preferred_language, available_balance=available_balance, @@ -61,7 +61,7 @@ def test_menu_processor(activated_account, key = cache_data_key(identifier, MetadataPointer.BALANCES_ADJUSTED) adjusted_balance = 45931650.64654012 cache_data(key, json.dumps(adjusted_balance)) - resp = response(activated_account, 'ussd.kenya.account_balances', name, init_database, generic_ussd_session) + resp = response(activated_account, 'ussd.account_balances', name, init_database, generic_ussd_session) tax_wei = to_wei(int(available_balance)) - int(adjusted_balance) tax = from_wei(int(tax_wei)) assert resp == translation_for(key=with_fees, @@ -84,28 +84,28 @@ def test_menu_processor(activated_account, if len(transaction_sets) >= 3: last_transaction_set = statement_transaction_set(preferred_language, transaction_sets[2]) - display_key = 'ussd.kenya.first_transaction_set' + display_key = 'ussd.first_transaction_set' ussd_menu = UssdMenu.find_by_name('first_transaction_set') name = ussd_menu.get('name') resp = response(activated_account, display_key, name, init_database, generic_ussd_session) assert resp == translation_for(display_key, preferred_language, first_transaction_set=first_transaction_set) - display_key = 'ussd.kenya.middle_transaction_set' + display_key = 'ussd.middle_transaction_set' ussd_menu = UssdMenu.find_by_name('middle_transaction_set') name = ussd_menu.get('name') resp = response(activated_account, display_key, name, init_database, generic_ussd_session) assert resp == translation_for(display_key, preferred_language, middle_transaction_set=middle_transaction_set) - display_key = 'ussd.kenya.last_transaction_set' + display_key = 'ussd.last_transaction_set' ussd_menu = UssdMenu.find_by_name('last_transaction_set') name = ussd_menu.get('name') resp = response(activated_account, display_key, name, init_database, generic_ussd_session) assert resp == translation_for(display_key, preferred_language, last_transaction_set=last_transaction_set) - display_key = 'ussd.kenya.display_user_metadata' + display_key = 'ussd.display_user_metadata' ussd_menu = UssdMenu.find_by_name('display_user_metadata') name = ussd_menu.get('name') identifier = bytes.fromhex(activated_account.blockchain_address) @@ -114,7 +114,7 @@ def test_menu_processor(activated_account, resp = response(activated_account, display_key, name, init_database, generic_ussd_session) assert resp == parse_person_metadata(cached_person_metadata, display_key, preferred_language) - display_key = 'ussd.kenya.account_balances_pin_authorization' + display_key = 'ussd.account_balances_pin_authorization' ussd_menu = UssdMenu.find_by_name('account_balances_pin_authorization') name = ussd_menu.get('name') resp = response(activated_account, display_key, name, init_database, generic_ussd_session) @@ -122,11 +122,11 @@ def test_menu_processor(activated_account, activated_account.failed_pin_attempts = 1 resp = response(activated_account, display_key, name, init_database, generic_ussd_session) - retry_pin_entry = translation_for('ussd.kenya.retry_pin_entry', preferred_language, remaining_attempts=2) + retry_pin_entry = translation_for('ussd.retry_pin_entry', preferred_language, remaining_attempts=2) assert resp == translation_for(f'{display_key}.retry', preferred_language, retry_pin_entry=retry_pin_entry) activated_account.failed_pin_attempts = 0 - display_key = 'ussd.kenya.start' + display_key = 'ussd.start' ussd_menu = UssdMenu.find_by_name('start') name = ussd_menu.get('name') resp = response(activated_account, display_key, name, init_database, generic_ussd_session) @@ -135,7 +135,7 @@ def test_menu_processor(activated_account, account_balance=available_balance, account_token_name=token_symbol) - display_key = 'ussd.kenya.start' + display_key = 'ussd.start' ussd_menu = UssdMenu.find_by_name('start') name = ussd_menu.get('name') older_timestamp = (activated_account.created - datetime.timedelta(days=35)) @@ -144,7 +144,7 @@ def test_menu_processor(activated_account, response(activated_account, display_key, name, init_database, generic_ussd_session) assert mock_get_adjusted_balance['timestamp'] == int((datetime.datetime.now() - datetime.timedelta(days=30)).timestamp()) - display_key = 'ussd.kenya.transaction_pin_authorization' + display_key = 'ussd.transaction_pin_authorization' ussd_menu = UssdMenu.find_by_name('transaction_pin_authorization') name = ussd_menu.get('name') generic_ussd_session['data'] = { @@ -163,7 +163,7 @@ def test_menu_processor(activated_account, token_symbol=token_symbol, sender_information=tx_sender_information) - display_key = 'ussd.kenya.exit_insufficient_balance' + display_key = 'ussd.exit_insufficient_balance' ussd_menu = UssdMenu.find_by_name('exit_insufficient_balance') name = ussd_menu.get('name') generic_ussd_session['data'] = { @@ -180,13 +180,13 @@ def test_menu_processor(activated_account, recipient_information=tx_recipient_information, token_balance=available_balance) - display_key = 'ussd.kenya.exit_invalid_menu_option' + display_key = 'ussd.exit_invalid_menu_option' ussd_menu = UssdMenu.find_by_name('exit_invalid_menu_option') name = ussd_menu.get('name') resp = response(activated_account, display_key, name, init_database, generic_ussd_session) assert resp == translation_for(display_key, preferred_language, support_phone=Support.phone_number) - display_key = 'ussd.kenya.exit_successful_transaction' + display_key = 'ussd.exit_successful_transaction' ussd_menu = UssdMenu.find_by_name('exit_successful_transaction') name = ussd_menu.get('name') generic_ussd_session['data'] = { diff --git a/apps/cic-ussd/tests/cic_ussd/processor/test_ussd.py b/apps/cic-ussd/tests/cic_ussd/processor/test_ussd.py index e5bbd52f..49c2bd49 100644 --- a/apps/cic-ussd/tests/cic_ussd/processor/test_ussd.py +++ b/apps/cic-ussd/tests/cic_ussd/processor/test_ussd.py @@ -97,7 +97,7 @@ def test_handle_menu_operations(activated_account, valid_service_codes = load_config.get('USSD_SERVICE_CODE').split(",") preferred_language = i18n.config.get('fallback') resp = handle_menu_operations(chain_str, external_session_id, phone, None, valid_service_codes[0], init_database, '4444') - assert resp == translation_for('ussd.kenya.account_creation_prompt', preferred_language) + assert resp == translation_for('ussd.account_creation_prompt', preferred_language) cached_ussd_session = get_cached_data(external_session_id) ussd_session = json.loads(cached_ussd_session) assert ussd_session['msisdn'] == phone @@ -118,5 +118,5 @@ def test_handle_menu_operations(activated_account, preferred_language = get_cached_preferred_language(activated_account.blockchain_address) persisted_ussd_session.state = 'enter_transaction_recipient' resp = handle_menu_operations(chain_str, external_session_id, phone, None, valid_service_codes[0], init_database, '1') - assert resp == translation_for('ussd.kenya.enter_transaction_recipient', preferred_language) + assert resp == translation_for('ussd.enter_transaction_recipient', preferred_language) diff --git a/apps/cic-ussd/tests/cic_ussd/processor/test_util.py b/apps/cic-ussd/tests/cic_ussd/processor/test_util.py index 038ad8ca..fcb90abb 100644 --- a/apps/cic-ussd/tests/cic_ussd/processor/test_util.py +++ b/apps/cic-ussd/tests/cic_ussd/processor/test_util.py @@ -32,7 +32,7 @@ def test_parse_person_metadata(activated_account, cache_person_metadata, cache_p cached_person_metadata = person_metadata.get_cached_metadata() person_metadata = json.loads(cached_person_metadata) preferred_language = get_cached_preferred_language(activated_account.blockchain_address) - display_key = 'ussd.kenya.display_person_metadata' + display_key = 'ussd.display_person_metadata' parsed_person_metadata = parse_person_metadata(cached_person_metadata, display_key, preferred_language) diff --git a/apps/cic-ussd/tests/cic_ussd/test_notifications.py b/apps/cic-ussd/tests/cic_ussd/test_notifications.py index bfd0b1f6..9e407b8c 100644 --- a/apps/cic-ussd/tests/cic_ussd/test_notifications.py +++ b/apps/cic-ussd/tests/cic_ussd/test_notifications.py @@ -8,8 +8,8 @@ from cic_ussd.notifications import Notifier @pytest.mark.parametrize("key, preferred_language, recipient, expected_message", [ - ("ussd.kenya.exit", "en", "+254712345678", "END Thank you for using the service."), - ("ussd.kenya.exit", "sw", "+254712345678", "END Asante kwa kutumia huduma.") + ("ussd.exit", "en", "+254712345678", "END Thank you for using the service."), + ("ussd.exit", "sw", "+254712345678", "END Asante kwa kutumia huduma.") ]) def test_send_sms_notification(celery_session_worker, expected_message, diff --git a/apps/cic-ussd/tests/cic_ussd/test_translation.py b/apps/cic-ussd/tests/cic_ussd/test_translation.py index a6448dbb..322883b1 100644 --- a/apps/cic-ussd/tests/cic_ussd/test_translation.py +++ b/apps/cic-ussd/tests/cic_ussd/test_translation.py @@ -10,11 +10,11 @@ from cic_ussd.translation import translation_for def test_translation_for(set_locale_files): english_translation = translation_for( - key='ussd.kenya.exit_invalid_request', + key='ussd.exit_invalid_request', preferred_language='en' ) swahili_translation = translation_for( - key='ussd.kenya.exit_invalid_request', + key='ussd.exit_invalid_request', preferred_language='sw' ) assert swahili_translation == 'END Chaguo si sahihi.' diff --git a/apps/cic-ussd/transitions/language_setting_transitions.json b/apps/cic-ussd/transitions/language_setting_transitions.json index 4ba0fa3a..bba4f496 100644 --- a/apps/cic-ussd/transitions/language_setting_transitions.json +++ b/apps/cic-ussd/transitions/language_setting_transitions.json @@ -1,21 +1,142 @@ [ { "trigger": "scan_data", - "source": "select_preferred_language", + "source": "initial_language_selection", + "dest": "account_creation_prompt", + "after": "cic_ussd.state_machine.logic.account.process_account_creation", + "conditions": "cic_ussd.state_machine.logic.language.is_valid_language_selection" + }, + { + "trigger": "scan_data", + "source": "initial_language_selection", + "dest": "initial_middle_language_set", + "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected" + }, + { + "trigger": "scan_data", + "source": "initial_language_selection", "dest": "exit", - "after": "cic_ussd.state_machine.logic.account.change_preferred_language", - "conditions": "cic_ussd.state_machine.logic.menu.menu_one_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + }, + { + "trigger": "scan_data", + "source": "initial_middle_language_set", + "dest": "initial_language_selection", + "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected" + }, + { + "trigger": "scan_data", + "source": "initial_middle_language_set", + "dest": "initial_last_language_set", + "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected" + }, + { + "trigger": "scan_data", + "source": "initial_middle_language_set", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + }, + { + "trigger": "scan_data", + "source": "initial_last_language_set", + "dest": "initial_middle_language_set", + "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected" + }, + { + "trigger": "scan_data", + "source": "initial_last_language_set", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + }, + { + "trigger": "scan_data", + "source": "initial_middle_language_set", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + }, + { + "trigger": "scan_data", + "source": "initial_language_selection", + "dest": "exit_invalid_menu_option" + }, + { + "trigger": "scan_data", + "source": "middle_language_set", + "dest": "exit_invalid_menu_option" + }, + { + "trigger": "scan_data", + "source": "last_language_set", + "dest": "exit_invalid_menu_option" }, { "trigger": "scan_data", "source": "select_preferred_language", "dest": "exit", - "after": "cic_ussd.state_machine.logic.account.change_preferred_language", - "conditions": "cic_ussd.state_machine.logic.menu.menu_two_selected" + "after": "cic_ussd.state_machine.logic.language.change_preferred_language", + "conditions": "cic_ussd.state_machine.logic.language.is_valid_language_selection" + }, + { + "trigger": "scan_data", + "source": "select_preferred_language", + "dest": "middle_language_set", + "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected" + }, + { + "trigger": "scan_data", + "source": "select_preferred_language", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + }, + { + "trigger": "scan_data", + "source": "middle_language_set", + "dest": "select_preferred_language", + "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected" + }, + { + "trigger": "scan_data", + "source": "middle_language_set", + "dest": "last_language_set", + "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected" + }, + { + "trigger": "scan_data", + "source": "middle_language_set", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + }, + { + "trigger": "scan_data", + "source": "last_language_set", + "dest": "middle_language_set", + "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected" + }, + { + "trigger": "scan_data", + "source": "last_language_set", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + }, + { + "trigger": "scan_data", + "source": "middle_language_set", + "dest": "exit", + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" }, { "trigger": "scan_data", "source": "select_preferred_language", "dest": "exit_invalid_menu_option" + }, + { + "trigger": "scan_data", + "source": "middle_language_set", + "dest": "exit_invalid_menu_option" + }, + { + "trigger": "scan_data", + "source": "last_language_set", + "dest": "exit_invalid_menu_option" } ] \ No newline at end of file diff --git a/apps/cic-ussd/transitions/signup_transitions.json b/apps/cic-ussd/transitions/signup_transitions.json index a0b14049..982f6ecc 100644 --- a/apps/cic-ussd/transitions/signup_transitions.json +++ b/apps/cic-ussd/transitions/signup_transitions.json @@ -1,29 +1,4 @@ [ - { - "trigger": "scan_data", - "source": "initial_language_selection", - "dest": "initial_pin_entry", - "after": "cic_ussd.state_machine.logic.account.change_preferred_language", - "conditions": "cic_ussd.state_machine.logic.menu.menu_one_selected" - }, - { - "trigger": "scan_data", - "source": "initial_language_selection", - "dest": "initial_pin_entry", - "after": "cic_ussd.state_machine.logic.account.change_preferred_language", - "conditions": "cic_ussd.state_machine.logic.menu.menu_two_selected" - }, - { - "trigger": "scan_data", - "source": "initial_language_selection", - "dest": "help", - "conditions": "cic_ussd.state_machine.logic.menu.menu_three_selected" - }, - { - "trigger": "scan_data", - "source": "initial_language_selection", - "dest": "exit_invalid_menu_option" - }, { "trigger": "scan_data", "source": "initial_pin_entry", @@ -39,7 +14,6 @@ { "trigger": "scan_data", "source": "initial_pin_confirmation", - "unless": "cic_ussd.state_machine.logic.validator.has_cached_person_metadata", "conditions": "cic_ussd.state_machine.logic.pin.pins_match", "dest": "start", "after": [ diff --git a/apps/cic-ussd/var/lib/locale/helpers.en.yml b/apps/cic-ussd/var/lib/locale/helpers.en.yml deleted file mode 100644 index 70b344df..00000000 --- a/apps/cic-ussd/var/lib/locale/helpers.en.yml +++ /dev/null @@ -1,36 +0,0 @@ -en: - female: |- - Female - from: |- - From - male: |- - Male - not_provided: |- - Not provided - no_transaction_history: |- - No transaction history - no_tokens_list: |- - No tokens to list - other: |- - Other - received: |- - Received - sent: |- - Sent - to: |- - To - guardians_list_header: |- - Walinzi uliowaongeza ni: - no_guardians_list: |- - No guardians set - error: - no_phone_number_provided: |- - No phone number was provided. - no_matching_account: |- - The number provided is not registered. - is_initiator: |- - Phone number cannot be your own. - is_existent_guardian: |- - This phone number is is already added as a guardian. - is_not_existent_guardian: |- - Phone number not set as PIN reset guardian. \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/locale/helpers.sw.yml b/apps/cic-ussd/var/lib/locale/helpers.sw.yml deleted file mode 100644 index cace30c9..00000000 --- a/apps/cic-ussd/var/lib/locale/helpers.sw.yml +++ /dev/null @@ -1,36 +0,0 @@ -sw: - female: |- - Mwanamke - from: |- - Kutoka kwa - male: |- - Mwanaume - not_provided: |- - Haijawekwa - no_transaction_history: |- - Hamna ripoti ya matumizi - no_tokens_list: |- - Hamna sarafu nyingine - other: |- - Nyingine - received: |- - Ulipokea - sent: |- - Ulituma - to: |- - Kwa - guardians_list_header: |- - Your set guardians are: - no_guardians_list: |- - Hamna walinzi walioongezwa - error: - no_phone_number_provided: |- - Namabari ya simu haijawekwa. - no_matching_account: |- - Nambari uliyoweka haijasajiliwa. - is_initiator: |- - Nambari yafaa kuwa tofauti na yako. - is_existent_guardian: |- - Namabari hii tayari imeongezwa kama mlinzi wa nambari ya siri. - is_not_existent_guardian: |- - Nambari hii haijaongezwa kama mlinzi wa nambari ya siri. \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/locale/sms.en.yml b/apps/cic-ussd/var/lib/locale/sms.en.yml deleted file mode 100644 index 678fbb15..00000000 --- a/apps/cic-ussd/var/lib/locale/sms.en.yml +++ /dev/null @@ -1,11 +0,0 @@ -en: - account_successfully_created: |- - You have been registered on Sarafu Network! To use dial *384*96# on Safaricom and *483*96# on other networks. For help %{support_phone}. - received_tokens: |- - Successfully received %{amount} %{token_symbol} from %{tx_sender_information} %{timestamp} to %{tx_recipient_information}. New balance is %{balance} %{token_symbol}. - sent_tokens: |- - Successfully sent %{amount} %{token_symbol} to %{tx_recipient_information} %{timestamp} from %{tx_sender_information}. New balance is %{balance} %{token_symbol}. - terms: |- - By using the service, you agree to the terms and conditions at http://grassecon.org/tos - upsell_unregistered_recipient: |- - %{tx_sender_information} tried to send you %{token_symbol} but you are not registered. To use dial *384*96# on Safaricom and *483*96# on other networks. For help %{support_phone}. \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/locale/sms.sw.yml b/apps/cic-ussd/var/lib/locale/sms.sw.yml deleted file mode 100644 index e2647cf2..00000000 --- a/apps/cic-ussd/var/lib/locale/sms.sw.yml +++ /dev/null @@ -1,11 +0,0 @@ -sw: - account_successfully_created: |- - Umesajiliwa kwa huduma ya Sarafu! Kutumia bonyeza *384*96# Safaricom ama *483*46# kwa utandao tofauti. Kwa Usaidizi %{support_phone}. - received_tokens: |- - Umepokea %{amount} %{token_symbol} kutoka kwa %{tx_sender_information} %{timestamp} ikapokewa na %{tx_recipient_information}. Salio lako ni %{balance} %{token_symbol}. - sent_tokens: |- - Umetuma %{amount} %{token_symbol} kwa %{tx_recipient_information} %{timestamp} kutoka kwa %{tx_sender_information}. Salio lako ni %{balance} %{token_symbol}. - terms: |- - Kwa kutumia hii huduma, umekubali sheria na masharti yafuatayo http://grassecon.org/tos - upsell_unregistered_recipient: |- - %{tx_sender_information} amejaribu kukutumia %{token_symbol} lakini hujasajili. Kutumia bonyeza *384*96# Safaricom ama *483*46# kwa utandao tofauti. Kwa Usaidizi %{support_phone}. \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/locale/ussd.en.yml b/apps/cic-ussd/var/lib/locale/ussd.en.yml deleted file mode 100644 index 40985fed..00000000 --- a/apps/cic-ussd/var/lib/locale/ussd.en.yml +++ /dev/null @@ -1,317 +0,0 @@ -en: - kenya: - initial_language_selection: |- - CON Welcome to Sarafu Network - 1. English - 2. Kiswahili - 3. Help - initial_pin_entry: |- - CON Please enter a new four number PIN for your account. - initial_pin_confirmation: |- - CON Enter your four number PIN again - enter_given_name: |- - CON Enter first name - 0. Back - enter_family_name: |- - CON Enter family name - 0. Back - enter_date_of_birth: |- - CON Enter year of birth - 0. Back - enter_gender: |- - CON Enter gender - 1. Male - 2. Female - 3. Other - 0. Back - enter_location: |- - CON Enter your location - 0. Back - enter_products: |- - CON Please enter a product or service you offer - 0. Back - start: |- - CON Balance %{account_balance} %{account_token_name} - 1. Send - 2. My Sarafu - 3. My Account - 4. Help - enter_transaction_recipient: |- - CON Enter phone number - 0. Back - enter_transaction_amount: |- - CON Enter amount - 0. Back - first_account_tokens_set: |- - CON Choose a number or symbol from your balances: - %{first_account_tokens_set} - 11. Next - 00. Exit - middle_account_tokens_set: |- - CON Choose a number or symbol from your balances: - %{middle_account_tokens_set} - 11. Next - 22. Previous - 00. Exit - last_account_tokens_set: |- - CON Choose a number or symbol from your balances: - %{last_account_tokens_set} - 22. Previous - 00. Exit - token_selection_pin_authorization: - first: |- - CON %{token_data} - Enter pin to select: - retry: |- - %{retry_pin_entry} - account_management: |- - CON My account - 1. My profile - 2. Change language - 3. Check balance - 4. Check statement - 5. PIN options - 0. Back - metadata_management: |- - CON My profile - 1. Edit name - 2. Edit gender - 3. Edit age - 4. Edit location - 5. Edit products - 6. View my profile - 0. Back - display_user_metadata: |- - CON Your details are: - Name: %{full_name} - Gender: %{gender} - Age: %{age} - Location: %{location} - You sell: %{products} - 0. Back - select_preferred_language: |- - CON Choose language - 1. English - 2. Kiswahili - 0. Back - retry_pin_entry: |- - CON Incorrect PIN entered, please try again. You have %{remaining_attempts} attempts remaining. - 0. Back - pin_management: |- - CON Pin options - 1. Change PIN - 2. Reset PIN - 3. Guard PIN - 0. Back - enter_current_pin: - first: |- - CON Enter current PIN. - 0. Back - retry: |- - %{retry_pin_entry} - enter_new_pin: |- - CON Enter your new four number PIN - 0. Back - new_pin_confirmation: |- - CON Enter your new four number PIN again - 0. Back - reset_guarded_pin: |- - CON Enter phone number you are the guardian to reset their pin - 0. Back - reset_guarded_pin_authorization: - first: |- - CON Enter YOUR pin to confirm %{guarded_account_information}'s reset - 0. Back - retry: |- - %{retry_pin_entry} - exit_pin_reset_initiated_success: |- - CON Success: You have initiated a PIN reset for %{guarded_account_information} - 0. Back - 9. Exit - exit_not_authorized_for_pin_reset: |- - CON Failure: You are not authorized to reset that PIN. You must be a guardian! - 0. Back - 9. Exit - guard_pin: |- - CON Pin guard - 1. View guardians - 2. Add guardian - 3. Remove guardian - 0. Back - guardian_list_pin_authorization: - first: |- - CON Enter your pin to view set guardians - 0. Back - retry: |- - %{retry_pin_entry} - guardian_list: |- - CON %{guardians_list} - 0. Back - 9. Exit - add_guardian: |- - CON Enter phone number to add as pin reset guardian - 0. Back - add_guardian_pin_authorization: - first: |- - CON Enter your pin to add %{guardian_information} as your PIN reset guardian - 0. Back - retry: |- - %{retry_pin_entry} - exit_guardian_addition_success: |- - CON Success: %{guardian_information} can now reset your PIN - 0. Back - 9. Exit - exit_invalid_guardian_addition: |- - CON %{error_exit} - 0. Back - 9. Exit - remove_guardian: |- - CON Enter phone number to revoke guardianship: - 0. Back - remove_guardian_pin_authorization: - first: |- - CON Enter your pin to remove %{guardian_information} as your PIN reset guardian - 0. Back - retry: |- - %{retry_pin_entry} - exit_guardian_removal_success: |- - CON Success: %{guardian_information} PIN reset guardianship is revoked - 0. Back - 9. Exit - exit_invalid_guardian_removal: |- - CON %{error_exit} - 0. Back - 9. Exit - transaction_pin_authorization: - first: |- - CON %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}. - Please enter your PIN to confirm. - 0. Back - retry: |- - %{retry_pin_entry} - display_metadata_pin_authorization: - first: |- - CON Please enter your PIN - 0. Back - retry: |- - %{retry_pin_entry} - account_balances_pin_authorization: - first: |- - CON Please enter your PIN to view balances - 0. Back - retry: |- - %{retry_pin_entry} - account_statement_pin_authorization: - first: |- - CON Please enter your PIN to view statement - 0. Back - retry: |- - %{retry_pin_entry} - name_edit_pin_authorization: - first: |- - CON Please enter your PIN - 0. Back - retry: |- - %{retry_pin_entry} - dob_edit_pin_authorization: - first: |- - CON Please enter your PIN - 0. Back - retry: |- - %{retry_pin_entry} - gender_edit_pin_authorization: - first: |- - CON Please enter your PIN - 0. Back - retry: |- - %{retry_pin_entry} - location_edit_pin_authorization: - first: |- - CON Please enter your PIN - 0. Back - retry: |- - %{retry_pin_entry} - products_edit_pin_authorization: - first: |- - CON Please enter your PIN - 0. Back - retry: |- - %{retry_pin_entry} - account_balances: - available_balance: |- - CON Your balances are as follows: - balance: %{available_balance} %{token_symbol} - 0. Back - with_fees: |- - CON Your balances are as follows: - balances: %{available_balance} %{token_symbol} - fees: %{tax} %{token_symbol} - 0. Back - with_rewards: |- - CON Your balances are as follows: - balance: %{available_balance} %{token_symbol} - fees: %{tax} %{token_symbol} - rewards: %{bonus} %{token_symbol} - 0. Back - first_transaction_set: |- - CON %{first_transaction_set} - 1. Next - 00. Exit - middle_transaction_set: |- - CON %{middle_transaction_set} - 1. Next - 2. Previous - 00. Exit - last_transaction_set: |- - CON %{last_transaction_set} - 2. Previous - 00. Exit - exit: |- - END Thank you for using the service. - exit_invalid_request: |- - END Invalid request. - exit_invalid_menu_option: |- - CON Invalid menu option. For help, call %{support_phone}. - 00. Back - 99. Exit - exit_invalid_input: |- - CON Invalid input. Nothing selected - 00. Back - 99. Exit - exit_pin_blocked: |- - END Your PIN has been blocked. For help, please call %{support_phone}. - exit_invalid_pin: |- - END The PIN you have entered is invalid. PIN must consist of 4 digits. For help, call %{support_phone}. - exit_invalid_new_pin: |- - END The PIN you have entered is invalid. PIN must be different from your current PIN. For help, call %{support_phone}. - exit_pin_mismatch: |- - END The new PIN does not match the one you entered. Please try again. For help, call %{support_phone}. - exit_invalid_recipient: |- - CON Recipient's phone number is not registered or is invalid: - 00. Retry - 99. Exit - exit_successful_transaction: |- - CON Your request has been sent. %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}. - 00. Back - 99. Exit - exit_insufficient_balance: |- - CON Payment of %{amount} %{token_symbol} to %{recipient_information} has failed due to insufficent balance. - Your Sarafu-Network balances is: %{token_balance} - 00. Back - 99. Exit - exit_successful_token_selection: |- - CON Success! %{token_symbol} is your active Sarafu. - 00. Back - 99. Exit - invalid_service_code: |- - Please dial %{valid_service_code} to access Sarafu Network - help: |- - CON For assistance call %{support_phone} - 00. Back - 99. Exit - complete: |- - CON Your request has been sent. You will receive an SMS shortly. - 00. Back - 99. Exit - account_creation_prompt: |- - END Your account is being created. You will receive an SMS when your account is ready. diff --git a/apps/cic-ussd/var/lib/locale/ussd.sw.yml b/apps/cic-ussd/var/lib/locale/ussd.sw.yml deleted file mode 100644 index 89bf9e71..00000000 --- a/apps/cic-ussd/var/lib/locale/ussd.sw.yml +++ /dev/null @@ -1,316 +0,0 @@ -sw: - kenya: - initial_language_selection: |- - CON Karibu Sarafu Network - 1. English - 2. Kiswahili - 3. Help - initial_pin_entry: |- - CON Tafadhali weka pin mpya yenye nambari nne kwa akaunti yako - initial_pin_confirmation: |- - CON Weka PIN yako tena - enter_given_name: |- - CON Weka jina lako la kwanza - enter_family_name: |- - CON Weka jina lako la mwisho - 0. Nyuma - enter_date_of_birth: |- - CON Weka mwaka wa kuzaliwa - 0. Nyuma - enter_gender: |- - CON Weka jinsia yako - 1. Mwanaume - 2. Mwanamke - 3. Nyngine - 0. Nyuma - enter_location: |- - CON Weka eneo lako - 0. Nyuma - enter_products: |- - CON Weka bidhaa ama huduma unauza - 0. Nyuma - start: |- - CON Salio %{account_balance} %{account_token_name} - 1. Tuma - 2. Sarafu yangu - 3. Akaunti yangu - 4. Usaidizi - enter_transaction_recipient: |- - CON Weka nambari ya simu - 0. Nyuma - enter_transaction_amount: |- - CON Weka kiwango - 0. Nyuma - first_account_tokens_set: |- - CON Chagua nambari au ishara kutoka kwa salio zako: - %{first_account_tokens_set} - 11. Mbele - 00. Ondoka - middle_account_tokens_set: |- - CON Chagua nambari au ishara kutoka kwa salio zako: - %{middle_account_tokens_set} - 11. Mbele - 22. Nyuma - 00. Ondoka - last_account_tokens_set: |- - CON Chagua nambari au ishara kutoka kwa salio zako: - %{last_account_tokens_set} - 22. Nyuma - 00. Ondoka - token_selection_pin_authorization: - first: |- - CON %{token_data} - Weka nambari ya siri kuchagua: - retry: |- - %{retry_pin_entry} - account_management: |- - CON Akaunti yangu - 1. Wasifu wangu - 2. Chagua lugha utakayotumia - 3. Angalia salio - 4. Angalia taarifa ya matumizi - 5. Mipangilio ya nambari ya siri - 0. Nyuma - metadata_management: |- - CON Wasifu wangu - 1. Weka jina - 2. Weka jinsia - 3. Weka umri - 4. Weka eneo - 5. Weka bidhaa - 6. Angalia wasifu wako - 0. Nyuma - display_user_metadata: |- - CON Wasifu wako una maelezo yafuatayo: - Jina: %{full_name} - Jinsia: %{gender} - Umri: %{age} - Eneo: %{location} - Unauza: %{products} - 0. Nyuma - select_preferred_language: |- - CON Chagua lugha - 1. Kingereza - 2. Kiswahili - 0. Nyuma - retry_pin_entry: |- - CON Nambari uliyoweka si sahihi, jaribu tena. Una majaribio %{remaining_attempts} yaliyobaki. - 0. Nyuma - pin_management: |- - CON Pin options - 1. Badilisha nambari yangu ya siri - 2. Tuma ombili la kubadilisha nambari ya siri - 3. Linda nambari ya siri - 0. Nyuma - enter_current_pin: - first: |- - CON Weka nambari ya siri. - 0. Nyuma - retry: |- - %{retry_pin_entry} - enter_new_pin: |- - CON Weka nambari ya siri mpya - 0. Nyuma - new_pin_confirmation: |- - CON Weka nambari yako ya siri tena - 0. Nyuma - reset_guarded_pin: |- - CON Weka nambari ya simu ili kutuma ombi la kubalisha nambari ya siri. - 0. Nyuma - reset_guarded_pin_authorization: - first: |- - CON Weka nambari YAKO ya siri ili kudhibitisha ombi la kubadilisha nambari ya siri ya %{guarded_account_information}. - 0. Nyuma - retry: |- - %{retry_pin_entry} - exit_pin_reset_initiated_success: |- - CON Ombi lako la kubadili nambari ya siri ya %{guarded_account_information} limetumwa. - 0. Nyuma - 9. Ondoka - exit_not_authorized_for_pin_reset: |- - CON Huruhusiwi kutuma ombi la kubadilisha nambari ya siri. - 0. Nyuma - 9. Ondoka - guard_pin: |- - CON Linda nambari ya siri - 1. Walinzi wa namabari ya siri - 2. Ongeza mlinzi - 3. Ondoa mlinzi - 0. Nyuma - guardian_list_pin_authorization: - first: |- - CON Weka nambari yako ya siri ili kuona walinzi uliowaongeza - 0. Nyuma - retry: |- - %{retry_pin_entry} - guardian_list: |- - CON %{guardians_list} - 0. Nyuma - 9. Ondoka - add_guardian: |- - CON Weka nambari ya simu ili kuongeza mlinzi - 0. Nyuma - add_guardian_pin_authorization: - first: |- - CON Weka nambari YAKO ya siri ili kumwongeza %{guardian_information} kama mlinzi - 0. Nyuma - retry: |- - %{retry_pin_entry} - exit_guardian_addition_success: |- - CON Ombi lako la kumwongeza: %{guardian_information} kama mlinzi limefanikiwa - 0. Nyuma - 9. Ondoka - exit_invalid_guardian_addition: |- - CON %{error_exit} - 0. Nyuma - 9. Ondoka - remove_guardian: |- - CON Weka nambari ya simu ili kuondoa mlinzi - 0. Nyuma - remove_guardian_pin_authorization: - first: |- - CON Weka nambari YAKO ya siri ili kumwondoa %{guardian_information} kama mlinzi - 0. Nyuma - retry: |- - %{retry_pin_entry} - exit_guardian_removal_success: |- - CON Ombi lako la kumwondoa: %{guardian_information} kama mlinzi limefanikiwa - 0. Nyuma - 9. Ondoka - exit_invalid_guardian_removal: |- - CON %{error_exit} - 0. Nyuma - 9. Ondoka - transaction_pin_authorization: - first: |- - CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}. - Tafadhali weka nambari yako ya siri kudhibitisha. - 0. Nyuma - retry: |- - %{retry_pin_entry} - display_metadata_pin_authorization: - first: |- - CON Tafadhali weka PIN yako - 0. Nyuma - retry: |- - %{retry_pin_entry} - account_balances_pin_authorization: - first: |- - CON Tafadhali weka PIN yako kuona salio. - 0. Nyuma - retry: |- - %{retry_pin_entry} - account_statement_pin_authorization: - first: |- - CON Tafadhali weka PIN yako kuona taarifa ya matumizi. - 0. Nyuma - retry: |- - %{retry_pin_entry} - name_edit_pin_authorization: - first: |- - CON Tafadhali weka PIN yako - 0. Nyuma - retry: |- - %{retry_pin_entry} - dob_edit_pin_authorization: - first: |- - CON Tafadhali weka PIN yako - 0. Nyuma - retry: |- - %{retry_pin_entry} - gender_edit_pin_authorization: - first: |- - CON Tafadhali weka PIN yako - 0. Nyuma - retry: |- - %{retry_pin_entry} - location_edit_pin_authorization: - first: |- - CON Tafadhali weka PIN yako - 0. Nyuma - retry: |- - %{retry_pin_entry} - products_edit_pin_authorization: - first: |- - CON Tafadhali weka PIN yako - 0. Nyuma - retry: |- - %{retry_pin_entry} - account_balances: - available_balance: |- - CON Salio zako ni zifuatazo: - salio: %{available_balance} %{token_symbol} - 0. Nyuma - with_fees: |- - CON Salio zako ni zifuatazo: - salio: %{available_balance} %{token_symbol} - ushuru: %{tax} %{token_symbol} - 0. Nyuma - with_rewards: |- - CON Salio zako ni zifuatazo: - salio: %{available_balance} %{token_symbol} - ushuru: %{tax} %{token_symbol} - tuzo: %{bonus} %{token_symbol} - 0. Nyuma - first_transaction_set: |- - CON %{first_transaction_set} - 1. Mbele - 00. Ondoka - middle_transaction_set: |- - CON %{middle_transaction_set} - 1. Mbele - 2. Nyuma - 00. Ondoka - last_transaction_set: |- - CON %{last_transaction_set} - 2. Nyuma - 00. Ondoka - exit: |- - END Asante kwa kutumia huduma. - exit_invalid_request: |- - END Chaguo si sahihi. - exit_invalid_menu_option: |- - CON Chaguo lako sio sahihi. Kwa usaidizi piga simu %{support_phone} - 00. Nyuma - 99. Ondoka - exit_invalid_input: |- - CON Chaguo lako halipatikani. Hakuna kilichochaguliwa. - 00. Nyuma - 99. Ondoka - exit_pin_blocked: |- - END PIN yako imefungwa. Kwa usaidizi tafadhali piga simu %{support_phone}. - exit_invalid_pin: |- - END PIN uliyobonyeza sio sahihi. PIN lazima iwe na nambari nne. Kwa usaidizi piga simu %{support_phone}. - exit_invalid_new_pin: |- - END PIN uliyobonyeza sio sahihi. PIN lazima iwe tofauti na pin yako ya sasa. Kwa usaidizi piga simu %{support_phone}. - exit_pin_mismatch: |- - END PIN mpya na udhibitisho wa pin mpya hazilingani. Tafadhali jaribu tena. Kwa usaidizi piga simu %{support_phone}. - exit_invalid_recipient: |- - CON Mpokeaji wa nambari hapatikani au sio sahihi. - 00. Jaribu tena - 99. Ondoka - exit_successful_transaction: |- - CON Ombi lako limetumwa. %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}. - 00. Nyuma - 99. Ondoka - exit_insufficient_balance: |- - CON Malipo ya %{amount} %{token_symbol} kwa %{recipient_information} halijakamilika kwa sababu salio lako haitoshi. - Akaunti yako ya Sarafu ina salio ifuatayo: %{token_balance} - 00. Nyuma - 99. Ondoka - exit_successful_token_selection: |- - CON Chaguo lako limekamilika, %{token_symbol} ni sarafu itakayotumika. - 00. Nyuma - 99. Ondoka - invalid_service_code: |- - Bonyeza %{valid_service_code} kutumia mtandao wa Sarafu - help: |- - CON Kwa usaidizi piga simu %{support_phone} - 0. Nyuma - 9. Ondoka - complete: |- - CON Ombi lako limetumwa. Utapokea uthibitishaji wa SMS kwa muda mfupi. - 00. Nyuma - 99. Ondoka - account_creation_prompt: |- - END Akaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari. diff --git a/apps/cic-ussd/var/lib/sys/guardians.txt b/apps/cic-ussd/var/lib/sys/guardians.txt new file mode 100644 index 00000000..e69de29b diff --git a/apps/cic-ussd/var/lib/sys/helpers.csv b/apps/cic-ussd/var/lib/sys/helpers.csv new file mode 100644 index 00000000..5bea05ba --- /dev/null +++ b/apps/cic-ussd/var/lib/sys/helpers.csv @@ -0,0 +1,19 @@ +keys,en,sw +female,Female,Mwanamke +from,From,Kutoka kwa +male,Male,Mwanaume +not_provided,Not provided,Haijawekwa +no_language_list,No language list,Hamna lugha ya kuchagua +no_transaction_history,No transaction history,Hamna ripoti ya matumizi +no_tokens_list,No tokens to list,Hamna sarafu nyingine +other,Other,Nyingine +received,Received,Ulipokea +sent,Sent,Ulituma +to,To,Kwa +guardians_list_header,Your set guardians are:,Walinzi uliowaongeza ni: +no_guardians_list,No guardians set,Hamna walinzi walioongezwa +error.no_phone_number_provided,No phone number was provided.,Namabari ya simu haijawekwa. +error.no_matching_account,The number provided is not registered.,Nambari uliyoweka haijasajiliwa. +error.is_initiator,Phone number cannot be your own.,Nambari yafaa kuwa tofauti na yako. +error.is_existent_guardian,This phone number is is already added as a guardian.,Namabari hii tayari imeongezwa kama mlinzi wa nambari ya siri. +error.is_not_existent_guardian,Phone number not set as PIN reset guardian.,Nambari hii haijaongezwa kama mlinzi wa nambari ya siri. \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/sys/languages.json b/apps/cic-ussd/var/lib/sys/languages.json new file mode 100644 index 00000000..ee5dd3c4 --- /dev/null +++ b/apps/cic-ussd/var/lib/sys/languages.json @@ -0,0 +1,9 @@ +{ + "en": "English", + "sw": "Kiswahili", + "kam": "Kamba", + "kik": "Kikiuyu", + "miji": "Mijikenda", + "luo": "Luo", + "bor": "Borana" +} diff --git a/apps/cic-ussd/var/lib/sys/sms.csv b/apps/cic-ussd/var/lib/sys/sms.csv new file mode 100644 index 00000000..1da02cae --- /dev/null +++ b/apps/cic-ussd/var/lib/sys/sms.csv @@ -0,0 +1,7 @@ +keys,en,sw +account_successfully_created,You have been registered on Sarafu Network! To use dial *384*96# on Safaricom and *483*96# on other networks. For help %{support_phone}.,Umesajiliwa kwa huduma ya Sarafu! Kutumia bonyeza *384*96# Safaricom ama *483*46# kwa utandao tofauti. Kwa Usaidizi %{support_phone}. +received_tokens,Successfully received %{amount} %{token_symbol} from %{tx_sender_information} %{timestamp} to %{tx_recipient_information}. New balance is %{balance} %{token_symbol}.,Umepokea %{amount} %{token_symbol} kutoka kwa %{tx_sender_information} %{timestamp} ikapokewa na %{tx_recipient_information}. Salio lako ni %{balance} %{token_symbol}. +sent_tokens,Successfully sent %{amount} %{token_symbol} to %{tx_recipient_information} %{timestamp} from %{tx_sender_information}. New balance is %{balance} %{token_symbol}.,Umetuma %{amount} %{token_symbol} kwa %{tx_recipient_information} %{timestamp} kutoka kwa %{tx_sender_information}. Salio lako ni %{balance} %{token_symbol}. +terms,"By using the service, you agree to the terms and conditions at http://grassecon.org/tos","Kwa kutumia hii huduma, umekubali sheria na masharti yafuatayo http://grassecon.org/tos" +upsell_unregistered_recipient,%{tx_sender_information} tried to send you %{token_symbol} but you are not registered. To use dial *384*96# on Safaricom and *483*96# on other networks. For help %{support_phone}.,%{tx_sender_information} amejaribu kukutumia %{token_symbol} lakini hujasajili. Kutumia bonyeza *384*96# Safaricom ama *483*46# kwa utandao tofauti. Kwa Usaidizi %{support_phone}. +pin_reset_initiated,%{pin_initiator} has sent a request to initiate your PIN reset.,%{pin_initiator} ametuma ombi la kubadilisha PIN yako. \ No newline at end of file diff --git a/apps/cic-ussd/var/lib/sys/ussd.csv b/apps/cic-ussd/var/lib/sys/ussd.csv new file mode 100644 index 00000000..a9b79f3e --- /dev/null +++ b/apps/cic-ussd/var/lib/sys/ussd.csv @@ -0,0 +1,862 @@ +keys,en,sw,kam,kik,miji,luo,bor +initial_language_selection,"CON Welcome to Sarafu Network +%{first_language_set} + +11. Next +00. Exit","CON Karibu Sarafu Network +%{first_language_set} + +11. Mbele +00. Ondoka","CON Kalivu Network ya Sarafu +1. English +2. Kiswahili +3. Kikamba +3. Help","CON Karibu Sarafu Network +1. Githungu +2. Githweri +3. Uteithio","CON Karibu Sarafu Network +1. Chizungu +2. Chiswahili +3. Avizwa","CON Machiegni e network mar Sarafu +1. Dho Ngere +2. Dho oswayo +3. Kony","CON Karibu Sarafu Network +1. Afaan ferenji +2. Afaan kiswahili +3. Qarqars" +initial_pin_entry,CON Please enter a new four number PIN for your account.,CON Tafadhali weka pin mpya yenye nambari nne kwa akaunti yako,CON Tafadhali ikia pin yumbya ila ina namba inya kinanduni chaku,CON Ekera namba yaku ya thiri njeru ena numba enna.,CON Ika piniyo ya namba Ne kwa akaunti Yakwako.,CON Kiyie to ket namba ni mopondo e akaont ni.,CON Tafadhal pin hareti kekhae ka namba afuri fulaa akaunti kake +initial_pin_confirmation,CON Enter your four number PIN again,CON Weka PIN yako tena,CON Ikia PIN yaku ingi,CON Ekera namba yaku ya thiri ringi,CON Uyira Kwika pin kaheri.,CON Ket namba ni mopondo kendo,CON Mar dibii pin kekhae +enter_given_name,"CON Enter first name +0. Back",CON Weka jina lako la kwanza,CON Ikia isyitwa yaku ya mbee,CON Ekera retwa rwaku ria mbere,CON Ika dzinaro rakwanza.,CON Ket nyingi mokwongo,CON Makhaa kake ka karaa kor +enter_family_name,"CON Enter family name +0. Back","CON Weka jina lako la mwisho +0. Rudi","CON Ikia isyitwa yaku ya muthya +0. Syoka itina","CON Ekera ritwa rwaku ria mwisho +0. Coka thutha","CON Ika dzinaro ra mwisho +0. Uya Nyuma","CON Ket nyingi mogik. +0. Dog chien","CON Makhaa kake ka egee +0. Dhebii" +enter_date_of_birth,"CON Enter year of birth +0. Back","CON Weka mwaka wa kuzaliwa +0. Rudi","CON Ikia mwaka wa kusyawa +0. Syoka itina","CON Ekera mwaka waku wa guciarwo +0. Coka thutha","CON Ika mwaka wakuvyalwa +0. Uya Nyuma","CON Ket iki mar nyuol +0. Dog chien","CON Gan kake ka athdalat kor +0. Dheebi" +enter_gender,"CON Enter gender +1. Male +2. Female +3. Other +0. Back","CON Weka jinsia yako +1. Mwanaume +2. Mwanamke +3. Nyngine +0. Rudi","CON Ikia gender yaku +1. Mundume +2. Mundumuka +3. Ingi +0. Syoka itina","CON We mudurume kana mutumia +1.Mudurume +2. Mutumia +3. Ingi +0. Coka thutha","CON Ika kala Umulume ama Umuche au vingine. +1. Mulume +2. Muche +3. Vinjine +0. Uya Nyuma","CON Ket kit chwech mari +1. Dichuo +2. Dhako +3. Moko +0. Dog chien","CON Athin Dir mo Dubr +1. Dir +2. Dubr +3. Ka dibii +0. Dheebi" +enter_location,"CON Enter your location, +0. Back","CON Weka eneo lako +0. Rudi","CON Ikia utui waku kana location +0. Syoka itina","CON Ekera kuria uumete +0. Coka thutha","CON Ika enero wombolako. +0. Uya nyuma","CON Ket kumaidake +0. Dog chien","CON Fulaa athin kubat kor +0. Dhebii" +enter_products,"CON Please enter a product or service you offer +0. Back","CON Weka bidhaa ama huduma unauza +0. Rudi","CON Ikia syindu kana huduma ila unenganae +0. Syoka itina","CON Ekera indo kana wira uria urendia +0. Coka thutha","CON Ika Viya ama utu uhendao +0. Uya Nyuma","CON Ket gima iuso kata tich mitimo +0. Dog chien","CON Waan gurgurt okan namaa kenit khes khae +0. Dheebi" +start,"CON Balance %{account_balance} %{account_token_name} +1. Send +2. My Sarafu +3. My Account +4. Help","CON Salio %{account_balance} %{account_token_name} +1. Tuma +2. Sarafu yangu +3. Akaunti yangu +4. Usaidizi","CON Mbalansi kana utyalyo %{account_balance} %{account_token_name} +1. Tuma +2. Kinandu chakwa +3. Utethyo","CON Matigari %{account_balance} %{account_token_name} +1. Tuma +2. Akaunti yaku +3. Uteithio","CON Sazo %{account_balance} %{account_token_name} +1. Huma +2. Akaunti yangu +3. Avizwa","CON Ma Odong' %{account_balance} %{account_token_name} +1. Or +2. Akaont na +3. Kony","CON Salio %{account_balance} %{account_token_name} +1. Erg +2. Akaunti khiy +3. Qarqars" +enter_transaction_recipient,"CON Enter phone number +0. Back","CON Weka nambari ya simu +0. Rudi","CON Ikia namba ya simu +0. Syoka itina","CON Ikira namba ya thimu +0. Coka thutha","CON Ika namba yasimu. +0. Uya Nyuma","CON Ket nambani mar simu +0. Dog chien","CON Namba ta simuu kekhai +0. Dheebi" +enter_transaction_amount,"CON Enter amount +0. Back","CON Weka kiwango +0. Rudi","CON Ikia kiwango +0. Syoka itina","CON Ikira muigana +0. Coka thutha","CON Ika chaasi. +0. Uya nyuma","CON Ket giko mari +0. Dog chien","CON kiwango kekhai +0. Dheebi" +first_account_tokens_set,"CON Choose a number or symbol from your balances: +%{first_account_tokens_set} + +0. Back +11. Next +00. Exit","CON Chagua nambari au ishara kutoka kwa salio zako: +%{first_account_tokens_set} + +0. Rudi +11. Mbele +00. Ondoka","Sakua Sarafu: + %{token_list} +99. Thi mbee +00. Syoka itina","Shaghura Sarafu: + %{token_list} +99. Mbere +00. Coka thutha","Tsagula Sarafu: + %{token_list} +99. Enderera +00. Uya Nyuma","Yier Sarafu: + %{token_list} +99. Nyime +00. Dog chien","Chaqui Sarafu: + %{token_list} +99. Dhuur +00. Dheebi" +middle_account_tokens_set,"CON Choose a number or symbol from your balances: +%{middle_account_tokens_set} +11. Next +22. Previous +00. Exit","CON Chagua nambari au ishara kutoka kwa salio zako: +%{middle_account_tokens_set} +11. Mbele +22. Rudi +00. Ondoka","Sakura Sarafu: + %{token_list} +99. Thi mbee +00. Syoka itina","Shaghura Sarafu: + %{token_list} +99. Mbere +00. Cooka thutha","Tsagula Sarafu: + %{token_list} +99. Enderera +00. Uya Nyuma","Yier Sarafu: + %{token_list} +99. Nyime +00. Dog chien","Chaqui Sarafu: + %{token_list} +99. Dhuur +00. Dheebi" +last_account_tokens_set,"CON Choose a number or symbol from your balances: +%{last_account_tokens_set} +22. Previous +00. Exit","CON Chagua nambari au ishara kutoka kwa salio zako: +%{last_account_tokens_set} +22. Rudi +00. Ondoka","Sakura sarafu: + %{token_list} +00. Syoka itina","Sarafu: + %{token_list} +00. Cooka thutha","Tsagula Sarafu: + %{token_list} +00. Uya Nyuma","Yier Sarafu: + %{token_list} +00. Dog chien","Chagua Sarafu: + %{token_list} +00. Dheebi" +token_selection_pin_authorization.first,"CON %{token_data} +Enter pin to select:","CON %{token_data} +Weka nambari ya siri kuchagua: +0. Back","%{token_info} +Sakua kwa kwikia pin yaku: +0. Syoka itina","%{token_info} +Ekera pin yaku gushaghura: +0. Cooka thutha","%{token_info} +Ika piniyo kutsagula Sarafu: +0. Uya Nyuma","%{token_info} +Ket pin ni iyier: +0. Dog chien","%{token_info} +Pin kake khai akh dibii chaguat +0. Dheebi" +token_selection_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +account_management,"CON My account +1. My profile +2. Change language +3. Check balance +4. Check statement +5. PIN options +0. Back","CON Akaunti yangu +1. Wasifu wangu +2. Chagua lugha utakayotumia +3. Angalia salio +4. Angalia taarifa ya matumizi +5. Mipangilio ya nambari ya siri +0. Rudi","CON Kinandu Chakwa +1. Profile/wasifu wakwa +2. Sakua luka ila ukatumiaa +3. Syisya Mbalansi yaku +4. Syisya welesyo wa utumii +5. Chenja namba yaku ya siri +0. Syoka itina","CON Akaunti yakwa +1. Maritwa makwa +2. Shaghura rothiumu ukuhuthira +3. Rora matigari +4. Rora rugano rwa mahuthira +5. Chengia namba ya thiri +0. Coka","CON Akaunti yangu +1. Malagizo Gangu +2. Tsagula Luga Undohumira +3. Lola Sazo +4. Lola tarifa Za Mahumizi +5. Galuza Namba Fitse +0. Uya Nyuma","CON Akaont na +1. Nyanonro mara +2. Yier dhok midwatiyogo +3. Ngi midong go +4. N'gi chal mar akaont +5. Lok nambani mopondo +0. Dog chien","CON Akaunti khiy +1. Wasifu wangu +2. Afaan dubaad chaqui +3. laali balansi +4. Angalia taarifa ya matumizi +5. Gargarch namba +0. Dheebi" +metadata_management,"CON My profile +1. Edit name +2. Edit gender +3. Edit age +4. Edit location +5. Edit products +6. View my profile +0. Back","CON Wasifu wangu +1. Weka jina +2. Weka jinsia +3. Weka umri +4. Weka eneo +5. Weka bidhaa +6. Angalia wasifu wako +0. Rudi","CON Profile/Wasifu wakwa +1. Ikia isyitwa +2. Ikia jinsia/gender yaku +3. Ikia miaka yaku +4. Ikia utui waku +5. Ikia syindu ila utesaa +6. Sisya profile/wasifu waku +0. Syoka itina","CON Maondu maku +1. Ekera ritwa +2. Ekera kana we mundurume kana mutumia +3. Ekera miaka yaku +4. Ekera kuria uikaraga +5. Ikira kiria uendagia +6. Rora maundu maku +0. Coka thutha","CON Malagizo Gangu +1. Ika dzinaro +2. Ika kala umulume ama Umuche +3. Ika umuri +4. Ika eneo +5. Ika Miyo ama viya uguzavyo +6. Lola malagizo Gangu +0. Uya nyuma","CON Wasifu wangu +1. Ket nyingi +2. Ket kit chuech mari +3. Ket iki +4. Ket kumaidake +5. Ket gikmaiuso +6. Ng'i nyanonro mara +0. Dog chien","CON Wasifu wangu +1. Maqa kekhai +2. Naam dira mo dubr +3. Gan kekhai +4. Fulaa itgalt kai +5. Mih kai +6. Angalia wasifu wako +0. Dheebi" +display_user_metadata,"CON Your details are: +Name: %{full_name} +Gender: %{gender} +Age: %{age} +Location: %{location} +You sell: %{products} +0. Back","CON Wasifu wako una maelezo yafuatayo: + Jina: %{full_name} + Jinsia: %{gender} + Umri: %{age} + Eneo: %{location} + Unauza: %{products} +0. Rudi","CON Profile/Wasifu waku wina maelesyo aa: + Isyitwa: %{full_name} + Jinsia yaku/gender: %{gender} + Miaka yaku: %{age} + Utui/location yaku: %{location} + Syindu ila uta: %{products} +0. Syoka itina","CON Maundu maku mena rugano ruru: + Maretwa: %{full_name} + Mutumia kana muthuri: %{gender} + Miaka : %{age} + Kuria uikaraga : %{location} + Kiria uendagia : %{products} +0. Coka thutha","CON Malagizo gako gana moro uthuwizirao: + Dzina: %{full_name} + Umuche ama Mulume: %{gender} + Umuri: %{age} + Umbolako: %{location} + Miyo uguzayo: %{products} +0. Uya nyuma","CON Nyanonro mari en: + Nying: %{full_name} + Kit chuech: %{gender} + Iga: %{age} + Kumidake: %{location} + Gima iuso: %{products} +0. Dog chien","CON Wasifu wako una maelezo yafuatayo: + JinaMakha: %{full_name} + Jinsia: %{gender} + Gan: %{age} + Fulaa : %{location} + Maan gurgurt: %{products} +0. Dheebi" +select_preferred_language,"CON Choose language: +%{first_language_set} + +0. Back +11. Next +00. Exit","CON Chagua lugha: +%{first_language_set} + +0. Rudi +11. Mbele +00. Ondoka","CON Sakua luka +1. Kisungu +2. Kiswahili +3. Kikamba +0. Syoka itina","CON Caghura ruthiomi +1. Githungu +2. Githweri +0. Coka","CON Tsagula Luga +1. Kizungu +2. Kiswahili +0. Uya nyuma","CON Yier dhok +1. Dho Ngere +2. Dho Oswayo +0. Dog chien","CON Chagua lugha +1. Afaan ferenji +2. Afaan kiswahili +0. Dheebi" +retry_pin_entry,"CON Incorrect PIN entered,please try again. You have %{remaining_attempts} attempts remaining. +0. Back","CON Nambari uliyoweka si sahihi, jaribu tena. Una majaribio %{remaining_attempts} yaliyobaki. +0. Rudi","CON Namba ila wekiya iyaile, tata kwikia ingi. Tata mala %{remaining_attempts} nimo matyele. +0. Itina","CON Namba uikirite ti njega, geria ringi.Ni maita %{remaining_attempts} matigarete. +0. Gucoka thutha","CON Nambari fitse urioika seyo, jeza kaheri. Usere Majezo %{remaining_attempts} Gaserego. +0. Uya nyuma","CON Namba miketo oknikare, tem kendo. Idong gi temo di %{remaining_attempts} modong. +0. Chien","CON Namba at keket suninit,laal amalle.Nafaas kaitdheebit %tanaataf +0. Dheebi" +pin_management,"CON Pin options +1. Change PIN +2. Reset PIN +3. Guard PIN +0. Back","CON Pin options +1. Badilisha nambari yangu ya siri +2. Tuma ombili la kubadilisha nambari ya siri +3. Linda nambari ya siri +0. Rudi",,,,, +enter_current_pin.first,"CON Enter current PIN. +0. Back","CON Weka nambari ya siri. +0. Rudi","CON Ikia namba yaku ya siri. +0. Syoka itina","CON Ekera namba ya thiri +0. Coka thutha","CON Ika namba fitse. +0. Uya Nyuma","CON Ket nambani mopondo. +0. Dog chien","CON Namba ka namii imben kekhai +0. Dheebi" +enter_current_pin.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +enter_new_pin,"CON Enter your new four number PIN +0. Back","CON Weka nambari ya siri mpya +0. Rudi","CON Ikia namba yaku ya siri ila yumbya +0. Syoka itina","CON Ekera namba njeru ya thiri +0. coka thutha","CON Ika namba fitse mbisha +0. Uya nyuma","CON Ket namba mopondo maanyien. +0. Dog chien","CON Namba hareti ka namii imben kekhai +0. Dheebi" +new_pin_confirmation,"CON Enter your new four number PIN again +0. Back","CON Weka nambari yako ya siri tena +0. Rudi","CON Ikia namba yaku ya siri ingi +0. Syoka itina","CON Ekera namba yaku ya thiri renge +0. Coka thutha","CON Uyira kuika lwaphiri +0. Uya Nyuma","CON Ket nambani mopondo kendo +0. Dog chien","CON Namba hareti ka namii imben kekhai amalle +0. Dheebi" +reset_guarded_pin,"CON Enter phone number you are the guardian to reset their pin +0. Back","CON Weka nambari ya simu ili kutuma ombi la kubalisha nambari ya siri. +0. Rudi",,,,, +reset_guarded_pin_authorization.first,"CON Enter YOUR pin to confirm %{guarded_account_information}'s reset +0. Back","CON Weka nambari YAKO ya siri ili kudhibitisha ombi la kubadilisha nambari ya siri ya %{guarded_account_information}. +0. Rudi",,,,, +reset_guarded_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},,,,, +exit_pin_reset_initiated_success,"CON Success: You have initiated a PIN reset for %{guarded_account_information} +0. Back +9. Exit","CON Ombi lako la kubadili nambari ya siri ya %{guarded_account_information} limetumwa. +0. Rudi +9. Ondoka",,,,, +exit_not_authorized_for_pin_reset,"CON Failure: You are not authorized to reset that PIN. You must be a guardian! +0. Back +9. Exit","CON Huruhusiwi kutuma ombi la kubadilisha nambari ya siri. +0. Rudi +9. Ondoka",,,,, +guard_pin,"CON Pin guard +1. View guardians +2. Add guardian +3. Remove guardian +0. Back","CON Linda nambari ya siri +1. Walinzi wa namabari ya siri +2. Ongeza mlinzi +3. Ondoa mlinzi +0. Rudi",,,,, +guardian_list_pin_authorization.first,"CON Enter your pin to view set guardians +0. Back","CON Weka nambari yako ya siri ili kuona walinzi uliowaongeza +0. Rudi",,,,, +guardian_list_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},,,,, +guardian_list,"CON %{guardians_list} +0. Back +9. Exit","CON %{guardians_list} +0. Rudi +9. Ondoka",,,,, +add_guardian,"CON Enter phone number to add as pin reset guardian +0. Back","CON Weka nambari ya simu ili kuongeza mlinzi +0. Rudi",,,,, +add_guardian_pin_authorization.first,"CON Enter your pin to add %{guardian_information} as your PIN reset guardian +0. Back","CON Weka nambari YAKO ya siri ili kumwongeza %{guardian_information} kama mlinzi +0. Rudi",,,,, +add_guardian_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},,,,, +exit_guardian_addition_success,"CON Success: %{guardian_information} can now reset your PIN +0. Back +9. Exit","CON Ombi lako la kumwongeza: %{guardian_information} kama mlinzi limefanikiwa +0. Rudi +9. Ondoka",,,,, +exit_invalid_guardian_addition,"CON %{error_exit} +0. Back +9. Exit","CON %{error_exit} +0. Rudi +9. Ondoka",,,,, +remove_guardian,"CON Enter phone number to revoke guardianship: +0. Back","CON Weka nambari ya simu ili kuondoa mlinzi +0. Rudi",,,,, +remove_guardian_pin_authorization.first,"CON Enter your pin to remove %{guardian_information} as your PIN reset guardian +0. Back","CON Weka nambari YAKO ya siri ili kumwondoa %{guardian_information} kama mlinzi +0. Rudi",,,,, +remove_guardian_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},,,,, +exit_guardian_removal_success,"CON Success: %{guardian_information} PIN reset guardianship is revoked +0. Back +9. Exit","CON Ombi lako la kumwondoa: %{guardian_information} kama mlinzi limefanikiwa +0. Rudi +9. Ondoka",,,,, +exit_invalid_guardian_removal,"CON %{error_exit} +0. Back +9. Exit","CON %{error_exit} +0. Rudi +9. Ondoka",,,,, +transaction_pin_authorization.first,"CON %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}. +Please enter your PIN to confirm. +0. Back","CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}. +Tafadhali weka nambari yako ya siri kudhibitisha. +0. Rudi","CON %{recipient_information} nukwata %{transaction_amount} %{token_symbol} kuma kwa %{sender_information}. +Tafadhali ikia namba yaku ya siri kuvitukithya. +0. Syoka itina","CON %{recipient_information} akuamukira %{transaction_amount} %{token_symbol} kuuma kwa %{sender_information}. +Ekera namba yaku ya thiri kuetekeria. +0. Coka thutha","CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}. +Unavoywa kuika nambayo fitse kugeluza. +0. Uya nyuma.","CON %{recipient_information} dhiyudo %{transaction_amount} %{token_symbol} kowuok kuom %{sender_information}. +Kiyie to ket nambani mopondo mondo iyie: +0. Dog chien","CON %{recipient_information} in argad%{transaction_amount} %{token_symbol} ir %{sender_information}. +Namba ka namii imben kekhai +0. Dheebi" +transaction_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +display_metadata_pin_authorization.first,"CON Please enter your PIN +0. Back","CON Tafadhali weka PIN yako +0. Rudi","CON Tafadhali ikia PIN yaku +0. Syoka itina","CON Ekera pin yaku +0. coka thutha","CON Unavoywa kuika namayo fitswe +0. Uya Nyuma","CON Kiyie to ket nambani mopondo +0. Dog chien","CON Namba ka namii imben kekhai +0. Dheebi" +display_metadata_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +account_balances_pin_authorization.first,"CON Please enter your PIN to view balances +0. Back","CON Tafadhali weka PIN yako kuona salio. +0. Rudi","CON Tafadhali ikia PIN yaku kwona utyalo. +0. Syoka itina","CON Ekera pin yaku kuona matigari maku +0. Coka ","CON Unavoywa namba fitswe kulola Sazo. +0. Uya nyuma","CON Kiyie to ket nambani mopondo mondo ine modong' +0. Dog chien","CON Namba ka namii imbeen kekhai ak balansi kake lalt +0. Dheebi" +account_balances_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +account_statement_pin_authorization.first,"CON Please enter your PIN to view statement +0. Back","CON Tafadhali weka PIN yako kuona taarifa ya matumizi. +0. Rudi","CON Tafadhali ikia PIN yaku kwona welesyo wa utumii. +0. Syoka itina","CON Ekera pin yaku kuona rugano rwa mahuthira maku +0. coka thutha","CON Unavoywa namba fitswe kupata maerezo ga mahumizi Gako. +0. Uya Nyuma","CON Kiyie to ket nambani mar siri mondo ine chenro mar tiyo. +0. Dog chien","CON Tafadhali weka PIN yako kuona taarifa ya matumizi. +0. Dheebi" +account_statement_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +name_edit_pin_authorization.first,"CON Please enter your PIN +0. Back","CON Tafadhali weka PIN yako +0. Rudi","CON Tafadhali ikia PIN yaku +0. Syoka itina","CON Ekera pin yaku +0. coka thutha","CON Unavoywa namba fitse +0. Uya Nyuma","CON Ket nambani mopondo +0. Dog chien","CON Namba ka namii imben kekhai +0. Dheebi" +name_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +dob_edit_pin_authorization.first,"CON Please enter your PIN +0. Back","CON Tafadhali weka PIN yako +0. Rudi","CON Tafadhali ikia PIN yaku +0. Syoka itina","CON Ekera namba yaku ya thiri +0. Rudi","CON Unavoywa namba fitswe +0. Uya nyuma","CON Kiyie to ket nambani mopondo +0. Dog chien","CON Namba kake ka namii imbeen kekhai +0. Dheebi" +dob_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +gender_edit_pin_authorization.first,"CON Please enter your PIN +0. Back","CON Tafadhali weka PIN yako +0. Rudi","CON Tafadhali ikia PIN yaku +0. Syoka itina","CON Ekera namba yaku ya thiri +0. coka thutha","CON Unavoywa namba fitswe +0. Uya nyuma","CON Kiyie to ket nambani mopondo +0. Dog chien","CON Namba kake ka namii imbeen kekhai +0. Dheebi" +gender_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +location_edit_pin_authorization.first,"CON Please enter your PIN +0. Back","CON Tafadhali weka PIN yako +0. Rudi","CON Tafadhali ikia PIN yaku +0. Syoka itina","CON Ekera namba yaku ya thiri +0. Coka thutha","CON Unavoywa namba fitswe +0. Uya nyuma","CON Kiyie to ket nambani mopondo +0. Dog chien","CON Namba kake ka namii imbeen kekhai +0. Dheebi" +location_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +products_edit_pin_authorization.first,"CON Please enter your PIN +0. Back","CON Tafadhali weka PIN yako +0. Rudi","CON Tafadhali ikia PIN yaku +0. Syoka itina","CON Ekera namba yaku ya thiri +0. Coka thutha","CON Unavoywa namba fitswe +0. Uya nyuma","CON Kiyie to ket nambani mopondo +0. Dog chien","CON Namba kake ka namii imbeen kekhai +0. Dheebi" +products_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry} +account_balances.available_balance,"CON Your balances are as follows: +balance: %{available_balance} %{token_symbol} +0. Back","CON Salio zako ni zifuatazo: + salio: %{available_balance} %{token_symbol} +0. Rudi","CON Utyalo waku ni uu: + utyalo: %{available_balance} %{token_symbol} +0. Syoka itina","CON Matigari maku ni maya: + matigari: %{available_balance} %{token_symbol} +0. coka","CON Masazogo nidzavivyo gatuwiravyo: + Sazo: %{available_balance} %{token_symbol} +0. Uya Nyuma","CON Dong mari en: + Dong: %{available_balance} %{token_symbol} +0. Dog chien","CON Balansi kake akan + salio: %{available_balance} %{token_symbol} +0. Dheebi" +account_balances.with_fees,"CON Your balances are as follows: +balances: %{available_balance} %{token_symbol} +fees: %{tax} %{token_symbol} +0. Back","CON Salio zako ni zifuatazo: + salio: %{available_balance} %{token_symbol} + ushuru: %{tax} %{token_symbol} +0. Rudi","CON Utyalo waku ni uu: + utyalo: %{available_balance} %{token_symbol} + tax/ushuru: %{tax} %{token_symbol} +0. Syoka itina","CON Matigari maku ni maya: + matigari: %{available_balance} %{token_symbol} + ushuru: %{tax} %{token_symbol} +0. coka thutha","CON Masazogo ni gatuwirago: + Masazo: %{available_balance} %{token_symbol} + Ushuuru: %{tax} %{token_symbol} +0. Uya nyuma","CON Dong mari en: + Dong: %{available_balance} %{token_symbol} + osuru: %{tax} %{token_symbol} +0. Dog chien","CON Balansi kake akan + salio: %{available_balance} %{token_symbol} + ushuru: %{tax} %{token_symbol} +0. Dheebi" +account_balances.with_rewards,"CON Your balances are as follows: +balance: %{available_balance} %{token_symbol} +fees: %{tax} %{token_symbol} +rewards: %{bonus} %{token_symbol} +0. Back","CON Salio zako ni zifuatazo: + salio: %{available_balance} %{token_symbol} + ushuru: %{tax} %{token_symbol} + tuzo: %{bonus} %{token_symbol} +0. Rudi","CON Utyalo waku ni uu: + Utyalo: %{available_balance} %{token_symbol} + Tax/ushuru: %{tax} %{token_symbol} + muthinzio: %{bonus} %{token_symbol} +0. Syoka itina","CON Salio zako ni zifuatazo: + salio: %{available_balance} %{token_symbol} + ushuru: %{tax} %{token_symbol} + tuzo: %{bonus} %{token_symbol} +0. coka thutha","CON Sazoro ni rituwiraro: + Sazo: %{available_balance} %{token_symbol} + Ushuuru: %{tax} %{token_symbol} + Zawadi: %{bonus} %{token_symbol} +0. Uya Nyuma","CON Dong mari en: + Dong: %{available_balance} %{token_symbol} + osuru: %{tax} %{token_symbol} + mich: %{bonus} %{token_symbol} +0. Dog chien","CON Balansi kake akan + salio: %{available_balance} %{token_symbol} + ushuru: %{tax} %{token_symbol} + tuzo: %{bonus} %{token_symbol} +0. Dheebi" +first_transaction_set,"CON %{first_transaction_set} + +0. Back +11. Next +00. Exit","CON %{first_transaction_set} + +0. Rudi +11. Mbele +00. Ondoka","CON %{first_transaction_set} +1. Mbee +00. Ondoka","CON %{first_transaction_set} +1. Mbere +00. uma","CON %{first_transaction_set} +1. Mbere +00. Uka","CON %{first_transaction_set} +1. Nyime +00. Wuogi","CON %{first_transaction_set} +1. Dhuur +00. Bai" +middle_transaction_set,"CON %{middle_transaction_set} +11. Next +22. Previous +00. Exit","CON %{middle_transaction_set} +11. Mbele +22. Rudi +00. Ondoka","CON %{middle_transaction_set} +1. Mbee +2. Itina +00. Ondoka","CON %{middle_transaction_set} +1. Mbere +2. coka thutha +00. Uma","CON %{middle_transaction_set} +1. Mbere +2. Uya nyuma +00. Uka","CON %{middle_transaction_set} +1. Nyime +2. Dog chien +00. Wuogi","CON %{middle_transaction_set} +1. Dhuur +2. Dheebi +00. Bai" +last_transaction_set,"CON %{last_transaction_set} +22. Previous +00. Exit","CON %{last_transaction_set} +22. Rudi +00. Ondoka","CON %{last_transaction_set} +2. Itina +00. Ondoka","CON %{last_transaction_set} +2. Coka thutha +00. Uma","CON %{last_transaction_set} +2. Uya Nyuma +00. Uka","CON %{last_transaction_set} +2. Dog chien +00. Wuogi","CON %{last_transaction_set} +2. Dhuur +00. Bai" +exit,END Thank you for using the service.,END Asante kwa kutumia huduma.,END Ni muvea kwa kutumia huduma ii.,END Thegio ni kuhuthira mutabo,END. Namvera kwa mahumizi ga ii huduma.,END Erokamano kuom tiyo kodwa.,END Asante kwa kutumia huduma. +exit_invalid_request,END Invalid request.,END Chaguo si sahihi.,END Usakuo waku uyaile.,END shaguro riaku ti riega,END. Tsagulo karisawa sawa,END Yiero okni kare,END Ka at chaquad suninit +exit_invalid_menu_option,"CON Invalid menu option. For help,call %{support_phone}. +00. Back +99. Exit","CON Chaguo lako sio sahihi. Kwa usaidizi piga simu %{support_phone} +00. Rudi +99. Ondoka","CON Usakuo waku uyaile. Kwa utethyo kuna simu %{support_phone} +00. Itina +99. Ondoka","CON Shaguro riaku ti riega.Kwa uteithio hura %{support_phone} +00. Coka thutha +99. Uma","CON Tsaguloro karisawa sawa. Kuavizwa piga %{support_phone} +00. Uya Nyuma +99. Uka","CON Yiero ni oknikare. Kuom kony go simu e %{support_phone} +00. Dog chien +99. Wuogi","CON Ka at chaqui suninit.qarqarsa simu dai%{support_phone} +00. Dheebi +99. Bai" +exit_invalid_input,"CON Invalid input. Nothing selected +00. Back +99. Exit","CON Chaguo lako halipatikani. Hakuna kilichochaguliwa. +00. Rudi +99. Ondoka","CON Usakuo waku wikwonekana.Vaii kindu kisakue. +00. Itina +99. Ondoka","CON Shaguro riaku ritironekana. Hatiri kindu washaghura. +00. Coka thutha +99. Uma","CON Tsaguloro karipatikana. Kakuna Kutsagurire chochosi. +00. Uya nyuma +99. Uka","CON Yiero ni okyudre. Onge gima iyiero. +00. Dog chien +99. Wuogi","CON Ka at chaguad injirt. oo +00. dheebi +99. Bai" +exit_pin_blocked,"END Your PIN has been blocked. For help, please call %{support_phone}.",END PIN yako imefungwa. Kwa usaidizi tafadhali piga simu %{support_phone}.,END PIN yaku niyavingwa. Kutethwa kuna simu ino %{support_phone}.,END PIN yaku niyahingwo. Kwa uteithio hura thimu %{support_phone}.,END. Namba fitse yakwako ifungwa. Kwa kuavizwa unaangwa upige simu %{support_phone}.,END Nambani mopondo olor. Kuom kony go simu e %{support_phone}.,END Pin kake yahidat. Qarqarsa simu dai %{support_phone}. +exit_invalid_pin,"END The PIN you have entered is invalid. PIN must consist of 4 digits. For help, call %{support_phone}.",END PIN uliyobonyeza sio sahihi. PIN lazima iwe na nambari nne. Kwa usaidizi piga simu %{support_phone}.,END PIN ila wekia iyaile. Ni lasima PIN ithiwe na namba inya. Kutethwa kuna namba ii %{support_phone}.,END PIN iria wekera tii njega. PIN nomoka ikorwo na namba inya. Kwa uteithio hura thimu %{support_phone}.,"END Namba fitse urohofya seyo, kaisawa. Namba fitswe inamalwa ikale na namba nee. Kwa kuvizwa, piga simu%{support_phone}.",END. Namba mopondo miketo oknikare. Nyaka obed gi nembni ang'wen. Kuom kony go simu e %{support_phone}.,END PIN ka at keket suninit. PIN Pin namba afuuri tatatu. Qarqarsa simu dai %{support_phone}. +exit_invalid_new_pin,"END The PIN you have entered is invalid. PIN must be different from your current PIN. For help, call %{support_phone}.",END PIN uliyobonyeza sio sahihi. PIN lazima iwe tofauti na pin yako ya sasa. Kwa usaidizi piga simu %{support_phone}.,END PIN ila wekia iyaile. PIN ni lasima ithiwe tofauti na pin yaku ya oyu. Kutethwa kuna namba ii %{support_phone}.,END PIN uria wekera ti njega. PIN nomohaka ikorwo na namba ndiganu na ya riu . Kwa uteithio hora thimu %{support_phone}.,END Namba fitswe uriohopya siyo ya karakara. Namba fitswe inahenzekana ikale itofauti na uhumirayo vivi. Kwa maavizo piga simu %{support_phone}.,END Namba mopondo miketo oknikare. Nyaka obed mopogore gi nambani mopondo masani. Kuom kony gochi e %{support_phone}.,END PIN ka at keket suninit.Pin kake walinfakaatin.Qarqars simu dai %{support_phone}. +exit_pin_mismatch,"END The new PIN does not match the one you entered. Please try again. For help, call %{support_phone}.",END PIN mpya na udhibitisho wa pin mpya hazilingani. Tafadhali jaribu tena. Kwa usaidizi piga simu %{support_phone}.,END PIN yumbya na uhakikisho wa pin yumbya syivwanene. Tafadhali tata ingi. Kutethwa kuna simu %{support_phone}.,END PIN njeru na pin ya guetekeria shitira hianana . Geria ringi. Kwa uteithio hora thimu %{support_phone}.,END Namba fitse uzdoinjiza kaikara kara na uriyohopya laphiri. Unavoywa ujeze kaheri. Kwa kuavizwa piga simu %{support_phone}.,GIKO. Namba mopondo miketo opogore gi manikuongo keto. Kiyie to ket kendo. Kuom kony gochi e %{support_phone}.,END PIN mpya na udhibitisho wa pin mpya hazilingani. Pin hareti ka at kekeet walinfakan. it dheebi amaale.Qarqars simu dai%{support_phone}. +exit_invalid_recipient,"CON Recipient's phone number is not registered or is invalid: +00. Retry +99. Exit","CON Mpokeaji wa nambari hapatikani au sio sahihi. +00. Jaribu tena +99. Ondoka","CON Mukwati wa namba ndokwatikana kana ii namba iyaile kana ti sahihi. +00. Tata ingi +99. Ondoka","CON Mpokeaji wa nambari hapatikani au sio sahihi.Namba ya mutumirwo ndiranyitikana kana ti njega +00. Geria ringi +99. Uma","CON Muphokezi wa namba kapatikana ama namba kai karakara. +00. Jeza Kaheri +99. Uka","CON Jayuto mar nambani okyudre kata oknikare. +00. Tem kendo +99. Wuogi","CON Mpokeaji wa nambari hapatikani au sio sahihi. +00. +99. Ondoka" +exit_successful_transaction,"CON Your request has been sent. %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}. +00. Back +99. Exit","CON Ombi lako limetumwa. %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}. +00. Rudi +99. Ondoka","CON Woni waku niwatumwa. %{recipient_information} nukupokea %{transaction_amount} %{token_symbol} kuma kwa %{sender_information}. +00. Itina +99. Ondoka","CON Mahoya maku nimatomwo. %{recipient_information} akuamukira%{transaction_amount} %{token_symbol} kuma kwa %{sender_information}. +00. Coka +99. Uma","CON Mavoyogo gahumwa. %{recipient_information} undaphokera %{transaction_amount} %{token_symbol} kuombola kwa %{sender_information}. +00. Uya Nyuma +99. Uka","CON Kwayo ni oseor. %{recipient_information} oboyudo %{transaction_amount} %{token_symbol} kowuok kuom %{sender_information}. +00. Dog chien +99. Wuogi","CON Qarqar kake yaergad. %{recipient_information} inargat %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}. +00. Dheebi +99. Bai" +exit_insufficient_balance,"CON Payment of %{amount} %{token_symbol} to %{recipient_information} has failed due to insufficient balance. +Your Sarafu-Network balances is: %{token_balance} +00. Back +99. Exit","CON Malipo ya %{amount} %{token_symbol} kwa %{recipient_information} halijakamilika kwa sababu salio lako haitoshi. +Akaunti yako ya Sarafu ina salio ifuatayo: %{token_balance} +00. Rudi +99. Ondoka","CON Ndivi ya %{amount} %{token_symbol} kwa %{recipient_information} inavitukithwa nundu utyalyo waku ni munini. +Kinandu chaku cha Sarafu kina utyalo uu: %{token_balance} +00. Itina +99. Ondoka","CON Marehi ma %{amount} %{token_symbol} kwa %{recipient_information} matinarekereka tondu matigari maku matiraigana. +Akaunti yako ya Sarafu ina salio ifuatayo: %{token_balance} +00. Coka +99. Uma","CON Maripho ga %{amount} %{token_symbol} kwa %{recipient_information} Karidzangwe kukamirika Kwaukala sazoro Karitosha. +Akauntiyo vivi ina sazo dza rituwiranavyo: %{token_balance} +00. Uya nyuma +99. Uka","CON Chudo mar %{amount} %{token_symbol} kuom %{recipient_information} okotieki nikech dong ni okrom. +Akaont ni mar Sarafu ni gi dong mar: %{token_balance} +00. Dog chien +99. Wuogi","CON Malipo ka%{amount} %{token_symbol} kwa %{recipient_information} Inkamilikee balansi kake ingau +Akaunti kake balansi akan kabd: %{token_balance} +00. Dheebi +99. Bai" +exit_successful_token_selection,"CON Success! %{token_symbol} is your active Sarafu. +00. Back +99. Exit","CON Chaguo lako limekamilika, %{token_symbol} ni sarafu itakayotumika. +00. Rudi +99. Ondoka",,,,, +invalid_service_code,Please dial %{valid_service_code} to access Sarafu Network,Bonyeza %{valid_service_code} kutumia mtandao wa Sarafu,Vinyia %{valid_service_code} kutumia mutandao wa Sarafu,Hihinya%{valid_service_code} kuhudhira mutabo wa Sarafu,Hofya %{valid_service_code} Kuhumira Mutandao wa sarafu,Dii %{valid_service_code} mondo iti gi Sarafu,Bonyeza %{valid_service_code} kutumia mtandao wa Sarafu +help,"CON For assistance call %{support_phone} +00. Back +99. Exit","CON Kwa usaidizi piga simu %{support_phone} +0. Rudi +9. Ondoka","CON Kwa utethyo kuna simu %{support_phone} +0. Itina +9. Ondoka","CON Kwa uteithio hora thimu %{support_phone} +0. Coka +9. Uma","CON Kwa Kuavizwa piga simu %{support_phone} +0. Uya nyuma +9. Uka","CON Kuom kony go simu e %{support_phone} +0. Dog chien +9. Wuogi","CON Qarqars simu dai%{support_phone} +0. Dheebi +9. Bai" +complete,"CON Your request has been sent. You will receive an SMS shortly. +00. Back +99. Exit","CON Ombi lako limetumwa. Utapokea uthibitishaji wa SMS kwa muda mfupi. +00. Rudi +99. Ondoka","CON Woni waku niwatumwa. Nukwata SMS ya kwonya ivinda ite yasa. +00. Itina +99. Ondoka","CON Mahoya maku nimatomwo. Niukuamukira SMS ya guitikirika ihinda ikuhi . +00. Coka +99. Uma","CON Vyoyoro rihumwa. Undaphokera Uthibitishaji wa SMS kwa muda mufuhi. +00. Uya nyuma +99. Uka","CON Kwayo ni oseor. Iboyudo mesej mar ote ni bang' saa matin. +00. Dog chien +99. Wuogi","CON Qarqars kake yaergad. Utapokea uthibitishaji wa SMS kwa muda mfupi. +00. Dheebi +99. Bai" +account_creation_prompt,END Your account is being created. You will receive an SMS when your account is ready.,END Akaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.,END Akaunti yako ya Sarafu yendeye usovwa. Nukwata SMS akaunti yaku yasovwa.,END Akaunti yaku ya Sarafu niiraharirio.Niugutumirwo SMS akauti yaku ya rikio kuharirio,END Akauntiyo ya sarafu idzikoni. Undaphokera ujumbe wa SMS ichikala tayari.,END Akaont ni mar Sarafu iloso. Iboyudo mesej ka akaont ni otieki.,END Akaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari. +initial_middle_language_set,"CON Choose language: +%{middle_language_set} + +11. Next +22. Previous +00. Exit","CON Chagua lugha: +%{middle_language_set} + +11. Mbele +22. Rudi +00. Ondoka",,,,, +initial_last_language_set,"CON Choose language: +%{last_language_set} + +22. Previous +00. Exit","CON Choose language: +%{last_language_set} + +22. Rudi +00. Ondoka",,,,, +middle_language_set,"CON Choose language: +%{middle_language_set} + +11. Next +22. Previous +00. Exit","CON Chagua lugha: +%{middle_language_set} + +11. Mbele +22. Rudi +00. Ondoka",,,,, +last_language_set,"CON Choose language: + +%{last_language_set} +22. Previous +00. Exit","CON Choose language: +%{last_language_set} + +22. Rudi +00. Ondoka",,,,, \ No newline at end of file