Skip to content

Commit

Permalink
Preserve user language
Browse files Browse the repository at this point in the history
Co-authored-by: iammajid <[email protected]>
  • Loading branch information
SailReal and iammajid committed Feb 7, 2025
1 parent 52620c5 commit b8af5a7
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 6 deletions.
7 changes: 5 additions & 2 deletions backend/src/main/java/org/cryptomator/hub/api/UserDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public final class UserDto extends AuthorityDto {

@JsonProperty("email")
public final String email;
@JsonProperty("language")
public final String language;
@JsonProperty("devices")
public final Set<DeviceResource.DeviceDto> devices;
@JsonProperty("ecdhPublicKey")
Expand All @@ -31,10 +33,11 @@ public final class UserDto extends AuthorityDto {
@JsonProperty("publicKey")
public final String legacyEcdhPublicKey;

UserDto(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("pictureUrl") String pictureUrl, @JsonProperty("email") String email, @JsonProperty("devices") Set<DeviceResource.DeviceDto> devices,
UserDto(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("pictureUrl") String pictureUrl, @JsonProperty("email") String email, @JsonProperty("language") String language, @JsonProperty("devices") Set<DeviceResource.DeviceDto> devices,
@Nullable @JsonProperty("ecdhPublicKey") @OnlyBase64Chars String ecdhPublicKey, @Nullable @JsonProperty("ecdsaPublicKey") @OnlyBase64Chars String ecdsaPublicKey, @Nullable @JsonProperty("privateKeys") @ValidJWE String privateKeys, @Nullable @JsonProperty("setupCode") @ValidJWE String setupCode) {
super(id, Type.USER, name, pictureUrl);
this.email = email;
this.language = language;
this.devices = devices;
this.ecdhPublicKey = ecdhPublicKey;
this.ecdsaPublicKey = ecdsaPublicKey;
Expand All @@ -46,6 +49,6 @@ public final class UserDto extends AuthorityDto {
}

public static UserDto justPublicInfo(User user) {
return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), Set.of(), user.getEcdhPublicKey(), user.getEcdsaPublicKey(),null, null);
return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), user.getLanguage(), Set.of(), user.getEcdhPublicKey(), user.getEcdsaPublicKey(),null, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public Response putMe(@Nullable @Valid UserDto dto) {
eventLogger.logUserKeysChanged(jwt.getSubject(), jwt.getName());
}
updateDevices(user, dto);
user.setLanguage(dto.language);
}
userRepo.persist(user);
return Response.created(URI.create(".")).build();
Expand Down Expand Up @@ -157,7 +158,7 @@ public UserDto getMe(@QueryParam("withDevices") boolean withDevices) {
User user = userRepo.findById(jwt.getSubject());
Function<Device, DeviceResource.DeviceDto> mapDevices = d -> new DeviceResource.DeviceDto(d.getId(), d.getName(), d.getType(), d.getPublickey(), d.getUserPrivateKeys(), d.getOwner().getId(), d.getCreationTime().truncatedTo(ChronoUnit.MILLIS));
var devices = withDevices ? user.devices.stream().map(mapDevices).collect(Collectors.toSet()) : Set.<DeviceResource.DeviceDto>of();
return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), devices, user.getEcdhPublicKey(), user.getEcdsaPublicKey(), user.getPrivateKeys(), user.getSetupCode());
return new UserDto(user.getId(), user.getName(), user.getPictureUrl(), user.getEmail(), user.getLanguage(), devices, user.getEcdhPublicKey(), user.getEcdsaPublicKey(), user.getPrivateKeys(), user.getSetupCode());
}

