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

Standardize i18n keys #948

Merged
Merged
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
18 changes: 14 additions & 4 deletions .github/workflows/pr_ci_i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,21 @@ jobs:
with:
python-version: "3.11"

- name: Execute i18n Values Check
- name: Execute i18n Key Identifiers Check
working-directory: ./frontend/i18n/check
run: python i18n_check_values.py
run: python i18n_check_key_identifiers.py

- name: Execute i18n Keys Check
- name: Execute i18n Unused Keys Check
if: always()
working-directory: ./frontend/i18n/check
run: python i18n_check_keys.py
run: python i18n_check_unused_keys.py

- name: Execute i18n Non Source Keys Check
if: always()
working-directory: ./frontend/i18n/check
run: python i18n_check_non_source_keys.py

- name: Execute i18n Repeat Values Check
if: always()
working-directory: ./frontend/i18n/check
run: python i18n_check_repeat_values.py
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: pretty-format-json

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[![nuxt](https://img.shields.io/badge/Nuxt%203-41b883.svg?logo=nuxt.js&logoColor=ffffff)](#tech-stack)
[![vue](https://img.shields.io/badge/Vue%203-41b883.svg?logo=vue.js&logoColor=ffffff)](#tech-stack)
[![tailwind](https://img.shields.io/badge/Tailwind%203-38BDF8.svg?logo=tailwindcss&logoColor=ffffff)](#tech-stack)
[![license](https://img.shields.io/github/license/activist-org/activist-iOS.svg?label=%20)](https://github.com/activist-org/activist/blob/main/LICENSE.txt)
[![license](https://img.shields.io/github/license/activist-org/activist.svg?label=%20)](https://github.com/activist-org/activist/blob/main/LICENSE.txt)
[![coc](https://img.shields.io/badge/Contributor%20Covenant-ff69b4.svg)](https://github.com/activist-org/activist/blob/main/.github/CODE_OF_CONDUCT.md)
[![instagram](https://img.shields.io/badge/Instagram-8134AF.svg?logo=instagram&logoColor=ffffff)](https://instagram.com/activist_org)
[![weblate](https://img.shields.io/badge/Weblate-144D3F.svg?logo=weblate&logoColor=ffffff)](https://hosted.weblate.org/projects/activist/activist)
Expand Down
63 changes: 30 additions & 33 deletions STYLEGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ Vue files (`.vue`) are Single-File Components that have `<template>`, `<script>`
></element>
```

> [!NOTE]
> Put the aria label as the last attribute on any given element so it's easy to see if it's missing (`aria-label` for as an HTML attribute and `ariaLabel` as a component prop)

Please see the [Vue.js style guide](https://vuejs.org/style-guide) for general suggestions on how to write Vue files.

### Page Routing
Expand Down Expand Up @@ -239,48 +242,42 @@ activist is a global platform and must function in countless different regions a
>
> - This is the source from which all the other languages are translated from
> - Edits to the other files should be made on activist's [public localization project on Weblate](https://hosted.weblate.org/projects/activist/activist)
> - Please alphabetize the keys, with your code editor likely having built in functionality for this
> - Do not put the dictionary into different levels!
> - The purpose of one flat dictionary is so that we can search for the key in the codebase and easily find its uses and where it's defined
> - Do not put the JSON dictionaries into different levels!
> - The purpose of the flat dictionaries is so that we can search for the key in the codebase and easily find its uses and where it's defined
> - Do not include periods in aria-labels (screen reader user will configure their own preferences for a hard stop)
> - Put the aria label as the last attribute on any given element so it's easy to see if it's missing (`aria-label` for as an HTML attribute and `ariaLabel` as a prop)

Localization keys should be defined based on their component or page within the platform and the content that they refer to (`CONTENT_REFERENCE` below). Please use the following rules as a guide if you find yourself needing to create new localization keys:
Localization keys should be defined based on the file in which they're used within the platform and the content that they refer to (`CONTENT_REFERENCE` below). Please use the following rules as a guide if you find yourself needing to create new localization keys:

- Separate directories and references by `.` and CamelCase file name words by `-` in keys
- Ex: `"components.search-bar.CONTENT_REFERENCE"` for the `SearchBar` component
- If the localization key is being passed to a component prop, include it in the content reference
- Ex: `"components.shield-topic.topic.CONTENT_REFERENCE"` for passing a localized `topic` prop to the `ShieldTopic` component
- `"CONTENT_REFERENCE"` in this case would be a reference to the name of a topic like `"environment"`
- Separate directories and references by `.` and PascalCase/camelCase file name components by `_` in keys
- Ex: `"components.search_bar.CONTENT_REFERENCE"` for the `SearchBar` component
- Even though Nuxt allows for us to nest components in directories, avoid repetition in the directory path used to define the localization key
- Ex: if you're defining a key within `DropdownCreate`:
- ✅ `"components.dropdown-create.CONTENT_REFERENCE"`
- ❌ `"components.dropdown.dropdown-create.CONTENT_REFERENCE"`
- Define keys based on the lowest level component or other entity in which they're used
- Ex: you're working on the about page for organizations and there's a `BtnAction` that's getting a localization key:
- ✅ `"components.btn-action.CONTENT_REFERENCE"`
- ❌ `"pages.organizations.id.about.CONTENT_REFERENCE"`
- The reason for this is we want to make sure that we can reuse keys wherever we can
- In the above example, if we defined the key based on its location on the organization about page when it's a `BtnAction` with a text like `"Support"`, then we'd need to create a different version of this key for each occurrence of the button depending on the location
- With the system detailed above, we have the `components.btn-action.label.support` key that we can use anywhere that we have a support button ✨
- Please end all aria-label keys with `-alt-text` so the localization team knows that they're for screen readers
- Ex: If you're defining a key within `CardAbout`:
- ✅ `"components.card_about.CONTENT_REFERENCE"`
- ❌ `"components.card.card_about.CONTENT_REFERENCE"`
- Define keys based on the lowest level file in which they're used
- Use `_global` to indicate that a key is used in multiple places in a given directory
- Ex: You're creating a key that's used by multiple cards:
- ✅ `"components.card._global.CONTENT_REFERENCE"`
- ❌ `"components.card.INDIVIDUAL_COMPONENT.CONTENT_REFERENCE"`
- Please end all aria-label keys with `_alt_text` so the localization team knows that they're for screen readers
- If you need a capitalized and lower case version of a word, signify the lower case version with `_lower` at the end of the key
- For pages with long texts please follow the below naming criteria:
- `"header"`: the main header (h1) of the given page
- `"section-#"`: a section that iterates by one with every header and subheader
- `"section-#-#"`: a subsection, with other `#-#` patterns also being possible (see below)
- `"section-#-subheader"`: marks the start of a new section (h2 and beyond)
- `"section-#-paragraph-#"`: a paragraph with one or more sentences
- `"section-#-paragraph-#-#"`: a paragraph with separate parts to insert things like links
- `"section-#-list-#-item-#"`: an item in a list
- `"section-#-list-#-item-#-#"`: a subitem of the given item
- If you're creating a value that already exists, move it and the original to a `_global` sub name at the lowest shared name in `en-US.json`
- If there are different uses of the same value in one file, then alphabetically combine the final keys with dashes (ex: `header-title`)
- `"header"`: The main header (h1) of the given page
- `"section_#"`: A section that iterates by one with every header and subheader
- `"section_#_#"`: A subsection, with other `#_#` patterns also being possible (see below)
- `"section_#_subheader"`: Marks the start of a new section (h2 and beyond)
- `"section_#_paragraph_#"`: A paragraph with one or more sentences
- `"section_#_paragraph_#_#"`: A paragraph with separate parts to insert things like links
- `"section_#_list_#_item_#"`: An item in a list
- `"section_#_list_#_item_#_#"`: A subitem of the given item
- If there are different uses of the same value in one file, then alphabetically combine the final keys with dashes (ex: `header_title`)
- Please alphabetize the keys, with your code editor likely having built in functionality for this
- Please always assign the full key as a string to assure that i18n content checks can pick up if the key has been used
- Eg: `section-1-2` and not `section-{var_number}-2`
- Eg: `section_1_2` and not `section_{var_number}_2`
- This makes sure that content writers and the i18n team are only working with language that's actively in use

The activist team is happy to help if there's any confusion with the above rules! Feel free to ask in the issue you're working on or even check once a PR is made and we'll make sure that conventions are being followed.
> [!NOTE]
> The activist community also maintains the [i18n-check project](https://github.com/activist-org/i18n-check-action?tab=readme-ov-file#contentions) that enforces all of the above in pull requests. Do your best and we'll help you out during the PR process! You can also join us in the [localization room on Matrix](https://matrix.to/#/!DzbdYyfhjinQBWXgQe:matrix.org?via=matrix.org) if you have questions :)

<a id="images-icons"></a>

Expand Down
52 changes: 26 additions & 26 deletions frontend/components/EmptyState.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,56 @@
>
<PageContent
:imgURL="BOOTSTRAP_CLOUD_MOON_URL"
imgAltText="components.empty-state.img-alt-text"
imgAltText="components.empty_state.img_alt_text"
>
<div>
<!-- Header -->
<span v-if="pageType == 'organizations'" class="responsive-h2">{{
$t("components.empty-state.organizations-header")
$t("components.empty_state.organizations_header")
}}</span>
<span v-if="pageType == 'groups'" class="responsive-h2">{{
$t("components.empty-state.groups-header")
$t("components.empty_state.groups_header")
}}</span>
<span v-if="pageType == 'events'" class="responsive-h2">{{
$t("components.empty-state.events-header")
$t("components.empty_state.events_header")
}}</span>
<span v-if="pageType == 'resources'" class="responsive-h2">{{
$t("components.empty-state.resources-header")
$t("components.empty_state.resources_header")
}}</span>
<span v-if="pageType == 'faq'" class="responsive-h2">{{
$t("components.empty-state.faq-header")
$t("components.empty_state.faq_header")
}}</span>
<span v-if="pageType == 'team'" class="responsive-h2">{{
$t("components.empty-state.team-header")
$t("components.empty_state.team_header")
}}</span>
<span v-if="pageType == 'affiliates'" class="responsive-h2">{{
$t("components.empty-state.affiliates-header")
$t("components.empty_state.affiliates_header")
}}</span>
<span v-if="pageType == 'tasks'" class="responsive-h2">{{
$t("components.empty-state.tasks-header")
$t("components.empty_state.tasks_header")
}}</span>
<span v-if="pageType == 'discussions'" class="responsive-h2">{{
$t("components.empty-state.discussions-header")
$t("components.empty_state.discussions_header")
}}</span>
<!-- Message -->
<div v-if="!permission" class="flex flex-col space-y-6 py-6">
<span class="responsive-h4">{{
$t("components.empty-state.message-no-permission")
$t("components.empty_state.message_no_permission")
}}</span>
<PageCommunityFooter
header="components.empty-state.cta-header-no-permission"
header="components.empty_state.cta_header_no_permission"
><BtnRouteInternal
class="w-full"
:cta="false"
label="components.btn-route-internal.return-home"
label="_global.return_home"
linkTo="/home"
fontSize="lg"
ariaLabel="components.btn-route-internal.return-home-aria-label"
ariaLabel="_global.return_home_aria_label"
/></PageCommunityFooter>
</div>
<div v-else class="flex flex-col space-y-6 py-6">
<span class="responsive-h4">{{
$t("components.empty-state.message-with-permission")
$t("components.empty_state.message_with_permission")
}}</span>
<div
class="mx-auto grid max-w-[70%] grid-cols-1 gap-y-4 pb-6 sm:mx-0 sm:max-w-[90%] sm:grid-cols-2 sm:grid-rows-1 sm:gap-x-4 sm:gap-y-0 md:max-w-[70%] md:gap-x-6 lg:max-w-[60%] xl:max-w-[50%] xl:gap-x-8 2xl:max-w-[80%]"
Expand All @@ -62,49 +62,49 @@
v-if="pageType == 'organizations'"
class="w-full"
:cta="true"
label="_global.create-organization"
label="components.empty_state.create_organization"
linkTo="/organizations/create"
fontSize="lg"
ariaLabel="components.btn-route-internal.create-organization-aria-label"
ariaLabel="components.empty_state.create_organization_aria_label"
/>
<BtnRouteInternal
v-if="pageType == 'groups'"
class="w-full"
:cta="true"
label="_global.create-group"
label="_global.create_group"
linkTo="/groups/create"
fontSize="lg"
ariaLabel="components.btn-route-internal.create-group-aria-label"
ariaLabel="components.empty_state.create_group_aria_label"
/>
<BtnRouteInternal
v-if="pageType == 'events'"
class="w-full"
:cta="true"
label="components.btn-route-internal.create-event"
label="components.empty_state.create_event"
linkTo="/events/create"
fontSize="lg"
ariaLabel="components.btn-route-internal.create-event-aria-label"
ariaLabel="components.empty_state.create_event_aria_label"
/>
<BtnRouteInternal
v-if="pageType == 'resources'"
class="w-full"
:cta="true"
label="components.btn-route-internal.create-resource"
label="components.btn_route_internal.create_resource"
linkTo="/resources/create"
fontSize="lg"
ariaLabel="components.btn-route-internal.create-resource-aria-label"
ariaLabel="components.empty_state.create_resource_aria_label"
/>
</div>
<PageCommunityFooter
header="components.empty-state.cta-header-no-permission"
header="components.empty_state.cta_header_no_permission"
:helpNeeded="true"
><BtnRouteInternal
class="w-full"
:cta="false"
label="components.btn-route-internal.return-home"
label="_global.return_home"
linkTo="/home"
fontSize="lg"
ariaLabel="components.btn-route-internal.return-home-aria-label"
ariaLabel="_global.return_home_aria_label"
/></PageCommunityFooter>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/FriendlyCaptcha.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
class="style-btn flex w-full cursor-not-allowed items-center space-x-4 rounded-md border-none p-1 px-3 text-lg shadow-none"
:disabled="true"
:aria-label="
$t('components.friendly-captcha.captcha-disabled-aria-label')
$t('components.friendly_captcha.captcha_disabled_aria_label')
"
>
<Icon :name="IconMap.SHIELD" size="28px" />
<p class="font-bold">
{{ $t("components.friendly-captcha.captcha-disabled") }}
{{ $t("components.friendly_captcha.captcha_disabled") }}
</p>
</button>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/Loading.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
v-if="$colorMode.value == 'light'"
class="h-40"
:src="ACTIVIST_ICON_LIGHT_URL"
:alt="$t('_global.activist-icon-img-alt-text')"
:alt="$t('_global.activist_icon_img_alt_text')"
/>
<img
v-else-if="$colorMode.value == 'dark'"
class="h-40"
:src="ACTIVIST_ICON_DARK_URL"
:alt="$t('_global.activist-icon-img-alt-text')"
:alt="$t('_global.activist_icon_img_alt_text')"
/>
</div>
</div> -->
Expand Down
6 changes: 3 additions & 3 deletions frontend/components/SearchBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
>
<TooltipBase
class="invisible -mt-8"
:text="$t('components.search-bar.slash-tooltip-label')"
:text="$t('components.search_bar.slash_tooltip_label')"
/>
<p class="-mt-[0.075rem]">/</p>
</div>
Expand All @@ -50,7 +50,7 @@
>
<TooltipBase
class="invisible -mt-8"
:text="$t('components.search-bar.command-tooltip-label')"
:text="$t('components.search_bar.command_tooltip_label')"
/>
<p>⌘k</p>
</div>
Expand All @@ -60,7 +60,7 @@
>
<TooltipBase
class="invisible -mt-8"
:text="$t('components.search-bar.control-tooltip-label')"
:text="$t('components.search_bar.control_tooltip_label')"
/>
<p>⌃k</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/btn/BtnRoadMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
id="btn-roadmap"
class="style-btn select-none items-center rounded-md px-3 py-[0.35rem] text-base font-semibold"
:to="localePath('/about/roadmap')"
:aria-label="$t('components.btn-roadmap.aria-label')"
:aria-label="$t('components.btn_road_map.aria_label')"
>
{{ $t("_global.roadmap") }}
</NuxtLink>
Expand Down
14 changes: 7 additions & 7 deletions frontend/components/card/CardConnect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@
<PopoverButton as="div">
<BtnAction
:cta="true"
label="components.btn-action.new-account"
label="components.card_connect.new_account"
fontSize="sm"
:leftIcon="IconMap.PLUS"
iconSize="1.35em"
ariaLabel="components._global.new-account-aria-label"
ariaLabel="components.card_connect.new_account_aria_label"
/>
</PopoverButton>
<transition
Expand All @@ -84,22 +84,22 @@
<PopupNewField
@on-cta-clicked="emit('on-new-account')"
@on-close-clicked="onClose(close)"
:title="$t('components.card-connect.app-account-popup-title')"
:title="$t('components.card_connect.app_account_popup_title')"
:fieldNamePrompt="
$t(
'components.card-connect.app-account-popup-field-name-prompt'
'components.card_connect.app_account_popup_field_name_prompt'
)
"
:fieldLabelPrompt="
$t(
'components.card-connect.app-account-popup-field-label-prompt'
'components.card_connect.app_account_popup_field_label_prompt'
)
"
:ctaBtnLabel="
$t('components.card-connect.app-account-popup-cta-btn-label')
$t('components.card_connect.app_account_popup_cta_btn_label')
"
:ctaBtnAriaLabel="
$t('components._global.new-account-aria-label')
$t('components.card_connect.new_account_aria_label')
"
/>
</PopoverPanel>
Expand Down
Loading
Loading