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

User language preference and user language menu #581

Merged
merged 14 commits into from
Jul 2, 2021
15 changes: 15 additions & 0 deletions app/graphql/mutations/update_user_language_preference.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Mutations
class UpdateUserLanguagePreference < BaseMutation
description 'Sets user language preference'

null true

argument :id, ID, required: true

ovillegaszen marked this conversation as resolved.
Show resolved Hide resolved

def resolve(**args)
UserPreferenceResolver.update_user_language_preference(object, args, context)
end
end
end

10 changes: 10 additions & 0 deletions app/graphql/resolvers/user_preference_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,15 @@ def confirm_profile_settings(context)

preference
end

def update_user_language_preference(_, args, _context)
language_id = args[:id]
current_user_id = _context[:current_user].id

user_preference = UserPreference.find_by(user_id: current_user_id)
user_preference.update(language_id: language_id)

{ id: language_id }
end
end
end
15 changes: 15 additions & 0 deletions app/graphql/types/language_graph_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Types
class LanguageGraphType < Types::BaseObject
graphql_name 'Language'
description 'Supported languages'

implements GraphQL::Relay::Node.interface
global_id_field :gid

field :id, ID, null: false
field :language_code, String, null: false
field :language_name, String, null: false

end
end

1 change: 1 addition & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ class MutationType < Types::BaseObject
field :delete_tag, TagGraphType, mutation: Mutations::DeleteTag

field :confirm_profile_settings, UserPreferenceGraphType, mutation: Mutations::ConfirmProfileSettings
field :update_user_language_preference, UserPreferenceGraphType, mutation: Mutations::UpdateUserLanguagePreference
end
end
6 changes: 6 additions & 0 deletions app/graphql/types/query_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,11 @@ def tag(id:)
def tags
Tag.all
end

field :languages, [LanguageGraphType], null: true
def languages
Language.all
end

end
end
1 change: 1 addition & 0 deletions app/graphql/types/user_preference_graph_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ class UserPreferenceGraphType < Types::BaseObject

field :id, ID, null: false
field :confirmed_profile_settings, Boolean, null: false
field :language_id, ID, null: false
end
end
5 changes: 3 additions & 2 deletions app/javascript/components/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const MuiTheme = {
},
}

