From 800e1e0db089467a4c960a0096c08c1755a97826 Mon Sep 17 00:00:00 2001 From: Tony Yip Date: Sat, 9 Nov 2019 18:24:14 +0800 Subject: [PATCH 1/5] ignore file for JetBrains IDE --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 261a0e57..79b9d1f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ script/output/* .DS_Store -node_modules \ No newline at end of file +node_modules +.idea \ No newline at end of file From cfe04398a422a22c1cffd68fba5656fa311c6af9 Mon Sep 17 00:00:00 2001 From: Tony Yip Date: Sat, 9 Nov 2019 22:56:02 +0800 Subject: [PATCH 2/5] use dynamic import for code-splitting in page level --- web/package-lock.json | 101 +++++++++++++++++++++++++++++++++--------- web/package.json | 1 + web/src/App.js | 63 +++++++++++++++++++------- 3 files changed, 128 insertions(+), 37 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 3d4559c4..eac7ee2c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1436,6 +1436,25 @@ "@types/yargs": "^13.0.0" } }, + "@loadable/component": { + "version": "5.10.3", + "resolved": "https://registry.npmjs.org/@loadable/component/-/component-5.10.3.tgz", + "integrity": "sha512-/aSO+tXw4vFMwZ6fgLaNQgLuEa7bgTpoBE4PxNzf08/ewAjymrCS3J7v3SbGE7IjGmmKL6vVwkpb7S3cYrk+ag==", + "requires": { + "@babel/runtime": "^7.6.0", + "hoist-non-react-statics": "^3.3.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + } + } + }, "@material-ui/core": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.5.2.tgz", @@ -11713,7 +11732,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -11731,11 +11751,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11748,15 +11770,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -11859,7 +11884,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -11869,6 +11895,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -11881,17 +11908,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -11908,6 +11938,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -11980,7 +12011,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -11990,6 +12022,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -12065,7 +12098,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -12095,6 +12129,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -12112,6 +12147,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -12150,11 +12186,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -16919,7 +16957,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -16937,11 +16976,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -16954,15 +16995,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -17065,7 +17109,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -17075,6 +17120,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -17087,17 +17133,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -17114,6 +17163,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -17186,7 +17236,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -17196,6 +17247,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -17271,7 +17323,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -17301,6 +17354,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -17318,6 +17372,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -17356,11 +17411,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, diff --git a/web/package.json b/web/package.json index 6f722b21..b7883158 100644 --- a/web/package.json +++ b/web/package.json @@ -5,6 +5,7 @@ "dependencies": { "@apollo/react-hooks": "^3.1.3", "@babel/core": "7.2.2", + "@loadable/component": "^5.10.3", "@material-ui/core": "4.5.2", "@material-ui/icons": "^3.0.2", "@material-ui/system": "^4.3.0", diff --git a/web/src/App.js b/web/src/App.js index e451bcd4..b69585dd 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -2,31 +2,64 @@ import React from 'react' import { Route, Switch, useRouteMatch } from 'react-router-dom' import CssBaseline from '@material-ui/core/CssBaseline' import { ThemeProvider } from '@material-ui/core/styles/' -import IndexPage from 'components/pages/landing' -import ProfilePage from 'components/pages/profile' -import DistrictPage from 'components/pages/district' -import DistrictListPage from 'components/pages/district/list' -import BattleGroundPage from 'components/pages/battleground' -import DisclaimerPage from 'components/pages/disclaimer' -import AboutDCPage from 'components/pages/about/dc' -import NotfoundPage from 'components/pages/notfound' -import SupportUsPage from 'components/pages/support-us' +import loadable from '@loadable/component' import ApolloClient from 'apollo-boost' import { ApolloProvider } from 'react-apollo' import theme from 'ui/theme' import './App.css' import Box from '@material-ui/core/Box' import styled from 'styled-components' -import MobileAppBar from 'components/organisms/MobileAppBar' -import Footer from 'components/organisms/Footer' import { ContextStoreProvider } from 'ContextStore' import withTracker from './WithTracker' -import SearchDrawer from 'components/pages/SearchDrawer' -import DistrictOverviewPage from 'components/pages/district/overview' -import DistrictAllPage from 'components/pages/district/all' -import GlobalDisclaimer from 'components/organisms/GlobalDisclaimer' import i18n from 'i18n' +const IndexPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/landing') +) +const ProfilePage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/profile') +) +const DistrictPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/district') +) +const DistrictListPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/district/list') +) +const BattleGroundPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/battleground') +) +const DisclaimerPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/disclaimer') +) +const AboutDCPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/about/dc') +) +const NotfoundPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/notfound') +) +const SupportUsPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/support-us') +) +const DistrictOverviewPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/district/overview') +) +const DistrictAllPage = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/district/all') +) + +const MobileAppBar = loadable(() => + import(/* webpackPrefetch: true */ 'components/organisms/MobileAppBar') +) +const Footer = loadable(() => + import(/* webpackPrefetch: true */ 'components/organisms/Footer') +) +const SearchDrawer = loadable(() => + import(/* webpackPrefetch: true */ 'components/pages/SearchDrawer') +) +const GlobalDisclaimer = loadable(() => + import(/* webpackPrefetch: true */ 'components/organisms/GlobalDisclaimer') +) + const client = new ApolloClient({ uri: process.env.REACT_APP_GRAPHQL_URI, }) From fbd5977cbc8e3dbd864ce259da131c4a49664c90 Mon Sep 17 00:00:00 2001 From: Tony Yip Date: Mon, 11 Nov 2019 16:21:10 +0800 Subject: [PATCH 3/5] fix fb sdk parse issue --- web/src/components/organisms/Footer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/components/organisms/Footer.js b/web/src/components/organisms/Footer.js index 152a8113..08e67f1e 100644 --- a/web/src/components/organisms/Footer.js +++ b/web/src/components/organisms/Footer.js @@ -39,6 +39,9 @@ const LinkBox = styled(Box)` function Footer(props) { const { t } = useTranslation() + React.useEffect(() => { + window.FB.XFBML.parse() + }, []) return ( <> From f3a4693650333122febc9147738cca26b773fbb0 Mon Sep 17 00:00:00 2001 From: coolsunwind Date: Mon, 11 Nov 2019 23:40:30 +0800 Subject: [PATCH 4/5] update i18n --- .../candidate/CandidatesTableContent.js | 17 +++++++++++++---- web/src/queries/gql.js | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/web/src/components/molecules/candidate/CandidatesTableContent.js b/web/src/components/molecules/candidate/CandidatesTableContent.js index 12860cd6..42ea75ff 100644 --- a/web/src/components/molecules/candidate/CandidatesTableContent.js +++ b/web/src/components/molecules/candidate/CandidatesTableContent.js @@ -12,7 +12,7 @@ import { COLORS } from 'ui/theme' import TableRow from '@material-ui/core/TableRow' import TableCell from '@material-ui/core/TableCell' import { HtmlTooltip } from 'components/atoms/Tooltip' -import { useTranslation } from 'react-i18next' +import { useTranslation, withTranslation } from 'react-i18next' const IMAGE_HOST_URI = process.env.REACT_APP_HOST_URI || 'https://hkvoteguide.github.io' @@ -151,7 +151,13 @@ class CandidatesTableContent extends Component { render() { const { props, matchCamp } = this - const { candidates, showEstablishment, showDemocracy, showOthers } = props + const { + candidates, + showEstablishment, + showDemocracy, + showOthers, + t, + } = props const currentLanguage = getCurrentLanguage() return ( <> @@ -185,7 +191,10 @@ class CandidatesTableContent extends Component { {candidate.person.related_organization || '-'} - {candidate.political_affiliation || '-'} + {withLanguage( + candidate.political_affiliation_en, + candidate.political_affiliation_zh + ) || t('candidate.noPoliticalAffiliation')} ))} @@ -194,4 +203,4 @@ class CandidatesTableContent extends Component { } } -export default withRouter(CandidatesTableContent) +export default withTranslation()(withRouter(CandidatesTableContent)) diff --git a/web/src/queries/gql.js b/web/src/queries/gql.js index ef5270a9..552a01e9 100644 --- a/web/src/queries/gql.js +++ b/web/src/queries/gql.js @@ -53,6 +53,8 @@ constituencies( where: { year: { _eq: $year } }, order_by: {code: asc} ) { candidate_number is_won political_affiliation + political_affiliation_zh + political_affiliation_en election_type camp person { From 0de59ad73ae9140c9c403ba1ee5dfde72a77662a Mon Sep 17 00:00:00 2001 From: coolsunwind Date: Tue, 12 Nov 2019 01:34:17 +0800 Subject: [PATCH 5/5] update i18n --- .../components/pages/battleground/index.js | 1 - web/src/components/pages/profile/index.js | 41 ++++++++++++++++--- .../templates/DCCAElectionHistories.js | 21 ++++++---- .../templates/DCCAElectionResult.js | 18 ++++++-- .../templates/PersonElectionHistories.js | 20 ++++++--- web/src/locales/en/translation.json | 33 +++++++++------ web/src/locales/zh/translation.json | 19 ++++++--- web/src/utils/helper.js | 14 +++++++ 8 files changed, 125 insertions(+), 42 deletions(-) diff --git a/web/src/components/pages/battleground/index.js b/web/src/components/pages/battleground/index.js index d554a71b..7320ec76 100644 --- a/web/src/components/pages/battleground/index.js +++ b/web/src/components/pages/battleground/index.js @@ -191,7 +191,6 @@ class BattleGroundPage extends Component { const DCCAStatus = district.tags && district.tags.find(tag => tag.type === 'boundary') - console.log(district) return ( <> diff --git a/web/src/components/pages/profile/index.js b/web/src/components/pages/profile/index.js index 95119281..28735911 100644 --- a/web/src/components/pages/profile/index.js +++ b/web/src/components/pages/profile/index.js @@ -99,6 +99,7 @@ const FlexRowContainer = styled(Box)` const CandidateHeaderContainer = styled(FlexRowContainer)` && { height: 120px; + margin-bottom: 16px; position: relative; display: flex; background: linear-gradient( @@ -121,6 +122,7 @@ const PersonName = styled.div` position: absolute; left: 116px; top: 36px; + margin-right: 16px; color: ${props => COLORS.camp[props.camp].text}; } ` @@ -261,15 +263,42 @@ class ProfilePage extends Component { currentTerm.term_to && Date.parse(new Date()) < Date.parse(currentTerm.term_to) ) { - text = `現任${currentTerm.district.dc_name_zh}區議員(${currentTerm.constituency.name_zh})` + text = t('currentTerm.councilor.withDistrict', { + district_name: withLanguage( + currentTerm.district.dc_name_en, + currentTerm.district.dc_name_zh + ), + dcca_name: withLanguage( + currentTerm.constituency.name_en, + currentTerm.constituency.name_zh + ), + }) } else { - const electionResult = person.candidates[0].is_won + text = person.candidates[0].is_won ? // '當選' : - t('election.tag1') + t('election.elected', { + year: person.candidates[0].year, + district_name: withLanguage( + person.candidates[0].constituency.district.dc_name_en, + person.candidates[0].constituency.district.dc_name_zh + ), + dcca_name: withLanguage( + person.candidates[0].constituency.name_en, + person.candidates[0].constituency.name_zh + ), + }) : // '參選' - t('election.tag2') - - text = `${electionResult}${person.candidates[0].year}年${person.candidates[0].constituency.district.dc_name_zh}區議員(${person.candidates[0].constituency.name_zh})` + t('election.run_for', { + year: person.candidates[0].year, + district_name: withLanguage( + person.candidates[0].constituency.district.dc_name_en, + person.candidates[0].constituency.district.dc_name_zh + ), + dcca_name: withLanguage( + person.candidates[0].constituency.name_en, + person.candidates[0].constituency.name_zh + ), + }) } return {text} diff --git a/web/src/components/templates/DCCAElectionHistories.js b/web/src/components/templates/DCCAElectionHistories.js index 57db43bb..448299ec 100644 --- a/web/src/components/templates/DCCAElectionHistories.js +++ b/web/src/components/templates/DCCAElectionHistories.js @@ -7,7 +7,8 @@ import { Typography } from '@material-ui/core' import DCCAElectionResult from 'components/templates/DCCAElectionResult' import Box from '@material-ui/core/Box' import { COLORS } from 'ui/theme' -import { getColorFromCamp } from 'utils/helper' +import { getColorFromCamp, geti18nFromCamp } from 'utils/helper' +import { withTranslation } from 'react-i18next' const DCCAElectionResultContainer = styled(Box)` && { @@ -34,6 +35,7 @@ class DCCAElectionHistories extends Component { code year name_zh + name_en candidates(where: { year: { _eq: $year${year} }, cacode: { _eq: $code${code} } }) { person { name_en @@ -46,6 +48,8 @@ class DCCAElectionHistories extends Component { vote_percentage is_won political_affiliation + political_affiliation_en + political_affiliation_zh } } dcd_candidates_aggregate_${year}_${code}: dcd_candidates_aggregate( @@ -81,7 +85,7 @@ class DCCAElectionHistories extends Component { } render() { - const { histories, presetTabIndex } = this.props + const { histories, presetTabIndex, t } = this.props const filteredHistories = histories.filter( history => history.year !== '2019' ) @@ -129,10 +133,13 @@ class DCCAElectionHistories extends Component { .camp )} > - { - electionResult.candidates.find(candi => candi.is_won) - .camp - } + {t( + geti18nFromCamp( + electionResult.candidates.find(candi => candi.is_won) + .camp, + true + ) + )} {electionResult.year} @@ -158,4 +165,4 @@ class DCCAElectionHistories extends Component { } } -export default DCCAElectionHistories +export default withTranslation()(DCCAElectionHistories) diff --git a/web/src/components/templates/DCCAElectionResult.js b/web/src/components/templates/DCCAElectionResult.js index ff29985c..9c2fbac5 100644 --- a/web/src/components/templates/DCCAElectionResult.js +++ b/web/src/components/templates/DCCAElectionResult.js @@ -8,12 +8,14 @@ import Rows from 'components/atoms/Rows' import LinearProgress from '@material-ui/core/LinearProgress' import { COLORS } from 'ui/theme' import { UnstyledNavLink } from 'components/atoms/Link' +import { useTranslation } from 'react-i18next' import { formatNumber, getColorFromCamp, withLanguage, getCurrentLanguage, + geti18nFromCamp, } from 'utils/helper' const Container = styled.div` @@ -56,6 +58,7 @@ const CandidateName = styled(Columns)` && { width: auto; min-width: 120px; + max-width: 180px; .person-name { font-weight: 600; } @@ -95,11 +98,13 @@ const DCCAElectionResult = props => { const [imageLoadError, setImageLoadError] = useState(true) const currentLanguage = getCurrentLanguage() + const { t } = useTranslation() return ( - {electionResult.year}年 - {electionResult.name_zh}( + {t('electionResults.year', { n: electionResult.year })} -{' '} + {withLanguage(electionResult.name_en, electionResult.name_zh)}( {electionResult.code}) {sortedCandidates.map((candidate, index) => { @@ -153,7 +158,8 @@ const DCCAElectionResult = props => { - {candidate.political_affiliation} ({candidate.camp}) + {candidate.political_affiliation} ( + {t(geti18nFromCamp(candidate.camp))}) @@ -162,7 +168,9 @@ const DCCAElectionResult = props => { <> - {formatNumber(candidate.votes)}票{' '} + {t('electionResults.votes', { + n: formatNumber(candidate.votes), + })} ({candidate.vote_percentage}%) @@ -181,7 +189,9 @@ const DCCAElectionResult = props => { ) : ( - 自動當選 + + {t('electionResults.uncontested')} + )} diff --git a/web/src/components/templates/PersonElectionHistories.js b/web/src/components/templates/PersonElectionHistories.js index e3c6c464..1ac5726e 100644 --- a/web/src/components/templates/PersonElectionHistories.js +++ b/web/src/components/templates/PersonElectionHistories.js @@ -7,7 +7,12 @@ import { Box, Grid } from '@material-ui/core' import { SuccessText, FailureText } from '../atoms/Text' import { UnstyledNavLink } from '../atoms/Link' import NavigateNextIcon from '@material-ui/icons/NavigateNext' -import { formatNumber, getCurrentLanguage, withLanguage } from 'utils/helper' +import { + formatNumber, + getCurrentLanguage, + withLanguage, + geti18nFromCamp, +} from 'utils/helper' import { useTranslation } from 'react-i18next' import { getCentroidFromYearAndCode, @@ -95,17 +100,20 @@ const PersonElectionHistories = props => { m.political_affiliation_en, m.political_affiliation_zh ) || t('candidate.noPoliticalAffiliation')} - ({m.camp}) + ({t(geti18nFromCamp(m.camp))}) {m.is_won ? ( - {m.votes > 0 ? `${formatNumber(m.votes)}票` : '自動當選'} + {m.votes > 0 + ? t('electionResults.votes', { n: formatNumber(m.votes) }) + : t('electionResults.uncontested')} ) : ( - {m.votes > 0 && `${formatNumber(m.votes)}票`} + {m.votes > 0 && + t('electionResults.votes', { n: formatNumber(m.votes) })} )} @@ -125,12 +133,12 @@ const PersonElectionHistories = props => { {m.is_won ? ( {/* 當選 */} - {t('election.tag1')} + {t('election.won')} ) : ( {/* 落敗 */} - {t('election.tag3')} + {t('election.lost')} )} diff --git a/web/src/locales/en/translation.json b/web/src/locales/en/translation.json index e4661636..704f1d61 100644 --- a/web/src/locales/en/translation.json +++ b/web/src/locales/en/translation.json @@ -16,7 +16,9 @@ "councilor.moreThanOneDistricts": "This area overlaps n constituencies in the previous term", "reportedPoliticalAffiliation": "Political affiliation reported ", "electionResults": "Election results", - "councillor.lastElectionResult.type1": "Uncontested", + "electionResults.uncontested": "Uncontested", + "electionResults.votes": "{{n}} votes", + "electionResults.year": "{{n}}", "councillor.lastElectionResult.type2": "Landslide victory", "councillor.lastElectionResult.type3": "Victory", "councillor.lastElectionResult.votes": "votes", @@ -32,9 +34,12 @@ "voteStats.text3": "Invalid vote", "voteStats.text4": "Vote for {{name}}", "metrics.text1": "Demographics", - "camp.democracy": "Pro-democratic", + "camp.democracy": "Pan-democratic", "camp.establishment": "Pro-establishment", "camp.others": "Others", + "camp.democracy_short_form": "Demo", + "camp.establishment_short_form": "Estab", + "camp.others_short_form": "Others", "no_of_districts": "{{n}} districts", "candidates": "Candidates", "relatedOrganizations": "Related organizations", @@ -57,7 +62,7 @@ "personVote.vote": "Vote", "personVote.speech": "Speech", "disclaimer.segment.text1": "This website is not affiliated with any candidate or his/her campaign members of the 2019 District Council Election and does not contain information intended to induce or prevent any candidate from being elected.", - "disclaimer.segment.source": "The information contained in this website is all public information and is summarized from Electoral Affairs Commission, Electoral and Electoral Office, Census and Statistics Department, Websites of District Councils and Initium Media.", + "disclaimer.segment.source": "The information contained in this website is all public information and is summarized from Electoral Affairs Commission, Electoral and Electoral Office, Census and Statistics Department, Websites of District Councils and Initium Media. ", "disclaimer.segment.report": "Every effort has been made to ensure the accuracy of the information before publication. Please click this link to report any suggestions or omissions.", "about.camp": "About the camp of candidates", "about.feedback": "Give feedback", @@ -77,6 +82,7 @@ "searchMenu.text1": "Return to home page", "searchMenu.text2": "List of constituencies in Hong Kong", "searchMenu.text3": "About District Council Election", + "searchMenu.text4": "Followed District", "searchMenu.feedback": "Give feedback", "searchTab.text1": "Find a constituency", "searchTab.text2": "Find a candidate.", @@ -110,17 +116,18 @@ "disclaimer.header.text2": "Category of camps before 2019", "disclaimer.paragraph4.segment1": "As for the classification of candidate camps prior to 2019, we have taken from the files collated by Initium Media in 2017. Every effort has been made to ensure the accuracy of the information before posting on this website. If there are suggestions or errors, please contact us by email at chiangsumlui@gmail.com, we will follow up as soon as possible.", "index.title1": "Election Countdown", - "currentTerm.councilor.withDistrict": "incumbent member of {{district_name}} District Council", - "election.tag1": "Elected", - "election.tag2": "To run for election", - "election.tag3": "Lost", + "currentTerm.councilor.withDistrict": "Incumbent District Council member of {{district_name}} - {{ dcca_name }} ", + "election.elected": "Elected as District Council member of {{district_name}} - {{ dcca_name }} in {{year}} ", + "election.run_for": "Run for {{year}} DC Election ({{district_name}} - {{ dcca_name }})", + "election.won": "Won", + "election.lost": "Lost", "currentTerm.councilor.withTerm": "Have been District Council Member for n years", - "candidate.tag1": "Veteran campaigning for re-election", - "candidate.tag2": "Novice running for the first time", - "candidate.tag3": "Refused to give up after losing", - "candidate.tag4": "comeback", - "candidate.tag5": "Uncontested in the previous election", - "candidate.tag6": "Cross-district election", + "candidate.tag1": "Run for next term", + "candidate.tag2": "First attempt", + "candidate.tag3": "Keep fighting", + "candidate.tag4": "Comeback", + "candidate.tag5": "Uncontested in the last election", + "candidate.tag6": "From other districts", "personHighlight.age.value": "{{n}}", "personHighlight.age.title": "Age", "personHighlight.age.tips": "Based on the age of the candidate profile", diff --git a/web/src/locales/zh/translation.json b/web/src/locales/zh/translation.json index e4612d8d..a99c3b13 100644 --- a/web/src/locales/zh/translation.json +++ b/web/src/locales/zh/translation.json @@ -16,7 +16,9 @@ "councilor.moreThanOneDistricts": "此區與上屆n個選區重疊", "reportedPoliticalAffiliation": "報稱政治聯繫", "electionResults": "選舉結果", - "councillor.lastElectionResult.type1": "自動當選", + "electionResults.uncontested": "自動當選", + "electionResults.votes": "{{n}}票", + "electionResults.year": "{{n}}年", "councillor.lastElectionResult.type2": "大勝", "councillor.lastElectionResult.type3": "險勝", "councillor.lastElectionResult.votes": "票", @@ -35,6 +37,9 @@ "camp.democracy": "民主", "camp.establishment": "建制", "camp.others": "其他", + "camp.democracy_short_form": "民主", + "camp.establishment_short_form": "建制", + "camp.others_short_form": "其他", "no_of_districts": "{{n}}個選區", "candidates": "候選人", "relatedOrganizations": "相關組織", @@ -77,6 +82,7 @@ "searchMenu.text1": "返回首頁", "searchMenu.text2": "全港選區一覽", "searchMenu.text3": "關於區議會選舉", + "searchMenu.text4": "己關注選區⭐", "searchMenu.feedback": "反映意見", "searchTab.text1": "找選區", "searchTab.text2": "找候選人", @@ -95,6 +101,8 @@ "battleground.alert.text3": "此選區為2019年", "battleground.button.hide": "隱藏地圖", "battleground.button.show": "顯示地圖", + "battleground.button.follow": "關注此選區⭐", + "battleground.button.unfollow": "取消關注", "disclaimer.paragraph1": "候選人的政治陣營,反映他們對政治議題的看法。一般而言,每人對各項議題可持不同立場,難以用一兩個詞語概括候選人背景。", "disclaimer.header.text1": "陣營分類(2019區議會選舉)", "disclaimer.paragraph2.segment1": "本屆區議會選舉的候選人政治陣營取自", @@ -110,10 +118,11 @@ "disclaimer.header.text2": "2019之前的陣營分類", "disclaimer.paragraph4.segment1": "至於2019年以前之候選人陣營分類,則取自端傳媒於2017年整理之檔案,本網站刊載前已盡力確保資料真確性,如有建議或錯漏,歡迎反映或電郵至chiangsumlui@gmail.com,我們會盡快跟進。", "index.title1": "距離投票日", - "currentTerm.councilor.withDistrict": "現任{{district_name}}區議員", - "election.tag1": "當選", - "election.tag2": "參選", - "election.tag3": "落敗", + "currentTerm.councilor.withDistrict": "現任{{district_name}}區議員({{dcca_name}})", + "election.elected": "當選{{year}}年{{district_name}}區議員({{dcca_name}})", + "election.run_for": "參選{{year}}年區議會選舉({{district_name}} - {{dcca_name}})", + "election.won": "當選", + "election.lost": "落敗", "currentTerm.councilor.withTerm": "n年區議員", "candidate.tag1": "競逐連任", "candidate.tag2": "首度參選", diff --git a/web/src/utils/helper.js b/web/src/utils/helper.js index 19a3c0bf..8d038a06 100644 --- a/web/src/utils/helper.js +++ b/web/src/utils/helper.js @@ -224,3 +224,17 @@ export const withLanguage = (name_en, name_zh) => { export const getCurrentLanguage = () => { return i18n.language || window.localStorage.i18nextLng || 'zh' } + +export const geti18nFromCamp = (camp, isShortForm = false) => { + if (!camp) return camp + + const suffix = isShortForm ? '_short_form' : '' + const mapping = { + 民主: `camp.democracy${suffix}`, + 建制: `camp.establishment${suffix}`, + 其他: `camp.others${suffix}`, + } + + if (!mapping[camp]) return camp + return mapping[camp] +}