Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add support for updating multiplatform projects from the translation source #35

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "Python 3",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pip3 install --user -r requirements.txt",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.debugpy",
"eamodio.gitlens",
"vscode-icons-team.vscode-icons",
"github.copilot",
"github.copilot-chat"
]
}
},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "vscode"
}
18 changes: 0 additions & 18 deletions .tx/config
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,3 @@ file_filter = news-media-scan/<lang>/description.xlf
source_file = news-media-scan/en/description.xlf
source_lang = en
type = XLIFF

[o:otf:p:ooni-explorer:r:website]
file_filter = explorer/<lang>/strings.json
source_file = explorer/en/strings.json
source_lang = en
type = KEYVALUEJSON

[o:otf:p:ooni-run:r:website]
file_filter = run/<lang>/strings.json
source_file = run/en/strings.json
source_lang = en
type = KEYVALUEJSON

[o:otf:p:ooni-test-lists-editor:r:website]
file_filter = test-lists-editor/<lang>/strings.json
source_file = test-lists-editor/en/strings.json
source_lang = en
type = KEYVALUEJSON
112 changes: 112 additions & 0 deletions convert-from-app-string.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import argparse
import json
import csv
import sys
import re
import xml.etree.ElementTree as ET

assert sys.version_info >= (3, 6), "Python >= 3.6 is required"

def parse_args():
p = argparse.ArgumentParser(description='translations: CSV to KEYVALUEJSON')
p.add_argument('--source', metavar='PATH', help='path to multiplatform source', required=True)
p.add_argument('--destination', metavar='PATH', help='path to multiplatform source', required=True)
p.add_argument('--json', metavar='PATH', help='path to multiplatform source', required=True)
p.add_argument('--app', metavar='STRING', help='app', required=True)
p.add_argument('--base', metavar='PATH', help='ooni base json input path', required=False)
p.add_argument('--lang', metavar='STRING', help='language', required=True)
opt = p.parse_args()
return opt

def load_json(in_path):
with open(in_path) as in_file:
return json.load(in_file)

def load_xml_keys(in_path):
tree = ET.parse(in_path)
root = tree.getroot()
result = {}
for string in root.findall('string'):
key = string.get('name')
value = string.text
result[key] = value
return result

def dict_to_android_xml(d, out_path, app):

resources = ET.Element('resources')

comment = ET.Comment('This file is generated from https://github.com/ooni/translations. Please do not modify unless you know what youre doing')
resources.insert(0, comment)

for key, text in d.items():
key = key.replace('.', '_')
if key == "Dashboard_Runv2_Overview_Description":
text = text.replace("\\n\\n%s", "")
# replace first `%s` with `%1$s` and second `%s` with `%2$s`
text = text.replace("%s", "%1$s", 1)
text = text.replace("%s", "%2$s", 1)

if key == "Dashboard_Experimental_Overview_Paragraph":
# replace first `{experimental_test_list}` with `%1$s`
text = text.replace("{experimental_test_list}", "%1$s", 1)

if key == "Settings_Websites_Categories_Description":
# replace first `{Count}` with `%1$s`
text = text.replace("{Count}", "%1$s", 1)

if key == "Modal_ResultsNotUploaded_Uploading":
# replace first `{testNumber}` with `%1$s`
text = text.replace("{testNumber}", "%1$s", 1)

if key == "Modal_ReRun_Websites_Title":
# replace first `{experimental_test_list}` with `%1$s`
text = text.replace("{websitesNumber}", "%1$s", 1)

if key == "Modal_UploadFailed_Paragraph":
# replace first `{numberFailed}` with `%1$s` and second `{totalUploads}` with `%2$s`
text = text.replace("{numberFailed}", "%1$s", 1)
text = text.replace("{totalUploads}", "%2$s", 1)

if key == "Settings_AutomatedTesting_RunAutomatically_Number":
# replace first `{testsNumber}` with `%1$s`
text = text.replace("{testsNumber}", "%1$s", 1)

if key == "Settings_AutomatedTesting_RunAutomatically_DateLast":
# replace first `{testDate}` with `%1$s`
text = text.replace("{testDate}", "%1$s", 1)

if app == 'news-media-scan' and key == "Modal_EnableNotifications_Paragraph":
# replace `OONI Probe` with `News Media Scan`
text = text.replace("OONI Probe", "News Media Scan", 1)

string_element = ET.SubElement(resources, 'string', name=key)
string_element.text = text

tree = ET.ElementTree(resources)
ET.indent(tree)
tree.write(out_path, encoding='utf-8', xml_declaration=True)

def main():
opt = parse_args()
source_keys = load_xml_keys(opt.source).keys()
json_data = load_json(opt.json)

filtered_data = {}
for key, text in json_data.items():
key = key.replace('.', '_')
if key in source_keys:
filtered_data[key] = text

if opt.base is not None:
base_data = load_json(opt.base)

for key, text in base_data.items():
key = key.replace('.', '_')
if key == "Modal_EnableNotifications_Paragraph":
filtered_data[key] = text

dict_to_android_xml(filtered_data, opt.destination, opt.app)

if __name__ == "__main__":
main()
184 changes: 184 additions & 0 deletions import-ooni-descriptors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import argparse
import requests
import json

BASE_URL = "https://api.dev.ooni.io"