const App = ({ loading, currentUser, offices, userPopover, toggleUserPopover, children }) => {
const App = ({ loading, currentUser, offices, userPopover, toggleUserPopover, children, languages }) => {
if (!currentUser) return null

return (
Expand All @@ -31,8 +31,9 @@ const App = ({ loading, currentUser, offices, userPopover, toggleUserPopover, ch
<div>
<Header>
<UserProfileMenu
languages={languages}
offices={offices}
togglePopover={e => toggleUserPopover(e.currentTarget)}
togglePopover={(e) => toggleUserPopover(e.currentTarget)}
popover={userPopover}
/>
</Header>
Expand Down
5 changes: 2 additions & 3 deletions app/javascript/components/UserProfileMenu/LanguageMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ const LanguageMenu = ( { languages, previousMenuValue, selectedItem, t }) =>
</PreviousItem>
<Separator />
{languages.map((language, i) => (
<Item key={i} value={{ office: selectedItem.office, language }}>
{' '}
{language.label}{' '}
<Item key={i} value={{ office: selectedItem.office, language: { label: language.languageName, value: language.languageCode } }}>
{language.languageName}
</Item>
))}
</>
Expand Down
22 changes: 13 additions & 9 deletions app/javascript/components/UserProfileMenu/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useContext } from 'react'
import React, { useState, useContext, useEffect } from 'react'
import * as R from 'ramda'
import styled from 'styled-components'
import { withRouter } from 'react-router'
Expand All @@ -14,6 +14,7 @@ import { Avatar } from '@zendeskgarden/react-avatars'
import { MD } from '@zendeskgarden/react-typography'

import UpdateUserOfficeMutation from '/mutations/UpdateUserOfficeMutation.gql'
import UpdateUserLanguagePreferenceMutation from '/mutations/UpdateUserLanguagePreferenceMutation.gql'
import { UserContext, FilterContext } from '/context'
import GeneralSettingsMenu from './GeneralSettingsMenu'
import DefaultOfficeMenu from './DefaultOfficeMenu'
Expand All @@ -34,30 +35,31 @@ const UserName = styled(MD)`
font-weight: bold;
`

const UserProfileMenu = ({ offices, location, router, togglePopover }) => {
const languages = [
{ label: 'English', value: 'en' },
{ label: '日本語', value: 'ja' },
{ label: 'Español', value: 'es' },
]
const UserProfileMenu = ({ offices, location, router, togglePopover, languages }) => {

const { i18n, t } = useTranslation()
const { currentUser, setOffice } = useContext(UserContext)
const { setOfficeValue } = useContext(FilterContext)
const [ isOpen, setIsOpen ] = useState(false)
const [ tempSelectedItem, setTempSelectedItem ] = useState()
const [ selectedItem, setSelectedItem ] = useState({ office: currentUser.office, language: languages[0] })
// language: reach out to local storage first
ovillegaszen marked this conversation as resolved.
Show resolved Hide resolved
const [ selectedItem, setSelectedItem ] = useState({ office: currentUser.office, language: i18n.language })
ovillegaszen marked this conversation as resolved.
Show resolved Hide resolved
const [ updateDefaultOffice ] = useMutation(UpdateUserOfficeMutation)
const [ updateUserLanguagePreference ] = useMutation(UpdateUserLanguagePreferenceMutation)

if (R.isNil(currentUser) || R.isEmpty(currentUser)) return null

useEffect(() => {
const selectedLanguage = R.find(R.propEq("languageCode", i18n.language), languages)
setSelectedItem({ ...selectedItem, language: { label: selectedLanguage.languageName, value: selectedLanguage.languageCode } })
ovillegaszen marked this conversation as resolved.
Show resolved Hide resolved
}, [i18n.language])

const handleOfficeSelect = office =>
updateDefaultOffice({ variables: { userId: currentUser.id, officeId: office.id } })
.then(response => setOffice(R.path(['data', 'updateUserOffice', 'office'], response)))
.then(_ => setOfficeValue(office.id))
.then(_ => togglePopover('user'))


const handleStateChange = (changes, stateAndHelpers) => {
if (R.hasPath(['isOpen'])(changes)) {
setIsOpen(
Expand Down Expand Up @@ -95,6 +97,8 @@ const UserProfileMenu = ({ offices, location, router, togglePopover }) => {
i18n.changeLanguage(selectedItem.language.value, () => {
// TODO: Handle callback (error/success)
})
const selectedLanguage = R.find(R.propEq("languageCode", selectedItem.language.value), languages)
ovillegaszen marked this conversation as resolved.
Show resolved Hide resolved
updateUserLanguagePreference({ variables: { id: selectedLanguage.id } })
setSelectedItem(selectedItem)
}
// The following are for accessibility support for Link navigation
Expand Down
5 changes: 5 additions & 0 deletions app/javascript/fragments/LanguageEntry.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fragment LanguageEntry on Language {
id
languageCode
languageName
}
1 change: 1 addition & 0 deletions app/javascript/fragments/UserPreferenceEntry.gql
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fragment UserPreferenceEntry on UserPreference {
id
confirmedProfileSettings
languageId
}
2 changes: 1 addition & 1 deletion app/javascript/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ i18n
.use(Backend)
.use(initReactI18next) // passes i18n down to react-i18next
.init({
lng: 'en',
lng: 'en', // reach out to local storage first.
fallbackLng: 'en',

keySeparator: false, // we do not use keys in form messages.welcome
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation updateUserLanguagePreference($id:ID!) {
updateUserLanguagePreference(id: $id) {
id
}
}
26 changes: 19 additions & 7 deletions app/javascript/pages/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,23 @@ import { togglePopover } from 'actions'

import AppQuery from './query.gql'

// TODO refactor it into AppPage as a functional component
const MomentLocale = ({ children }) => {
// TODO refactor it into AppPage as a functional component // todo: change name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the name change todo completed?

const RemoteLocale = ({ children, currentUser, languages }) => {
const { i18n } = useTranslation()

useEffect(() => {
moment.locale(i18n.language)
}, [i18n.language])
const languageId = R.pipe(R.prop('preference'), R.prop('languageId'))(currentUser)
ovillegaszen marked this conversation as resolved.
Show resolved Hide resolved

if (!R.isNil(languageId)) {
const chosenLanguage = R.find(R.propEq('id', languageId))(languages)
const languageCode = R.prop('languageCode', chosenLanguage)
i18n.changeLanguage(languageCode, () => {
// TODO: Handle callback (error/success)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to be done in a separate issue?

})
moment.locale(languageCode)
}
}, [currentUser])

return <>{children}</>
}

Expand All @@ -28,15 +39,16 @@ class AppPage extends Component {

render() {
const {
data: { networkStatus, currentUser, offices },
data: { networkStatus, currentUser, offices, languages },
togglePopover,
popover,
children,
} = this.props

return (
<MomentLocale>
<RemoteLocale currentUser={currentUser} languages={languages}>
<App
languages={languages}
loading={networkStatus === NetworkStatus.loading}
currentUser={currentUser}
offices={offices}
Expand All @@ -45,7 +57,7 @@ class AppPage extends Component {
>
{children}
</App>
</MomentLocale>
</RemoteLocale>
)
}
}
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/pages/App/query.gql
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "fragments/UserEntry.gql"
#import "fragments/OfficeEntry.gql"
#import "fragments/UserPreferenceEntry.gql"
#import "fragments/LanguageEntry.gql"

query AppQuery {
currentUser {
Expand All @@ -17,4 +18,7 @@ query AppQuery {
offices(sortBy: NAME_ASC) {
...OfficeEntry
}
languages {
...LanguageEntry
}
}
3 changes: 3 additions & 0 deletions app/models/language.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Language < ApplicationRecord
has_many :user_preferences
end
1 change: 1 addition & 0 deletions app/models/user_preference.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class UserPreference < ApplicationRecord
belongs_to :user
belongs_to :language

scope :for_user, ->(user) { where(user: user) }
end
16 changes: 16 additions & 0 deletions db/migrate/20201216035051_create_language_table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class CreateLanguageTable < ActiveRecord::Migration[5.2]
def change
create_table :languages do |t|
t.string :language_code
t.string :language_name
end

add_index :languages, :language_code, unique: true

return unless Language.all.length.zero?

[
['en', 'English'], ['es', 'Español'], ['ja', '日本語']
].each { |language| Language.create_with(language_code: language[0]).find_or_create_by(language_name: language[1]) }
end
end
6 changes: 6 additions & 0 deletions db/migrate/20201216035300_add_language_to_user_preference.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddLanguageToUserPreference < ActiveRecord::Migration[5.2]
def change
add_column :user_preferences, :language_id, :bigint, default:1
add_foreign_key :user_preferences, :languages
end
end
10 changes: 9 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2020_05_06_054322) do
ActiveRecord::Schema.define(version: 2020_12_16_035300) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -87,6 +87,12 @@
t.index ["user_id"], name: "index_individual_events_on_user_id"
end

create_table "languages", force: :cascade do |t|
t.string "language_code"
t.string "language_name"
t.index ["language_code"], name: "index_languages_on_language_code", unique: true
end

create_table "offices", id: :serial, force: :cascade do |t|
t.string "name", null: false
t.string "identifier", null: false
Expand Down Expand Up @@ -145,6 +151,7 @@
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "language_id", default: 1
t.index ["user_id"], name: "index_user_preferences_on_user_id"
end

Expand Down Expand Up @@ -184,5 +191,6 @@
add_foreign_key "event_tags", "tags"
add_foreign_key "individual_event_tags", "individual_events"
add_foreign_key "individual_event_tags", "tags"
add_foreign_key "user_preferences", "languages"
add_foreign_key "user_preferences", "users"
end