Feature: language config (#27)

* Added language configuration support

Main page now has a dropdown for selecting preferred language of
results.

Refactored config to be its own model with language constants.

* Added more language support

Interface language is now updated using the "hl" arg

Fixed chinese traditional and simplified values

Updated decoding of characters to gb2312

* Updated to use conditional decoding dependent on language

* Updated filter to not rely on valid config to work properly
main
Ben Busby 2020-05-12 17:15:53 -06:00 committed by GitHub
parent f700ed88e7
commit a11ceb0a57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 13 deletions

View File

@ -19,7 +19,7 @@ class Filter:
if config is None: if config is None:
config = {} config = {}
self.near = config['near'] if 'near' in config else None self.near = config['near'] if 'near' in config else ''
self.dark = config['dark'] if 'dark' in config else False self.dark = config['dark'] if 'dark' in config else False
self.nojs = config['nojs'] if 'nojs' in config else False self.nojs = config['nojs'] if 'nojs' in config else False
self.mobile = mobile self.mobile = mobile

0
app/models/__init__.py Normal file
View File

74
app/models/config.py Normal file
View File

@ -0,0 +1,74 @@
class Config:
# Derived from here:
# https://sites.google.com/site/tomihasa/google-language-codes#searchlanguage
LANGUAGES = [
{'name': 'English', 'value': 'lang_en'},
{'name': 'Afrikaans', 'value': 'lang_af'},
{'name': 'Arabic', 'value': 'lang_ar'},
{'name': 'Armenian', 'value': 'lang_hy'},
{'name': 'Belarusian', 'value': 'lang_be'},
{'name': 'Bulgarian', 'value': 'lang_bg'},
{'name': 'Catalan', 'value': 'lang_ca'},
{'name': 'Chinese (Simplified)', 'value': 'lang_zh-CN'},
{'name': 'Chinese (Traditional)', 'value': 'lang_zh-TW'},
{'name': 'Croatian', 'value': 'lang_hr'},
{'name': 'Czech', 'value': 'lang_cs'},
{'name': 'Danish', 'value': 'lang_da'},
{'name': 'Dutch', 'value': 'lang_nl'},
{'name': 'Esperanto', 'value': 'lang_eo'},
{'name': 'Estonian', 'value': 'lang_et'},
{'name': 'Filipino', 'value': 'lang_tl'},
{'name': 'Finnish', 'value': 'lang_fi'},
{'name': 'French', 'value': 'lang_fr'},
{'name': 'German', 'value': 'lang_de'},
{'name': 'Greek', 'value': 'lang_el'},
{'name': 'Hebrew', 'value': 'lang_iw'},
{'name': 'Hindi', 'value': 'lang_hi'},
{'name': 'Hungarian', 'value': 'lang_hu'},
{'name': 'Icelandic', 'value': 'lang_is'},
{'name': 'Indonesian', 'value': 'lang_id'},
{'name': 'Italian', 'value': 'lang_it'},
{'name': 'Japanese', 'value': 'lang_ja'},
{'name': 'Korean', 'value': 'lang_ko'},
{'name': 'Latvian', 'value': 'lang_lv'},
{'name': 'Lithuanian', 'value': 'lang_lt'},
{'name': 'Norwegian', 'value': 'lang_no'},
{'name': 'Persian', 'value': 'lang_fa'},
{'name': 'Polish', 'value': 'lang_pl'},
{'name': 'Portuguese', 'value': 'lang_pt'},
{'name': 'Romanian', 'value': 'lang_ro'},
{'name': 'Russian', 'value': 'lang_ru'},
{'name': 'Serbian', 'value': 'lang_sr'},
{'name': 'Slovak', 'value': 'lang_sk'},
{'name': 'Slovenian', 'value': 'lang_sl'},
{'name': 'Spanish', 'value': 'lang_es'},
{'name': 'Swahili', 'value': 'lang_sw'},
{'name': 'Swedish', 'value': 'lang_sv'},
{'name': 'Thai', 'value': 'lang_th'},
{'name': 'Turkish', 'value': 'lang_tr'},
{'name': 'Ukrainian', 'value': 'lang_uk'},
{'name': 'Vietnamese', 'value': 'lang_vi'},
]
def __init__(self, **kwargs):
self.url = ''
self.lang = 'lang_en'
self.dark = False
self.nojs = False
self.near = ''
for key, value in kwargs.items():
setattr(self, key, value)
def __getitem__(self, name):
return getattr(self, name)
def __setitem__(self, name, value):
return setattr(self, name, value)
def __delitem__(self, name):
return delattr(self, name)
def __contains__(self, name):
return hasattr(self, name)

View File

@ -26,7 +26,7 @@ def gen_user_agent(normal_ua):
return DESKTOP_UA.format(mozilla, linux, firefox) return DESKTOP_UA.format(mozilla, linux, firefox)
def gen_query(query, args, near_city=None): def gen_query(query, args, near_city=None, language='lang_en'):
param_dict = {key: '' for key in VALID_PARAMS} param_dict = {key: '' for key in VALID_PARAMS}
# Use :past(hour/day/week/month/year) if available # Use :past(hour/day/week/month/year) if available
# example search "new restaurants :past month" # example search "new restaurants :past month"
@ -49,6 +49,9 @@ def gen_query(query, args, near_city=None):
if near_city is not None: if near_city is not None:
param_dict['near'] = '&near=' + urlparse.quote(near_city) param_dict['near'] = '&near=' + urlparse.quote(near_city)
# Set language for results (lr) and interface (hl)
param_dict['lr'] = '&lr=' + language + '&hl=' + language.replace('lang_', '')
for val in param_dict.values(): for val in param_dict.values():
if not val or val is None: if not val or val is None:
continue continue
@ -58,12 +61,19 @@ def gen_query(query, args, near_city=None):
class Request: class Request:
def __init__(self, normal_ua): def __init__(self, normal_ua, language='lang_en'):
self.modified_user_agent = gen_user_agent(normal_ua) self.modified_user_agent = gen_user_agent(normal_ua)
self.language = language
def __getitem__(self, name): def __getitem__(self, name):
return getattr(self, name) return getattr(self, name)
def get_decode_value(self):
if 'lang_zh' in self.language:
return 'gb2312'
else:
return 'unicode-escape'
def send(self, base_url=SEARCH_URL, query='', return_bytes=False): def send(self, base_url=SEARCH_URL, query='', return_bytes=False):
response_header = [] response_header = []
@ -80,4 +90,4 @@ class Request:
if return_bytes: if return_bytes:
return b_obj.getvalue() return b_obj.getvalue()
else: else:
return b_obj.getvalue().decode('unicode-escape', 'ignore') return b_obj.getvalue().decode(self.get_decode_value(), 'ignore')

View File

@ -1,5 +1,6 @@
from app import app from app import app
from app.filter import Filter from app.filter import Filter
from app.models.config import Config
from app.request import Request, gen_query from app.request import Request, gen_query
import argparse import argparse
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@ -19,13 +20,14 @@ CONFIG_PATH = app.config['STATIC_FOLDER'] + '/config.json'
@app.before_request @app.before_request
def before_request_func(): def before_request_func():
g.user_request = Request(request.headers.get('User-Agent')) json_config = json.load(open(CONFIG_PATH)) if os.path.exists(CONFIG_PATH) else {'url': request.url_root}
g.user_config = json.load(open(CONFIG_PATH)) if os.path.exists(CONFIG_PATH) else {'url': request.url_root} g.user_config = Config(**json_config)
if 'url' not in g.user_config or not g.user_config['url']: if not g.user_config.url:
g.user_config['url'] = request.url_root g.user_config.url = request.url_root
g.app_location = g.user_config['url'] g.user_request = Request(request.headers.get('User-Agent'), language=g.user_config.lang)
g.app_location = g.user_config.url
@app.errorhandler(404) @app.errorhandler(404)
@ -35,8 +37,12 @@ def unknown_page(e):
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def index(): def index():
bg = '#000' if 'dark' in g.user_config and g.user_config['dark'] else '#fff' bg = '#000' if g.user_config.dark else '#fff'
return render_template('index.html', bg=bg, ua=g.user_request.modified_user_agent) return render_template('index.html',
bg=bg,
ua=g.user_request.modified_user_agent,
languages=Config.LANGUAGES,
current_lang=g.user_config.lang)
@app.route('/opensearch.xml', methods=['GET']) @app.route('/opensearch.xml', methods=['GET'])
@ -69,7 +75,7 @@ def search():
mobile = 'Android' in user_agent or 'iPhone' in user_agent mobile = 'Android' in user_agent or 'iPhone' in user_agent
content_filter = Filter(mobile, g.user_config, secret_key=app.secret_key) content_filter = Filter(mobile, g.user_config, secret_key=app.secret_key)
full_query = gen_query(q, request_params, content_filter.near) full_query = gen_query(q, request_params, content_filter.near, language=g.user_config.lang)
get_body = g.user_request.send(query=full_query) get_body = g.user_request.send(query=full_query)
results = content_filter.reskin(get_body) results = content_filter.reskin(get_body)
@ -81,7 +87,7 @@ def search():
@app.route('/config', methods=['GET', 'POST']) @app.route('/config', methods=['GET', 'POST'])
def config(): def config():
if request.method == 'GET': if request.method == 'GET':
return json.dumps(g.user_config) return json.dumps(g.user_config.__dict__)
else: else:
config_data = request.form.to_dict() config_data = request.form.to_dict()
if 'url' not in config_data or not config_data['url']: if 'url' not in config_data or not config_data['url']:

View File

@ -41,6 +41,19 @@
<!-- TODO: Add option to regenerate user agent? --> <!-- TODO: Add option to regenerate user agent? -->
<span class="ua-span">User Agent: {{ ua }}</span> <span class="ua-span">User Agent: {{ ua }}</span>
</div> </div>
<div class="config-div">
<label for="config-lang">Language: </label>
<select name="lang" id="config-lang">
{% for lang in languages %}
<option value="{{ lang.value }}"
{% if lang.value in current_lang %}
selected
{% endif %}>
{{ lang.name }}
</option>
{% endfor %}
</select>
</div>
<div class="config-div"> <div class="config-div">
<label for="config-near">Near: </label> <label for="config-near">Near: </label>
<input type="text" name="near" id="config-near" placeholder="City Name"> <input type="text" name="near" id="config-near" placeholder="City Name">