TESTS = [
{
"name": "Test_Websites_Fullname",
"short_description": "Dashboard_Websites_Card_Description",
"description": "Dashboard_Websites_Overview_Paragraph",
"nettests": [
{
"test_name": "web_connectivity",
}
],
"icon": "OoniWebsites",
"color": "#4c6ef5",
},
{
"name": "Test_InstantMessaging_Fullname",
"short_description": "Dashboard_InstantMessaging_Card_Description",
"description": "Dashboard_InstantMessaging_Overview_Paragraph",
"nettests": [
{
"test_name": "whatsapp",
},
{
"test_name": "telegram",
},
{
"test_name": "facebook_messenger",
},
{
"test_name": "signal",
},
],
"icon": "OoniInstantMessaging",
"color": "#15aabf",
},
{
"name": "Test_Circumvention_Fullname",
"short_description": "Dashboard_Circumvention_Card_Description",
"description": "Dashboard_Circumvention_Overview_Paragraph",
"nettests": [
{
"test_name": "psiphon",
},
{
"test_name": "tor",
},
],
"icon": "OoniCircumvention",
"color": "#e64980",
},
{
"name": "Test_Performance_Fullname",
"short_description": "Dashboard_Performance_Card_Description",
"description": "Dashboard_Performance_Overview_Paragraph",
"nettests": [
{
"test_name": "ndt",
},
{
"test_name": "dash",
},
{
"test_name": "http_header_field_manipulation",
},
{
"test_name": "http_invalid_request_line",
},
],
"icon": "OoniPerformance",
"color": "#be4bdb",
},
{
"name": "Test_Experimental_Fullname",
"short_description": "Dashboard_Experimental_Card_Description",
"description": "Dashboard_Experimental_Overview_Paragraph",
"nettests": [
{
"test_name": "stunreachability",
},
{
"test_name": "openvpn",
},
{
"test_name": "vanilla_tor",
},
],
"icon": "OoniExperimental",
"color": "#495057",
},
]

def load_json(in_path):
with open(in_path) as in_file:
return json.load(in_file)

def get_item_intl(key, supported_languages):
item_intl = {}
for lang in supported_languages:
item_intl[lang] = get_string_for_label(key, lang)
return item_intl

def get_string_for_label(label,lang="en"):
label = label.replace("_", ".")
json_data = load_json(f"probe-mobile/{lang}/strings.json")
return json_data[label]


def set_auth_token(token):
global AUTH_TOKEN
AUTH_TOKEN = token

def create_link(name, name_intl, short_description, short_description_intl, description, description_intl, expiration_date, nettests,icon,color, author="[email protected]"):
url = f"{BASE_URL}/api/v2/oonirun/links"
headers = {
"Authorization": f"Bearer {AUTH_TOKEN}",
"Content-Type": "application/json"
}
data = {
"name": name,
"name_intl": name_intl,
"short_description": short_description,
"short_description_intl": short_description_intl,
"description": description,
"description_intl": description_intl,
"author": author,
"icon": icon,
"color": color,
"nettests": nettests,
"expiration_date": expiration_date
}
response = requests.post(url, headers=headers, data=json.dumps(data))
return response.json()

def upload_ooni_tests(supported_languages):
for descriptor in TESTS:

link_name = get_string_for_label(descriptor["name"])
name_intl = get_item_intl(descriptor["name"], supported_languages)

description = get_string_for_label(descriptor["description"])
description_intl = get_item_intl(descriptor["description"], supported_languages)

short_description = get_string_for_label(descriptor["short_description"])
short_description_intl = get_item_intl(descriptor["short_description"], supported_languages)

nettests = descriptor["nettests"]
icon = descriptor["icon"]
color = descriptor["color"]

expiration_date = "4000-01-01T00:00:00.000000Z"

response = create_link(
name=link_name,
name_intl=name_intl,
short_description=short_description,
short_description_intl=short_description_intl,
description=description,
description_intl=description_intl,
nettests=nettests,
icon=icon,
color=color,
expiration_date=expiration_date,
)
print(f"Uploaded {link_name}: {response}")

def main():
set_auth_token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MzA4NTk1OTgsImlhdCI6MTczMDg1OTU5OCwiZXhwIjoxNzQ2NDExNTk4LCJhdWQiOiJ1c2VyX2F1dGgiLCJsb2dpbl90aW1lIjoxNzMwODU5NTk4LCJyb2xlIjoiYWRtaW4iLCJhY2NvdW50X2lkIjoiYmU3YmRlNGU3NTAxOTc3MTMzNzhjY2U1M2E0NzUyYjEiLCJlbWFpbF9hZGRyZXNzIjoibm9yYmVsQG9vbmkub3JnIn0.zVs2UJE11HgSUJ5bVbvlZzARTXX5XCF2MWhD-7hZBdo")
opt = parse_args()
upload_ooni_tests(supported_languages=opt.langs)


def parse_args():
p = argparse.ArgumentParser(description='')
p.add_argument('--langs', metavar='str', help='language', nargs='*', required=True)
opt = p.parse_args()
return opt

if __name__ == "__main__":
main()
11 changes: 11 additions & 0 deletions import-ooni-descriptors.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
set -e

source utils.sh

app=${1:-"probe-mobile"}

source supported_languages_mobile.sh $app

python import-ooni-descriptors.py \
--langs ${SUPPORTED_LANGUAGES[@]}
1 change: 0 additions & 1 deletion json-to-android-xml.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import argparse
import json
import cgi
import csv
import sys
import re
Expand Down
Loading