From 448efb8f2aa14119cf5d25b290b72ff95569a437 Mon Sep 17 00:00:00 2001 From: "Joao A. Candido Ramos" Date: Fri, 16 Apr 2021 16:16:14 +0200 Subject: [PATCH 1/5] Add "view image" functionality (#268) * add view image option * prevent whoogle links from opening in a new tab. * remove view image template on mobile requests * change loop values to be more robust to the number of images * Update app/templates/imageresults.html * fix "Basically the .cvifge class needs width: 100%; in order to expand the search input to fit the form width." * Update app/templates/imageresults.html * remove hardcoded string from template * Add view image config var to app.json * Add view image config var to whoogle.env Co-authored-by: jacr13 Co-authored-by: Ben Busby --- README.md | 17 ++- app.json | 5 + app/filter.py | 62 ++++++++ app/models/config.py | 2 + app/request.py | 12 +- app/templates/imageresults.html | 116 +++++++++++++++ app/templates/index.html | 246 ++++++++++++++++---------------- app/utils/search.py | 14 +- whoogle.env | 3 + 9 files changed, 350 insertions(+), 127 deletions(-) create mode 100644 app/templates/imageresults.html diff --git a/README.md b/README.md index a26f6c5..dcd3087 100644 --- a/README.md +++ b/README.md @@ -103,17 +103,25 @@ Sandboxed temporary instance: ```bash $ whoogle-search --help -usage: whoogle-search [-h] [--port ] [--host ] [--debug] - [--https-only] +usage: whoogle-search [-h] [--port ] [--host ] [--debug] [--https-only] [--userpass ] + [--proxyauth ] [--proxytype ] [--proxyloc ] Whoogle Search console runner optional arguments: - -h, --help show this help message and exit + -h, --help Show this help message and exit --port Specifies a port to run on (default 5000) --host Specifies the host address to use (default 127.0.0.1) --debug Activates debug mode for the server (default False) - --https-only Enforces HTTPS redirects for all requests (default False) + --https-only Enforces HTTPS redirects for all requests + --userpass + Sets a username/password basic auth combo (default None) + --proxyauth + Sets a username/password for a HTTP/SOCKS proxy (default None) + --proxytype + Sets a proxy type for all connections (default None) + --proxyloc + Sets a proxy location for all connections (default None) ``` See the [available environment variables](#environment-variables) for additional configuration. @@ -286,6 +294,7 @@ These environment variables allow setting default config values, but can be over | WHOOGLE_CONFIG_ALTS | Use social media site alternatives (nitter, invidious, etc) | | WHOOGLE_CONFIG_TOR | Use Tor routing (if available) | | WHOOGLE_CONFIG_NEW_TAB | Always open results in new tab | +| WHOOGLE_CONFIG_VIEW_IMAGE | Enable View Image option | | WHOOGLE_CONFIG_GET_ONLY | Search using GET requests only | | WHOOGLE_CONFIG_URL | The root url of the instance (`https:///`) | | WHOOGLE_CONFIG_STYLE | The custom CSS to use for styling (should be single line) | diff --git a/app.json b/app.json index 691f8fc..47f4ca0 100644 --- a/app.json +++ b/app.json @@ -115,6 +115,11 @@ "value": "", "required": false }, + "WHOOGLE_CONFIG_VIEW_IMAGE": { + "description": "[CONFIG] Enable View Image option (set to 1 or leave blank)", + "value": "", + "required": false + }, "WHOOGLE_CONFIG_GET_ONLY": { "description": "[CONFIG] Search using GET requests only (set to 1 or leave blank)", "value": "", diff --git a/app/filter.py b/app/filter.py index 7848a79..4fede8a 100644 --- a/app/filter.py +++ b/app/filter.py @@ -254,3 +254,65 @@ class Filter: # Replace link destination link_desc[0].replace_with(get_site_alt(link_desc[0])) + + def view_image(self, soup) -> BeautifulSoup: + """Replaces the soup with a new one that handles mobile results and + adds the link of the image full res to the results. + + Args: + soup: A BeautifulSoup object containing the image mobile results. + + Returns: + BeautifulSoup: The new BeautifulSoup object + """ + + # get some tags that are unchanged between mobile and pc versions + search_input = soup.find_all('td', attrs={'class': "O4cRJf"})[0] + search_options = soup.find_all('div', attrs={'class': "M7pB2"})[0] + cor_suggested = soup.find_all('table', attrs={'class': "By0U9"}) + next_pages = soup.find_all('table', attrs={'class': "uZgmoc"})[0] + information = soup.find_all('div', attrs={'class': "TuS8Ad"})[0] + + results = [] + # find results div + results_div = soup.find_all('div', attrs={'class': "nQvrDb"})[0] + # find all the results + results_all = results_div.find_all('div', attrs={'class': "lIMUZd"}) + + for item in results_all: + urls = item.find('a')['href'].split('&imgrefurl=') + + img_url = urlparse.unquote(urls[0].replace('/imgres?imgurl=', '')) + webpage = urlparse.unquote(urls[1].split('&')[0]) + img_tbn = urlparse.unquote(item.find('a').find('img')['src']) + results.append({ + 'domain': urlparse.urlparse(webpage).netloc, + 'img_url': img_url, + 'webpage': webpage, + 'img_tbn': img_tbn + }) + + soup = BeautifulSoup(render_template('imageresults.html', + length=len(results), + results=results, + view_label="View Image"), + features='html.parser') + # replace search input object + soup.find_all('td', + attrs={'class': "O4cRJf"})[0].replaceWith(search_input) + # replace search options object (All, Images, Videos, etc.) + soup.find_all('div', + attrs={'class': "M7pB2"})[0].replaceWith(search_options) + # replace correction suggested by google object if exists + if len(cor_suggested): + soup.find_all( + 'table', + attrs={'class': "By0U9"} + )[0].replaceWith(cor_suggested[0]) + # replace next page object at the bottom of the page + soup.find_all('table', + attrs={'class': "uZgmoc"})[0].replaceWith(next_pages) + # replace information about user connection at the bottom of the page + soup.find_all('div', + attrs={'class': "TuS8Ad"})[0].replaceWith(information) + return soup diff --git a/app/models/config.py b/app/models/config.py index 3898ae7..5b2f192 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -27,7 +27,9 @@ class Config: self.tor = read_config_bool('WHOOGLE_CONFIG_TOR') self.near = os.getenv('WHOOGLE_CONFIG_NEAR', '') self.new_tab = read_config_bool('WHOOGLE_CONFIG_NEW_TAB') + self.view_image = read_config_bool('WHOOGLE_CONFIG_VIEW_IMAGE') self.get_only = read_config_bool('WHOOGLE_CONFIG_GET_ONLY') + self.safe_keys = [ 'lang_search', 'lang_interface', diff --git a/app/request.py b/app/request.py index 9239b6e..1c0c2c6 100644 --- a/app/request.py +++ b/app/request.py @@ -151,6 +151,8 @@ class Request: self.language = config.lang_search self.mobile = 'Android' in normal_ua or 'iPhone' in normal_ua self.modified_user_agent = gen_user_agent(self.mobile) + if not self.mobile: + self.modified_user_agent_mobile = gen_user_agent(True) # Set up proxy, if previously configured if os.environ.get('WHOOGLE_PROXY_LOC'): @@ -197,7 +199,8 @@ class Request: return [_.attrib['data'] for _ in root.findall('.//suggestion/[@data]')] - def send(self, base_url=SEARCH_URL, query='', attempt=0) -> Response: + def send(self, base_url=SEARCH_URL, query='', attempt=0, + force_mobile=False) -> Response: """Sends an outbound request to a URL. Optionally sends the request using Tor, if enabled by the user. @@ -211,8 +214,13 @@ class Request: Response: The Response object returned by the requests call """ + if force_mobile and not self.mobile: + modified_user_agent = self.modified_user_agent_mobile + else: + modified_user_agent = self.modified_user_agent + headers = { - 'User-Agent': self.modified_user_agent + 'User-Agent': modified_user_agent } # FIXME: Should investigate this further to ensure the consent diff --git a/app/templates/imageresults.html b/app/templates/imageresults.html new file mode 100644 index 0000000..17ee98a --- /dev/null +++ b/app/templates/imageresults.html @@ -0,0 +1,116 @@ + + + + + + + + + + + +
+
+ + Google + +
+
+
+ + + + + + + + + + +
+ +
+
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+ + {% for i in range((length // 4) + 1) %} + + {% for j in range([length - (i*4), 4]|min) %} + + {% endfor %} + + {% endfor %} +
+ +
+
+ + +
+
+
+ +
+
+ + diff --git a/app/templates/index.html b/app/templates/index.html index 9d43988..d063ba5 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,153 +1,159 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - Whoogle Search - - -
-
- {{ logo|safe }} -
-
-
-
- -
- -
-
- {% if not config_disabled %} -
- -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + Whoogle Search + + +
+
+ {{ logo|safe }} +
+
+
+
+ +
+ +
+
+ {% if not config_disabled %} +
+ +
-
+
- - {% for ctry in countries %} - + {% endfor %} - -
— Note: If enabled, a website will only appear in the results if it is *hosted* in the selected country.
+ +
— Note: If enabled, a website will only appear in the results if it is *hosted* in the selected country.
- - {% for lang in languages %} {% endfor %} - +
- - {% for lang in languages %} {% endfor %} - +
- - + + +
+
+ +
-
- - -
- - + +
- - + +
- - + +
- - -
— Replaces Twitter/YouTube/Instagram/Reddit links + + +
— Replaces Twitter/YouTube/Instagram/Reddit links with Nitter/Invidious/Bibliogram/Libreddit links.
- - + + +
+
+ + +
— (Experimental) Adds the "View Image" option on desktop to view full size images in search results. + This will cause image result thumbnails to be lower resolution.
- - + +
- - + +
- - + +
- - +
-   -   - +   +   +
- +
-
- {% endif %} -
-
+
+ {% endif %} +
+ - + + diff --git a/app/utils/search.py b/app/utils/search.py index a856bf6..bb24e4b 100644 --- a/app/utils/search.py +++ b/app/utils/search.py @@ -119,11 +119,23 @@ class Search: self.request_params, self.config, content_filter.near) - get_body = g.user_request.send(query=full_query) + + # force mobile search when view image is true and + # the request is not already made by a mobile + view_image = ('tbm=isch' in full_query + and self.config.view_image + and not g.user_request.mobile) + + get_body = g.user_request.send(query=full_query, + force_mobile=view_image) # Produce cleanable html soup from response html_soup = bsoup(content_filter.reskin(get_body.text), 'html.parser') + # Replace current soup if view_image is active + if view_image: + html_soup = content_filter.view_image(html_soup) + # Indicate whether or not a Tor connection is active tor_banner = bsoup('', 'html.parser') if g.user_request.tor_valid: diff --git a/whoogle.env b/whoogle.env index 3a0f88b..594ec67 100644 --- a/whoogle.env +++ b/whoogle.env @@ -46,6 +46,9 @@ # Open results in new tab #WHOOGLE_CONFIG_NEW_TAB=1 +# Enable View Image option +#WHOOGLE_CONFIG_VIEW_IMAGE=1 + # Search using GET requests only (exposes query in logs) #WHOOGLE_CONFIG_GET_ONLY=1 From 75e74109816c5e9a00db0c0d3fa81cb1cdf034e4 Mon Sep 17 00:00:00 2001 From: Ben Busby Date: Fri, 21 May 2021 11:22:43 -0400 Subject: [PATCH 2/5] Add WHOOGLE_CONFIG_DISABLE doc to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dcd3087..8a01649 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,7 @@ These environment variables allow setting default config values, but can be over | Variable | Description | | ------------------------------ | --------------------------------------------------------------- | +| WHOOGLE_CONFIG_DISABLE | Hide config from UI and disallow changes to config by client | | WHOOGLE_CONFIG_COUNTRY | Filter results by hosting country | | WHOOGLE_CONFIG_LANGUAGE | Set interface language | | WHOOGLE_CONFIG_SEARCH_LANGUAGE | Set search result language | From c2a76bd73a1d9293e7b13f1c2c28044a6f055aa5 Mon Sep 17 00:00:00 2001 From: Oscar Date: Tue, 25 May 2021 01:06:48 +1000 Subject: [PATCH 3/5] Add new public instance to readme (#323) https://whoogle.silkky.cloud --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8a01649..b6015ac 100644 --- a/README.md +++ b/README.md @@ -389,6 +389,7 @@ A lot of the app currently piggybacks on Google's existing support for fetching - [https://whooglesearch.net/](https://whooglesearch.net/) - [https://search.flawcra.cc/](https://search.flawcra.cc/) - [https://search.exonip.de/](https://search.exonip.de/) +- [https://whoogle.silkky.cloud/](https://whoogle.silkky.cloud/) ## Screenshots #### Desktop ![Whoogle Desktop](docs/screenshot_desktop.jpg) From 7c221b7f7f1576119e7465b69d6ca1f8edc827b1 Mon Sep 17 00:00:00 2001 From: Justin Goette <53531335+jcgoette@users.noreply.github.com> Date: Mon, 24 May 2021 12:56:08 -0400 Subject: [PATCH 4/5] Remove duplicate docker-compose restart (#324) --- docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index cbdb2ae..f29586c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: whoogle-search: image: benbusby/whoogle-search container_name: whoogle-search - restart: on-failure:5 + restart: unless-stopped pids_limit: 50 mem_limit: 256mb memswap_limit: 256mb @@ -41,4 +41,3 @@ services: #- whoogle.env ports: - 5000:5000 - restart: unless-stopped From 4649d96ddaea20b2d405cd4a4e354fcd47d48233 Mon Sep 17 00:00:00 2001 From: Ben Busby Date: Mon, 24 May 2021 17:03:02 -0400 Subject: [PATCH 5/5] Support basic localization (#325) * Replace hardcoded strings using translation json file This introduces a new "translations.json" file under app/static/settings that is loaded on app init and uses the user config value for interface language to determine the appropriate strings to use in Whoogle-specific elements of the UI (primarily only on the home page). * Verify interface lang can be used for localization Check the configured interface language against the available localization dict before attempting to use, otherwise fall back to english. Also expanded language names in the languages json file. * Add test for validating translation language keys Also adds Spanish translation to json (the only non-English language I can add and reasonably validate on my own). * Validate all translations against original keyset, update readme Readme has been updated to include basic contributing guidelines for both code and translations. --- README.md | 59 +++++++++++++++-- app/__init__.py | 2 + app/models/config.py | 13 ++++ app/routes.py | 6 ++ app/static/settings/countries.json | 2 +- app/static/settings/languages.json | 92 +++++++++++++-------------- app/static/settings/translations.json | 58 +++++++++++++++++ app/templates/display.html | 2 +- app/templates/index.html | 50 +++++++-------- test/test_misc.py | 13 ++++ 10 files changed, 219 insertions(+), 78 deletions(-) create mode 100644 app/static/settings/translations.json diff --git a/README.md b/README.md index b6015ac..6f914ad 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,11 @@ Contents 1. [Set Primary Search Engine](#set-whoogle-as-your-primary-search-engine) 2. [Prevent Downtime (Heroku Only)](#prevent-downtime-heroku-only) 3. [Manual HTTPS Enforcement](#https-enforcement) -7. [FAQ](#faq) -8. [Public Instances](#public-instances) -9. [Screenshots](#screenshots) -10. Mirrors (read-only) +7. [Contributing](#contributing) +8. [FAQ](#faq) +9. [Public Instances](#public-instances) +10. [Screenshots](#screenshots) +11. Mirrors (read-only) 1. [GitLab](https://gitlab.com/benbusby/whoogle-search) 2. [Gogs](https://gogs.benbusby.com/benbusby/whoogle-search) @@ -365,6 +366,56 @@ Note: You should have your own domain name and [an https certificate](https://le - Pip/Pipx: Add the `--https-only` flag to the end of the `whoogle-search` command - Default `run` script: Modify the script locally to include the `--https-only` flag at the end of the python run command +## Contributing + +Under the hood, Whoogle is a basic Flask app with the following structure: + +- `app/` + - `routes.py`: Primary app entrypoint, contains all API routes + - `request.py`: Handles all outbound requests, including proxied/Tor connectivity + - `filter.py`: Functions and utilities used for filtering out content from upstream Google search results + - `utils/` + - `bangs.py`: All logic related to handling DDG-style "bang" queries + - `results.py`: Utility functions for interpreting/modifying individual search results + - `search.py`: Creates and handles new search queries + - `session.py`: Miscellaneous methods related to user sessions + - `templates/` + - `index.html`: The home page template + - `display.html`: The search results template + - `header.html`: A general "top of the page" query header for desktop and mobile + - `search.html`: An iframe-able search page + - `logo.html`: A template consisting mostly of the Whoogle logo as an SVG (separated to help keep `index.html` a bit cleaner) + - `opensearch.xml`: A template used for supporting [OpenSearch](https://developer.mozilla.org/en-US/docs/Web/OpenSearch). + - `imageresults.html`: An "exprimental" template used for supporting the "Full Size" image feature on desktop. + - `static/` + - CSS/Javascript files, should be self-explanatory + - `static/settings` + - Key-value JSON files for establishing valid configuration values + + +If you're new to the project, the easiest way to get started would be to try fixing [an open bug report](https://github.com/benbusby/whoogle-search/issues?q=is%3Aissue+is%3Aopen+label%3Abug). If there aren't any open, or if the open ones are too stale, try taking on a [feature request](https://github.com/benbusby/whoogle-search/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement). Generally speaking, if you can write something that has any potential of breaking down in the future, you should write a test for it. + +The project follows the [PEP 8 Style Guide](https://www.python.org/dev/peps/pep-0008/), but is liable to change. Static typing should always be used when possible. Function documentation is greatly appreciated, and typically follows the below format: + +```python +def contains(x: list, y: int) -> bool: + """Check a list (x) for the presence of an element (y) + + Args: + x: The list to inspect + y: The int to look for + + Returns: + bool: True if the list contains the item, otherwise False + """ + + return y in x +``` + +#### Translating + +Whoogle currently supports translations using [`translations.json`](https://github.com/benbusby/whoogle-search/blob/main/app/static/settings/languages.json). Language values in this file need to match the "value" of the according language in [`languages.json`](https://github.com/benbusby/whoogle-search/blob/main/app/static/settings/languages.json) (i.e. "lang_en" for English, "lang_es" for Spanish, etc). After you add a new set of translations to `translations.json`, open a PR with your changes and they will be merged in as soon as possible. + ## FAQ **What's the difference between this and [Searx](https://github.com/asciimoo/searx)?** diff --git a/app/__init__.py b/app/__init__.py index c3d781b..06d9306 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -33,6 +33,8 @@ app.config['LANGUAGES'] = json.load(open( os.path.join(app.config['STATIC_FOLDER'], 'settings/languages.json'))) app.config['COUNTRIES'] = json.load(open( os.path.join(app.config['STATIC_FOLDER'], 'settings/countries.json'))) +app.config['TRANSLATIONS'] = json.load(open( + os.path.join(app.config['STATIC_FOLDER'], 'settings/translations.json'))) app.config['CONFIG_PATH'] = os.getenv( 'CONFIG_VOLUME', os.path.join(app.config['STATIC_FOLDER'], 'config')) diff --git a/app/models/config.py b/app/models/config.py index 5b2f192..8413a65 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -77,6 +77,19 @@ class Config: return key in self.safe_keys + def get_localization_lang(self): + """Returns the correct language to use for localization, but falls + back to english if not set. + + Returns: + str -- the localization language string + """ + if (self.lang_interface and + self.lang_interface in current_app.config['TRANSLATIONS']): + return self.lang_interface + + return 'lang_en' + def from_params(self, params) -> 'Config': """Modify user config with search parameters. This is primarily used for specifying configuration on a search-by-search basis on diff --git a/app/routes.py b/app/routes.py index 2e8152a..4409901 100644 --- a/app/routes.py +++ b/app/routes.py @@ -130,6 +130,9 @@ def index(): return render_template('index.html', languages=app.config['LANGUAGES'], countries=app.config['COUNTRIES'], + translation=app.config['TRANSLATIONS'][ + g.user_config.get_localization_lang() + ], logo=render_template( 'logo.html', dark=g.user_config.dark), @@ -235,6 +238,9 @@ def search(): query=urlparse.unquote(query), search_type=search_util.search_type, config=g.user_config, + translation=app.config['TRANSLATIONS'][ + g.user_config.get_localization_lang() + ], response=response, version_number=app.config['VERSION_NUMBER'], search_header=(render_template( diff --git a/app/static/settings/countries.json b/app/static/settings/countries.json index 57c4619..da5ce2f 100644 --- a/app/static/settings/countries.json +++ b/app/static/settings/countries.json @@ -1,5 +1,5 @@ [ - {"name": "Default (none)", "value": ""}, + {"name": "-------", "value": ""}, {"name": "Afghanistan", "value": "countryAF"}, {"name": "Albania", "value": "countryAL"}, {"name": "Algeria", "value": "countryDZ"}, diff --git a/app/static/settings/languages.json b/app/static/settings/languages.json index 4666b7c..7056afa 100644 --- a/app/static/settings/languages.json +++ b/app/static/settings/languages.json @@ -1,49 +1,49 @@ [ - {"name": "Default (none specified)", "value": ""}, + {"name": "-------", "value": ""}, {"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"} + {"name": "Afrikaans (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 (Català)", "value": "lang_ca"}, + {"name": "Chinese, Simplified (简体中文)", "value": "lang_zh-CN"}, + {"name": "Chinese, Traditional (繁体中文)", "value": "lang_zh-TW"}, + {"name": "Croatian (Hrvatski)", "value": "lang_hr"}, + {"name": "Czech (čeština)", "value": "lang_cs"}, + {"name": "Danish (Dansk)", "value": "lang_da"}, + {"name": "Dutch (Nederlands)", "value": "lang_nl"}, + {"name": "Esperanto (Esperanto)", "value": "lang_eo"}, + {"name": "Estonian (Eestlane)", "value": "lang_et"}, + {"name": "Filipino (Pilipino)", "value": "lang_tl"}, + {"name": "Finnish (Suomalainen)", "value": "lang_fi"}, + {"name": "French (Français)", "value": "lang_fr"}, + {"name": "German (Deutsche)", "value": "lang_de"}, + {"name": "Greek (Ελληνικά)", "value": "lang_el"}, + {"name": "Hebrew (עִברִית)", "value": "lang_iw"}, + {"name": "Hindi (हिंदी)", "value": "lang_hi"}, + {"name": "Hungarian (Magyar)", "value": "lang_hu"}, + {"name": "Icelandic (Íslenska)", "value": "lang_is"}, + {"name": "Indonesian (Indonesian)", "value": "lang_id"}, + {"name": "Italian (Italiano)", "value": "lang_it"}, + {"name": "Japanese (日本語)", "value": "lang_ja"}, + {"name": "Korean (한국어)", "value": "lang_ko"}, + {"name": "Latvian (Latvietis)", "value": "lang_lv"}, + {"name": "Lithuanian (Lietuvis)", "value": "lang_lt"}, + {"name": "Norwegian (Norwegian)", "value": "lang_no"}, + {"name": "Persian (فارسی)", "value": "lang_fa"}, + {"name": "Polish (Polskie)", "value": "lang_pl"}, + {"name": "Portugese (Português)", "value": "lang_pt"}, + {"name": "Romanian (Română)", "value": "lang_ro"}, + {"name": "Russian (русский)", "value": "lang_ru"}, + {"name": "Serbian (Српски)", "value": "lang_sr"}, + {"name": "Slovak (Slovák)", "value": "lang_sk"}, + {"name": "Slovenian (Slovenščina)", "value": "lang_sl"}, + {"name": "Spanish (Español)", "value": "lang_es"}, + {"name": "Swahili (Kiswahili)", "value": "lang_sw"}, + {"name": "Swedish (Svenska)", "value": "lang_sv"}, + {"name": "Thai (ไทย)", "value": "lang_th"}, + {"name": "Turkish (Türk)", "value": "lang_tr"}, + {"name": "Ukranian (Український)", "value": "lang_uk"}, + {"name": "Vietnamese (Tiếng Việt)", "value": "lang_vi"} ] diff --git a/app/static/settings/translations.json b/app/static/settings/translations.json new file mode 100644 index 0000000..98e8030 --- /dev/null +++ b/app/static/settings/translations.json @@ -0,0 +1,58 @@ +{ + "lang_en": { + "search": "Search", + "config": "Configuration", + "config-country": "Filter Results by Country", + "config-country-help": "Note: If enabled, a website will only appear in the search results if it is *hosted* in the selected country.", + "config-lang": "Interface Language", + "config-lang-search": "Search Language", + "config-near": "Near", + "config-near-help": "City Name", + "config-block": "Block", + "config-block-help": "Comma-separated site list", + "config-nojs": "Show NoJS Links", + "config-dark": "Dark Mode", + "config-safe": "Safe Search", + "config-alts": "Replace Social Media Links", + "config-alts-help": "Replaces Twitter/YouTube/Instagram/etc links with privacy respecting alternatives.", + "config-new-tab": "Open Links in New Tab", + "config-images": "Full Size Image Search", + "config-images-help": "(Experimental) Adds the 'View Image' option to desktop image searches. This will cause image result thumbnails to be lower resolution.", + "config-tor": "Use Tor", + "config-get-only": "GET Requests Only", + "config-url": "Root URL", + "config-css": "Custom CSS", + "load": "Load", + "apply": "Apply", + "save-as": "Save As...", + "github-link": "View on GitHub" + }, + "lang_es": { + "search": "Buscar", + "config": "Configuración", + "config-country": "Filtrar Resultados por País", + "config-country-help": "Nota: Si está habilitado, un sitio web solo aparecerá en los resultados de búsqueda si está alojado en ese país.", + "config-lang": "Idioma de Interfaz", + "config-lang-search": "Idioma de Búsqueda", + "config-near": "Cerca", + "config-near-help": "Nombre de la Ciudad", + "config-block": "Bloquear", + "config-block-help": "Lista de sitios separados por comas", + "config-nojs": "Mostrar Enlaces NoJS", + "config-dark": "Modo Oscuro", + "config-safe": "Búsqueda Segura", + "config-alts": "Reemplazar Enlaces de Redes Sociales", + "config-alts-help": "Reemplaza los enlaces de Twitter/YouTube/Instagram/etc con alternativas que respetan la privacidad.", + "config-new-tab": "Abrir enlaces en una pestaña nueva", + "config-images": "Búsqueda de imágenes a tamaño completo", + "config-images-help": "(Experimental) Agrega la opción 'Ver imagen' a las búsquedas de imágenes de escritorio. Esto hará que las miniaturas de los resultados de la imagen aparezcan con una resolución más baja.", + "config-tor": "Usa Tor", + "config-get-only": "GET solo solicitudes", + "config-url": "URL raíz", + "config-css": "CSS personalizado", + "load": "Cargar", + "apply": "Aplicar", + "save-as": "Guardar como...", + "github-link": "Ver en GitHub" + } +} diff --git a/app/templates/display.html b/app/templates/display.html index 398e276..8c30f6e 100644 --- a/app/templates/display.html +++ b/app/templates/display.html @@ -20,7 +20,7 @@ diff --git a/app/templates/index.html b/app/templates/index.html index d063ba5..5cba56c 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -53,17 +53,17 @@ autocorrect="off" autocomplete="off">
- +
{% if not config_disabled %}
- +
- + -
— Note: If enabled, a website will only appear in the results if it is *hosted* in the selected country.
+
— {{ translation['config-country-help'] }}
- + {% for lang in languages %}
- +
- +
- +
- +
- +
- + -
— Replaces Twitter/YouTube/Instagram/Reddit links - with Nitter/Invidious/Bibliogram/Libreddit links.
+
— {{ translation['config-alts-help'] }}
- +
- + -
— (Experimental) Adds the "View Image" option on desktop to view full size images in search results. - This will cause image result thumbnails to be lower resolution.
+
— {{ translation['config-images-help'] }}
- +
- +
- +
- +