Skip to content
This repository has been archived by the owner on Aug 3, 2024. It is now read-only.

Commit

Permalink
Add the language setting page (#1210)
Browse files Browse the repository at this point in the history
* Add initial language picker prototype

* Heap o' improvements and Pirate tongue

* Move .visually-hidden to shared utils and add copyright notice

* Add a little space before categories names

* Simplify search to input focus logic

* Remove larger font size and padding from the search field

* Some refactors

* Braw's descent into madness

Thanks web development!

In seriousness though, tried to make the list more accessible. Making it
fully accessible feels like unbearable task, so at least that.

* Litol refactoring

* Extract new strings and remove old ones

* Update @vintl/nuxt to 1.3.0

This fixes the bug where default locale won't be saved.

* A buncha refactorings and cleanup

* Scuttle the Pirate lingo

'Twas employed 'ere for testin' purposes, but fear not, for it shall be
returnin' in the days to come. Should ye require it fer testin', simply
roll back this here commit.

* Clean languages source file

* Change "US" to "United States"

I think it would make distinguishing two languages simpler as now
there's more than one letter of difference (US/UK vs United States/
United Kingdom).
  • Loading branch information
brawaru authored Aug 21, 2023
1 parent a420d5b commit 467b0fa
Show file tree
Hide file tree
Showing 17 changed files with 785 additions and 10 deletions.
1 change: 1 addition & 0 deletions assets/images/utils/languages.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/utils/radio-button-checked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/utils/radio-button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions assets/styles/utils.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,22 @@ body {
.text-container p {
line-height: 1.3;
}

// From the Bootstrap project
// The MIT License (MIT)
// Copyright (c) 2011-2023 The Bootstrap Authors
// https://github.com/twbs/bootstrap/blob/2f617215755b066904248525a8c56ea425dde871/scss/mixins/_visually-hidden.scss#L8
.visually-hidden {
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;

&:not(caption) {
position: absolute !important;
}
}
13 changes: 13 additions & 0 deletions composables/auto-ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type AutoRef<T> = [T] extends [(...args: any[]) => any]
? Ref<T> | (() => T)
: T | Ref<T> | (() => T)

/**
* Accepts a value directly, a ref with the value or a getter function and returns a Vue ref.
* @param value The value to use.
* @returns Either the original or newly created ref.
*/
export function useAutoRef<T>(value: AutoRef<T>): Ref<T> {
if (typeof value === 'function') return computed(() => value())
return isRef(value) ? value : ref(value as any)
}
91 changes: 91 additions & 0 deletions composables/display-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useAutoRef, type AutoRef } from './auto-ref.ts'

const safeTags = new Map<string, string>()

function safeTagFor(locale: string) {
let safeTag = safeTags.get(locale)
if (safeTag == null) {
safeTag = new Intl.Locale(locale).baseName
safeTags.set(locale, safeTag)
}
return safeTag
}

type DisplayNamesWrapper = Intl.DisplayNames & {
of(tag: string): string | undefined
}

const displayNamesDicts = new Map<string, DisplayNamesWrapper>()

function getWrapperKey(locale: string, options: Intl.DisplayNamesOptions) {
return JSON.stringify({ ...options, locale })
}

export function createDisplayNames(
locale: string,
options: Intl.DisplayNamesOptions = { type: 'language' }
) {
const wrapperKey = getWrapperKey(locale, options)
let wrapper = displayNamesDicts.get(wrapperKey)

if (wrapper == null) {
const dict = new Intl.DisplayNames(locale, options)

const badTags: string[] = []

wrapper = {
resolvedOptions() {
return dict.resolvedOptions()
},
of(tag: string) {
let attempt = 0

// eslint-disable-next-line no-labels
lookupLoop: do {
let lookup: string
switch (attempt) {
case 0:
lookup = tag
break
case 1:
lookup = safeTagFor(tag)
break
default:
// eslint-disable-next-line no-labels
break lookupLoop
}

if (badTags.includes(lookup)) continue

try {
return dict.of(lookup)
} catch (err) {
console.warn(
`Failed to get display name for ${lookup} using dictionary for ${
this.resolvedOptions().locale
}`
)
badTags.push(lookup)
continue
}
} while (++attempt < 5)

return undefined
},
}

displayNamesDicts.set(wrapperKey, wrapper)
}

return wrapper
}

