diff --git a/.editorconfig b/.editorconfig index 49c154da..dd454a13 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,5 +7,4 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf -# editorconfig-tools is unable to ignore longs strings or urls -max_line_length = null +max_line_length = 100 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..544138be --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/App/Screens/About/Language/Language.js b/App/Screens/About/Language/Language.js index a953ddcc..a0b2b690 100644 --- a/App/Screens/About/Language/Language.js +++ b/App/Screens/About/Language/Language.js @@ -14,46 +14,50 @@ // You should have received a copy of the GNU General Public License // along with Sh**t! I Smoke. If not, see . +import { getName } from 'country-list'; +import { getNativeName } from 'iso-639-1'; import { inject, observer } from 'mobx-react'; import React, { Component } from 'react'; import { Picker, StyleSheet, Text, View } from 'react-native'; import { i18n } from '../../../localization'; -import * as names from './names.json'; import * as theme from '../../../utils/theme'; +function getLanguageName (code) { + const [languageCode, countryCode] = code.split('-'); + const languageName = getNativeName(languageCode); + const countryName = countryCode ? ` (${getName(countryCode)})` : ''; + + return languageName + countryName; +} + @inject('stores') @observer export class Language extends Component { - handleValueChange = (itemValue) => { + handleValueChange = itemValue => { i18n.locale = itemValue; // Reload app for changes to take effect this.props.stores.reloadApp(); - } + }; render () { // Using this hack to show custom picker style // https://github.com/facebook/react-native/issues/7817#issuecomment-264851951 return ( - {names[i18n.locale].nativeName} + {getLanguageName(i18n.locale)} - {Object.keys(i18n.translations).map((lang) => - ) - } + {Object.keys(i18n.translations).map(lang => ( + + ))} - ); } } diff --git a/App/Screens/About/Language/names.json b/App/Screens/About/Language/names.json deleted file mode 100644 index 6e9addf3..00000000 --- a/App/Screens/About/Language/names.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "en": { - "name": "English", - "nativeName": "English" - }, - "es": { - "name": "Spanish", - "nativeName": "Español" - }, - "fr": { - "name": "French", - "nativeName": "Français" - } -} diff --git a/App/Screens/Details/Header/Header.js b/App/Screens/Details/Header/Header.js index a6c1571e..2fbd46fb 100644 --- a/App/Screens/Details/Header/Header.js +++ b/App/Screens/Details/Header/Header.js @@ -16,6 +16,7 @@ import React, { Component } from 'react'; import { formatRelative } from 'date-fns'; +import * as datesFnsLocales from 'date-fns/locale'; import { Image, StyleSheet, Text, View } from 'react-native'; import { inject, observer } from 'mobx-react'; @@ -36,8 +37,7 @@ export class Header extends Component { stores: { api } } = this.props; - const lastUpdated = - api.time && api.time.v ? new Date(api.time.v * 1000) : null; + const lastUpdated = api.time && api.time.v ? new Date(api.time.v * 1000) : null; const { dominentpol, iaqi } = api; return ( @@ -52,10 +52,15 @@ export class Header extends Component { {lastUpdated && this.renderInfo( i18n.t('details_header_latest_update_label'), - formatRelative(lastUpdated, new Date()) + formatRelative(lastUpdated, new Date(), { + locale: datesFnsLocales[i18n.locale.split('-')[0]] + }) )} {dominentpol && - this.renderInfo(i18n.t('details_header_primary_pollutant_label'), dominentpol.toUpperCase())} + this.renderInfo( + i18n.t('details_header_primary_pollutant_label'), + dominentpol.toUpperCase() + )} {trackedPollutant.map( diff --git a/App/Screens/ErrorScreen/ErrorScreen.js b/App/Screens/ErrorScreen/ErrorScreen.js index d5449a47..feb4d967 100644 --- a/App/Screens/ErrorScreen/ErrorScreen.js +++ b/App/Screens/ErrorScreen/ErrorScreen.js @@ -16,7 +16,7 @@ import { inject, observer } from 'mobx-react'; import React, { Component } from 'react'; -import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import error from '../../../assets/images/error.png'; import { i18n } from '../../localization'; @@ -31,24 +31,28 @@ export class ErrorScreen extends Component { const { error: errorText } = this.props.stores; return ( - - - - - {i18n.t('error_screen_common_sorry')} - {i18n.t('error_screen_error_cannot_load_cigarettes')} + + + + + + {i18n.t('error_screen_common_sorry')} + {i18n.t('error_screen_error_cannot_load_cigarettes')} + + + + + + {i18n.t('error_screen_choose_other_location').toUpperCase()} + + + + {i18n.t('error_screen_error_description')} + + {i18n.t('error_screen_error_message', { errorText })} - - - {i18n.t('error_screen_choose_other_location').toUpperCase()} - - - - {i18n.t('error_screen_error_description')} - - {i18n.t('error_screen_error_message', { errorText })} - + ); } } @@ -62,7 +66,8 @@ const styles = StyleSheet.create({ ...theme.fullScreen, ...theme.withPadding, flexGrow: 1, - flexDirection: 'column' + flexDirection: 'column', + marginVertical: theme.spacing.normal }, errorMessage: { ...theme.text, diff --git a/App/Screens/Home/Home.js b/App/Screens/Home/Home.js index 8f46fa79..75b5db75 100644 --- a/App/Screens/Home/Home.js +++ b/App/Screens/Home/Home.js @@ -16,14 +16,7 @@ import React, { Component } from 'react'; import { inject, observer } from 'mobx-react'; -import { - ScrollView, - Share, - StyleSheet, - Text, - TouchableOpacity, - View -} from 'react-native'; +import { ScrollView, Share, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { Cigarettes } from './Cigarettes'; import { Header } from './Header'; @@ -67,9 +60,7 @@ export class Home extends Component { {isStationTooFar && ( - - {i18n.t('home_station_too_far_message')} - + {i18n.t('home_station_too_far_message')} )} {this.renderBigButton()} {this.renderFooter()} @@ -87,7 +78,9 @@ export class Home extends Component { return ( - {i18n.t('home_btn_why_is_station_so_far').toUpperCase()} + + {i18n.t('home_btn_why_is_station_so_far').toUpperCase()} + ); @@ -96,7 +89,9 @@ export class Home extends Component { return ( - {i18n.t('home_btn_see_detailed_info').toUpperCase()} + + {i18n.t('home_btn_see_detailed_info').toUpperCase()} + ); @@ -110,18 +105,22 @@ export class Home extends Component { {isStationTooFar ? ( ) : ( )} - + ); }; @@ -152,19 +151,19 @@ export class Home extends Component { const text = i18n.t('home_smoked_cigarette_title', { swearWord: this.renderShit(), presentPast: this.renderPresentPast(), - singularPlural: cigarettes === 1 ? i18n.t('home_common_cigarette').toLowerCase() : i18n.t('home_common_cigarettes').toLowerCase(), + singularPlural: + cigarettes === 1 + ? i18n.t('home_common_cigarette').toLowerCase() + : i18n.t('home_common_cigarettes').toLowerCase(), cigarettes }); - const firstPartText = text.split('<')[0]; - const secondPartText = text.split('<')[1]; + const [firstPartText, secondPartText] = text.split('<'); return ( {firstPartText} - - {secondPartText.split('>')[0]} - + {secondPartText.split('>')[0]} {secondPartText.split('>')[1]} ); diff --git a/App/localization/index.js b/App/localization/index.js index 1c981d48..11272928 100644 --- a/App/localization/index.js +++ b/App/localization/index.js @@ -21,18 +21,22 @@ import i18n from 'i18n-js'; const en = require('./languages/en'); const es = require('./languages/es'); const fr = require('./languages/fr'); +const ptBR = require('./languages/pt-BR'); i18n.fallbacks = true; i18n.translations = { en, + 'en-GB': en, + 'en-US': en, es, - fr + fr, + 'fr-BE': fr, + 'fr-CH': fr, + 'fr-FR': fr, + 'fr-CA': fr, + 'pt-BR': ptBR }; -// `Localization.locale` can come in the form of `en-US` sometimes, so we just -// take the 1st part. -i18n.locale = (Localization.locale || 'en').split('-')[0]; +i18n.locale = Localization.locale || 'en'; -export { - i18n -}; +export { i18n }; diff --git a/App/localization/languages/fr.json b/App/localization/languages/fr.json index 1286b2c8..b699bbf5 100644 --- a/App/localization/languages/fr.json +++ b/App/localization/languages/fr.json @@ -1,4 +1,61 @@ { - "error_screen_common_sorry": "Désolé!\n", - "error_screen_error_cannot_load_cigarettes": "Nous n'avons pas pu\ncharger vos\ncigarettes." + "error_screen_common_sorry": "Désolé ! ", + "error_screen_error_cannot_load_cigarettes": "Nous ne pouvons pas charger vos cigarettes.", + "error_screen_choose_other_location": "Choisir un autre lieu", + "error_screen_error_description": "Il y a soit un problème avec nos bases de données, ou bien vous n'avez pas de station de qualité de l'air près de vous. Réessayez plus tard !", + "error_screen_error_message": "Erreur: {{errorText}}", + "home_station_too_far_message": "Nous ne sommes pas parvenus à trouver une station plus proche de vous. Les résultats pourraient être inexacts à cette distance.", + "home_share_title": "Saviez-vous que vous fumez peut-être jusqu'à 20 cigarettes par jour, juste parce-que vous vivez dans une grande ville?", + "home_share_message": "Zut! J'ai 'fumé' {{cigarettes}} cigarettes aujourd'hui en respirant l'air urbain. Et toi? Regarde ici : https://shootismoke.github.io", + "home_header_air_quality_station_distance": "Station de qualité de l'air: {{distanceToStation}}km", + "home_btn_why_is_station_so_far": "La station est éloignée !", + "home_btn_see_detailed_info": "Plus de détails", + "home_btn_more_details": "Détails", + "home_btn_faq_about": "A propos", + "home_btn_share": "Partager", + "home_common_you_ll_smoke": "Vous allez fumer", + "home_common_you_smoked": "Vous avez fumé", + "home_common_oh": "Oh", + "home_common_cigarette": "Cigarette", + "home_common_cigarettes": "Cigarettes", + "home_smoked_cigarette_title": "{{swearWord}}! {{presentPast}} <{{cigarettes}} {{singularPlural}}> aujourd'hui.", + "home_swear_word_dang": "Ma foi ", + "home_swear_word_shoot": "Eh bien ", + "home_swear_word_darn": "Bigre ", + "home_swear_word_geez": "Miséricorde ", + "home_swear_word_omg": "La barbe ", + "home_swear_word_crap": "Saperlipopette ", + "home_swear_word_arrgh": "Arrgh", + "loading_title_cough": "Kof", + "loading_title_loading": "Chargement", + "search_header_input_placeholder": "Chercher une ville ou une adresse", + "nav_btn_back": "Retour", + "details_air_quality_station_marker": "Station de qualité de l'air", + "details_your_position_marker": "Ma position", + "details_header_latest_update_label": "Dernière mise à jour :", + "details_header_primary_pollutant_label": "Polluant principal :", + "details_distance_label": "Station AQI: {{distanceToStation}}km", + "about_how_do_you_calculate_the_number_of_cigarettes_title": "Comment calculons nous le nombre de cigarettes ?", + "about_how_do_you_calculate_the_number_of_cigarettes_message_1": "Cette application a été inspirée des recherches de Berkeley à propos du", + "about_how_do_you_calculate_the_number_of_cigarettes_link_1": "lien entre la pollution de l'air et les cigarettes fumées", + "about_how_do_you_calculate_the_number_of_cigarettes_message_2": ". La règle de base est simple: une cigarette par jour équivaut approximativement à un niveau de PM2,5 de 22", + "about_where_does_data_come_from_title": "D'où viennent les données ?", + "about_where_does_data_come_from_message_1": "Les données de qualité de l'air viennent de", + "about_where_does_data_come_from_link_1": "WAQI", + "about_were_does_data_come_from_message_2": "sous la forme de PM2.5 du niveau AQI qui sont généralement mis à jour toutes les heures et converties automatique en niveau de PM2.5 par l'application.", + "about_why_is_the_station_so_far_title": "Pourquoi la station est-elle si loin de moi ?", + "about_why_is_the_station_so_far_message": "Comme les stations qui mesurent et communiquent les résultats de la qualité de l'air toutes les heures coûtent cher, les données sont toujours limitées aux régions bien développées et aux grandes villes du monde. Si vous êtes loin d'un centre urbain plus important, les résultats ne seront probablement pas aussi précis. Les chances sont que votre air est meilleur dans ce cas au moins!", + "about_weird_results_title": "Les résultats sont incohérents!", + "about_weird_results_message_1": "Nous avons également rencontré quelques résultats surprenants : des grandes villes avec un meilleur air que des petits villages; augmentation soudaine et considérable du nombre de cigarettes; Les stations de la même ville affichent des chiffres très différents… Le fait est que la qualité de l’air dépend de plusieurs facteurs tels que la température, la pression, l’humidité et même la direction et l’intensité du vent. Si le résultat vous semble étrange, regardez sur", + "about_weird_results_link_1": "WAQI", + "about_weird_results_message_2": "pour plus d'informations et un historique de votre station.", + "about_box_per_day": "par jour", + "about_box_footnote": "* Les particules atmosphériques (PM) dont le diamètre est inférieur à 2,5 micromètres augmentent les risques d’inhalation par les êtres vivants.", + "about_credits_title": "Crédits", + "about_credits_concept_and_development": "Concept & Développement :", + "about_credits_design_and_copywriting": "Design & Rédaction :", + "about_credits_data_from": "Mesure de qualité de l'air de", + "about_credits_source_code": "Code source", + "about_credits_available_github": "disponible sur GitHub", + "about_language": "Langue" } diff --git a/App/localization/languages/pt-BR.json b/App/localization/languages/pt-BR.json new file mode 100644 index 00000000..524cf1e0 --- /dev/null +++ b/App/localization/languages/pt-BR.json @@ -0,0 +1,61 @@ +{ + "error_screen_common_sorry": "Desculpe!", + "error_screen_error_cannot_load_cigarettes": "Não podemos\ncarregar seus\ncigarros.", + "error_screen_choose_other_location": "Escolha outra localização", + "error_screen_error_description": "Há um problema com a base de dados ou você não possui estações de monitoramento perto de você. Tente mais tarde!", + "error_screen_error_message": "Erro: {{errorText}}", + "home_station_too_far_message": "Não conseguimos encontrar uma estação próxima de você.\nOs resultados podem ser imprecisos a essa distância.", + "home_share_title": "Você sabia que pode estar fumando até 20 cigarros por dia, só por morar em uma cidade grande?", + "home_share_message": "Nossa! Eu 'fumei' {{cigarettes}} cigarros hoje só respirando na cidade. E você? Descubra aqui: https://shootismoke.github.io", + "home_header_air_quality_station_distance": "Estação de Monitoramento: {{distanceToStation}}km away", + "home_btn_why_is_station_so_far": "Por que a estação é tão longe?", + "home_btn_see_detailed_info": "Veja mais informações", + "home_btn_more_details": "Mais detalhes", + "home_btn_faq_about": "FAQ/Sobre", + "home_btn_share": "Compartilhe", + "home_common_you_ll_smoke": "Você vai fumar", + "home_common_you_smoked": "Você fumou", + "home_common_oh": "Oh", + "home_common_cigarette": "Cigarro", + "home_common_cigarettes": "Cigarros", + "home_smoked_cigarette_title": "{{swearWord}}! {{presentPast}} <{{cigarettes}} {{singularPlural}}> hoje.", + "home_swear_word_dang": "Puxa", + "home_swear_word_shoot": "Droga", + "home_swear_word_darn": "Putz", + "home_swear_word_geez": "Etcha", + "home_swear_word_omg": "Nossa", + "home_swear_word_crap": "Ai ai", + "home_swear_word_arrgh": "Arrgh", + "loading_title_cough": "Cof", + "loading_title_loading": "Carregando", + "search_header_input_placeholder": "Busque por cidade ou endereço", + "nav_btn_back": "Voltar", + "details_air_quality_station_marker": "Estação de Monitoramento", + "details_your_position_marker": "Sua posição", + "details_header_latest_update_label": "Último update", + "details_header_primary_pollutant_label": "Poluente Primário:", + "details_distance_label": "Estação QA: {{distanceToStation}}km longe\n", + "about_how_do_you_calculate_the_number_of_cigarettes_title": "Como vocês calculam o número de cigarros?", + "about_how_do_you_calculate_the_number_of_cigarettes_message_1": "Este aplicativo foi inspirado na pesquisa da Berkeley Earth", + "about_how_do_you_calculate_the_number_of_cigarettes_link_1": "sobre a equivalência entre poluição do ar e o fumo", + "about_how_do_you_calculate_the_number_of_cigarettes_message_2": ". A regra é simples: um cigarro por dia é o equivalente a um nível de PM2.5 de 22", + "about_where_does_data_come_from_title": "Qual a origem dos dados?", + "about_where_does_data_come_from_message_1": "Os dados de qualidade do ar vêm da", + "about_where_does_data_come_from_link_1": "WAQI", + "about_were_does_data_come_from_message_2": "na forma de níveis PM2.5 AQI que são normalmente atualizados a cada uma hora e convertidos diretamente para PM2.5 pelo app", + "about_why_is_the_station_so_far_title": "Por que a estação está tão longe da minha localização atual?", + "about_why_is_the_station_so_far_message": "Como as estações que medem e comunicam os resultados da qualidade do ar a cada hora são caras, os dados ainda são limitados a regiões desenvolvidas e grandes cidades. Se você está longe de ser um centro urbano mais proeminente, os resultados provavelmente não serão tão precisos. As chances são de que o ar que você respira é melhor, pelo menos!", + "about_weird_results_title": "Os resultados são estranhos ou inconsistentes com outras fontes!", + "about_weird_results_message_1": "Nós também encontramos alguns resultados surpreendentes: grandes cidades com melhor ar do que pequenas aldeias; súbito aumento no número de cigarros; estações da mesma cidade mostrando números significativamente diferentes... O fato é que a qualidade do ar depende de vários fatores, como temperatura, pressão, umidade e até direção e intensidade do vento. Se o resultado parecer estranho para você, verifique", + "about_weird_results_link_1": "a WAQI", + "about_weird_results_message_2": "para mais informações e histórico na sua estação.", + "about_box_per_day": "por dia", + "about_box_footnote": "* Material particulado atmosférico (PM, em inglês) que possui diâmetro menor que 2,5 micrômetros, com chances maiores de inalação por seres vivos.", + "about_credits_title": "Créditos", + "about_credits_concept_and_development": "Conceito & Desenvolvimento por", + "about_credits_design_and_copywriting": "Design & Redação por", + "about_credits_data_from": "Dados de qualidade do ar de", + "about_credits_source_code": "Código-fonte", + "about_credits_available_github": "disponível no Github", + "about_language": "Idioma" +} diff --git a/package.json b/package.json index 44421ba8..546f330e 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,12 @@ "@expo/vector-icons": "^9.0.0", "async-retry": "^1.2.3", "axios": "^0.18.0", + "country-list": "^2.1.0", "date-fns": "^2.0.0-alpha.25", "expo": "^32.0.0", "haversine": "^1.1.0", "i18n-js": "^3.2.1", + "iso-639-1": "^2.0.5", "mobx": "^4.6.0", "mobx-react": "^5.3.6", "mobx-state-tree": "^3.7.1", diff --git a/yarn.lock b/yarn.lock index 88e843d4..2cbf801c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2743,6 +2743,11 @@ cosmiconfig@^5.0.5: lodash.get "^4.4.2" parse-json "^4.0.0" +country-list@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/country-list/-/country-list-2.1.0.tgz#0c0c26183e30743a620e5a01edd905a1777b0094" + integrity sha512-RHyEQyui0SHKFFtK342rN1YE9vNRH6ZYgm8aG3rUUHQW6BmcFCkQln0YUILPyBtdBxocxYAfElm4nXk3KOG8vg== + create-react-class@^15.6.3: version "15.6.3" resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" @@ -4793,6 +4798,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +iso-639-1@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/iso-639-1/-/iso-639-1-2.0.5.tgz#a72ad3de139a96c4c4420b97b60b0af4cec4d7a3" + integrity sha512-2TcJ8AcsqM4AXLi92eFZX3xa7X6Eno/chq9yOR0AvSgb15Smmoh1miXyYJVWCkSmbzDimds3Ix2M4efhnOuxOg== + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"