@POST
Expand Down
11 changes: 11 additions & 0 deletions backend/src/main/java/org/cryptomator/hub/entities/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public class User extends Authority {
@Column(name = "email")
private String email;

@Column(name = "language")
private String language;

@Column(name = "ecdh_publickey")
private String ecdhPublicKey;

Expand Down Expand Up @@ -77,6 +80,14 @@ public void setEmail(String email) {
this.email = email;
}

public String getLanguage() {
return language;
}

public void setLanguage(String language) {
this.language = language;
}

public String getEcdhPublicKey() {
return ecdhPublicKey;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "user_details" add "language" VARCHAR;
1 change: 1 addition & 0 deletions frontend/src/common/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type UserDto = {
name: string;
pictureUrl?: string;
email: string;
language: string;
devices: DeviceDto[];
accessibleVaults: VaultDto[];
ecdhPublicKey?: string;
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/components/UserProfile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</ListboxButton>
<transition leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
<ListboxOptions class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-hidden text-sm">
<ListboxOption v-for="locale in Locale" :key="locale" v-slot="{ active, selected }" class="relative cursor-default select-none py-2 pl-3 pr-9 ui-not-active:text-gray-900 ui-active:text-white ui-active:bg-primary" :value="locale">
<ListboxOption v-for="locale in Locale" :key="locale" v-slot="{ active, selected }" class="relative cursor-default select-none py-2 pl-3 pr-9 ui-not-active:text-gray-900 ui-active:text-white ui-active:bg-primary" :value="locale" @click="saveLanguage(locale)">
<span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">{{ t(`locale.${locale}`) }}</span>
<span v-if="selected" :class="[active ? 'text-white' : 'text-primary', 'absolute inset-y-0 right-0 flex items-center pr-4']">
<CheckIcon class="h-5 w-5" aria-hidden="true" />
Expand Down Expand Up @@ -99,6 +99,14 @@ async function fetchData() {
}
}
async function saveLanguage(locale: Locale) {
const updatedUser = me.value;
if (updatedUser !== undefined) {
updatedUser.language = locale.toString();
await backend.users.putMe(updatedUser);
}
}
function openKeycloakUserAccount() {
window.open(keycloakUserAccountURL.value, '_blank');
}
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { I18nOptions } from 'vue-i18n';
import de from './de-DE.json';
import en from './en-US.json';

import { createI18n } from 'vue-i18n';

export enum Locale {
EN = 'en',
DE = 'de'
Expand Down Expand Up @@ -37,3 +39,22 @@ export const numberFormats: I18nOptions['numberFormats'] = {
}
}
};

export const mapToLocal = (local: string): Locale =>
(Object.values(Locale) as string[]).includes(local)
? (local as Locale)
: Locale.EN;

const i18n = createI18n({
locale: navigator.language,
fallbackLocale: Locale.EN,
messages,
datetimeFormats,
numberFormats,
globalInjection: true,
missingWarn: false,
fallbackWarn: false,
legacy: false
});

export default i18n;
7 changes: 5 additions & 2 deletions frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import AdminSettings from '../components/AdminSettings.vue';
import AuditLog from '../components/AuditLog.vue';
import AuthenticatedMain from '../components/AuthenticatedMain.vue';
import CreateVault from '../components/CreateVault.vue';
import Forbidden from '../components/Forbidden.vue';
import InitialSetup from '../components/InitialSetup.vue';
import NotFound from '../components/NotFound.vue';
import UnlockError from '../components/UnlockError.vue';
import UnlockSuccess from '../components/UnlockSuccess.vue';
import UserProfile from '../components/UserProfile.vue';
import VaultDetails from '../components/VaultDetails.vue';
import VaultList from '../components/VaultList.vue';
import Forbidden from '../components/Forbidden.vue';

import i18n, { mapToLocal } from '../i18n';

function checkRole(role: string): NavigationGuardWithThis<undefined> {
return async (to, _) => {
Expand Down Expand Up @@ -176,10 +178,11 @@ router.beforeEach((to, from, next) => {

// THIRD check user/browser keys (requires auth)
router.beforeEach(async (to) => {
const me = await userdata.me;
i18n.global.locale.value = mapToLocal(me.language);
if (to.meta.skipSetup) {
return;
}
const me = await userdata.me;
if (!me.setupCode) {
return { path: '/app/setup' };
}
Expand Down

0 comments on commit b8af5a7

Please sign in to comment.