export function useDisplayNames(
locale: AutoRef<string>,
options?: AutoRef<Intl.DisplayNamesOptions | undefined>
) {
const $locale = useAutoRef(locale)
const $options = useAutoRef(options)

return computed(() => createDisplayNames($locale.value, $options.value))
}
10 changes: 10 additions & 0 deletions helpers/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Checks if any of the modifier keys is down for the event.
* @param e Event that is triggered with the state of modified keys.
* @returns Whether any of the modifier keys is pressed.
*/
export function isModifierKeyDown(
e: Pick<KeyboardEvent, 'ctrlKey' | 'altKey' | 'metaKey' | 'shiftKey'>
) {
return e.ctrlKey || e.altKey || e.metaKey || e.shiftKey
}
48 changes: 48 additions & 0 deletions locales/en-US/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,53 @@
},
"frog.title": {
"message": "Frog"
},
"settings.language.categories.auto": {
"message": "Automatic"
},
"settings.language.categories.default": {
"message": "Standard languages"
},
"settings.language.categories.experimental": {
"message": "Experimental languages"
},
"settings.language.categories.fun": {
"message": "Fun languages"
},
"settings.language.categories.search-result": {
"message": "Search results"
},
"settings.language.description": {
"message": "Choose your preferred language for the site. Translations are contributed by volunteers <crowdin-link>on Crowdin</crowdin-link>."
},
"settings.language.languages.automatic": {
"message": "Sync with the system language"
},
"settings.language.languages.language-label": {
"message": "{translatedName}. {displayName}"
},
"settings.language.languages.language-label-applying": {
"message": "{label}. Applying..."
},
"settings.language.languages.language-label-error": {
"message": "{label}. Error"
},
"settings.language.languages.load-failed": {
"message": "Cannot load this language. Try again in a bit."
},
"settings.language.languages.search-field.description": {
"message": "Submit to focus the first search result"
},
"settings.language.languages.search-field.placeholder": {
"message": "Search for a language..."
},
"settings.language.languages.search-results-announcement": {
"message": "{matches, plural, =0 {No languages match} one {# language matches} other {# languages match}} your search."
},
"settings.language.languages.search.no-results": {
"message": "No languages match your search."
},
"settings.language.title": {
"message": "Language"
}
}
2 changes: 1 addition & 1 deletion locales/en-US/languages.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"en-US": "American English"
"en-US": "English (United States)"
}
8 changes: 6 additions & 2 deletions locales/en-US/meta.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"displayName": {
"description": "The name of the language in dialect form (e.g. Français canadien for French spoken in Canada, not French (Canada))",
"message": "American English"
"description": "Please enter the name of the language in its specific variant or regional form (e.g., English (US) for American English, not just English). If the language does not have any specific variant, simply enter the name of the language (e.g., Français, Deutsch).",
"message": "English (United States)"
},
"searchTerms": {
"description": "Please provide additional search terms associated with the language, if needed, to enhance the search functionality (e.g., American English, Deutschland). Each search term should be entered on a separate line. Translate as a hyphen (-) if no additional terms are needed.",
"message": "USA\nAmerican English"
}
}
20 changes: 18 additions & 2 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,18 @@ const meta = {
* Preferably only the locales that reach a certain threshold of complete
* translations would be included in this array.
*/
const ENABLED_LOCALES: string[] = []
const enabledLocales: string[] = []

/**
* Overrides for the categories of the certain locales.
*/
const localesCategoriesOverrides: Partial<Record<string, 'fun' | 'experimental'>> = {
'en-x-pirate': 'fun',
'en-x-updown': 'fun',
'en-x-lolcat': 'fun',
'en-x-uwu': 'fun',
'ru-x-bandit': 'fun',
}

export default defineNuxtConfig({
app: {
Expand Down Expand Up @@ -214,7 +225,7 @@ export default defineNuxtConfig({

for await (const localeDir of globIterate('locales/*/', { posix: true })) {
const tag = basename(localeDir)
if (!ENABLED_LOCALES.includes(tag) && opts.defaultLocale !== tag) continue
if (!enabledLocales.includes(tag) && opts.defaultLocale !== tag) continue

const locale =
opts.locales.find((locale) => locale.tag === tag) ??
Expand Down Expand Up @@ -246,6 +257,11 @@ export default defineNuxtConfig({
}
}

const categoryOverride = localesCategoriesOverrides[tag]
if (categoryOverride != null) {
;(locale.meta ??= {}).category = categoryOverride
}

const cnDataImport = resolveCompactNumberDataImport(tag)
if (cnDataImport != null) {
;(locale.additionalImports ??= []).push({
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@typescript-eslint/parser": "^5.59.8",
"@vintl/compact-number": "^2.0.4",
"@vintl/how-ago": "^2.0.1",
"@vintl/nuxt": "^1.2.3",
"@vintl/nuxt": "^1.3.0",
"eslint": "^8.41.0",
"eslint-config-prettier": "^8.8.0",
"eslint-import-resolver-typescript": "^3.5.5",
Expand All @@ -41,6 +41,7 @@
"@ltd/j-toml": "^1.38.0",
"dayjs": "^1.11.7",
"floating-vue": "^2.0.0-beta.20",
"fuse.js": "^6.6.2",
"highlight.js": "^11.7.0",
"js-yaml": "^4.1.0",
"jszip": "^3.10.1",
Expand Down
4 changes: 4 additions & 0 deletions pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<CurrencyIcon />
</NavStackItem>
</template>
<NavStackItem link="/settings/language" label="Language">
<LanguagesIcon />
</NavStackItem>
</NavStack>
</aside>
</div>
Expand All @@ -39,6 +42,7 @@ import UserIcon from '~/assets/images/utils/user.svg'
import CurrencyIcon from '~/assets/images/utils/currency.svg'
import ShieldIcon from '~/assets/images/utils/shield.svg'
import KeyIcon from '~/assets/images/utils/key.svg'
import LanguagesIcon from '~/assets/images/utils/languages.svg'
const route = useRoute()
const auth = await useAuth()
Expand Down
Loading

0 comments on commit 467b0fa

Please sign in to comment.