feat: add google drive dam foundation
This commit is contained in:
41
frontend/src/features/content/stores/mediaLibraryStore.js
Normal file
41
frontend/src/features/content/stores/mediaLibraryStore.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
|
||||
export const useMediaLibraryStore = defineStore('media-library', () => {
|
||||
const client = useClient();
|
||||
|
||||
const dam = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
async function fetchWorkspaceDam(workspaceId) {
|
||||
if (!workspaceId) {
|
||||
dam.value = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await client.get(`/api/workspaces/${workspaceId}/dam`);
|
||||
dam.value = response.data ?? null;
|
||||
return dam.value;
|
||||
} catch (fetchError) {
|
||||
console.error('Failed to load workspace DAM:', fetchError);
|
||||
dam.value = null;
|
||||
error.value = 'Failed to load the media library.';
|
||||
return null;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dam,
|
||||
isLoading,
|
||||
error,
|
||||
fetchWorkspaceDam,
|
||||
};
|
||||
});
|
||||
@@ -1,25 +1,35 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMediaLibraryStore } from '@/features/content/stores/mediaLibraryStore.js';
|
||||
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
|
||||
import {
|
||||
mdiCheckCircleOutline,
|
||||
mdiCloudSyncOutline,
|
||||
mdiFolderGoogleDrive,
|
||||
mdiImageMultipleOutline,
|
||||
mdiVideoOutline,
|
||||
} from '@mdi/js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const mediaLibraryStore = useMediaLibraryStore();
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
|
||||
const mediaTypes = [
|
||||
{ label: t('mediaLibrary.mediaTypes.images'), icon: mdiImageMultipleOutline },
|
||||
{ label: t('mediaLibrary.mediaTypes.videos'), icon: mdiVideoOutline },
|
||||
];
|
||||
const activeWorkspaceId = computed(() => workspaceStore.activeWorkspaceId);
|
||||
const dam = computed(() => mediaLibraryStore.dam);
|
||||
const assets = computed(() => dam.value?.assets ?? []);
|
||||
const folderPath = computed(() => dam.value?.folder?.path ?? t('mediaLibrary.notConfigured'));
|
||||
|
||||
const workflowSteps = [
|
||||
t('mediaLibrary.workflow.connectDrive'),
|
||||
t('mediaLibrary.workflow.syncAssets'),
|
||||
t('mediaLibrary.workflow.organizeLibrary'),
|
||||
];
|
||||
|
||||
async function loadDam() {
|
||||
await mediaLibraryStore.fetchWorkspaceDam(activeWorkspaceId.value);
|
||||
}
|
||||
|
||||
onMounted(loadDam);
|
||||
watch(activeWorkspaceId, loadDam);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -35,26 +45,40 @@
|
||||
<div class="hero-card-icon">
|
||||
<v-icon :icon="mdiFolderGoogleDrive" />
|
||||
</div>
|
||||
<strong>{{ t('mediaLibrary.syncCard.title') }}</strong>
|
||||
<span>{{ t('mediaLibrary.syncCard.description') }}</span>
|
||||
<strong>{{ dam?.backingStore?.isConfigured ? dam.backingStore.rootFolderName : t('mediaLibrary.syncCard.title') }}</strong>
|
||||
<span>{{ dam?.backingStore?.isConfigured ? folderPath : t('mediaLibrary.syncCard.description') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="mediaLibraryStore.isLoading"
|
||||
class="page-message"
|
||||
>
|
||||
{{ t('mediaLibrary.loading') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="mediaLibraryStore.error"
|
||||
class="page-message error"
|
||||
>
|
||||
{{ mediaLibraryStore.error }}
|
||||
</div>
|
||||
|
||||
<div class="content-grid">
|
||||
<article class="panel">
|
||||
<div class="panel-header">
|
||||
<strong>{{ t('mediaLibrary.mediaTypesTitle') }}</strong>
|
||||
<span>{{ t('mediaLibrary.mediaTypesDescription') }}</span>
|
||||
<strong>{{ t('mediaLibrary.damRootTitle') }}</strong>
|
||||
<span>{{ dam?.backingStore?.isConfigured ? dam.backingStore.rootFolderUrl : t('mediaLibrary.notConfiguredDescription') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="media-type-list">
|
||||
<div
|
||||
v-for="type in mediaTypes"
|
||||
:key="type.label"
|
||||
class="media-type-item"
|
||||
>
|
||||
<v-icon :icon="type.icon" />
|
||||
<span>{{ type.label }}</span>
|
||||
<div class="media-type-item">
|
||||
<v-icon :icon="mdiFolderGoogleDrive" />
|
||||
<span>{{ folderPath }}</span>
|
||||
</div>
|
||||
<div class="media-type-item">
|
||||
<v-icon :icon="mdiCheckCircleOutline" />
|
||||
<span>{{ t('mediaLibrary.workspaceSlug', { slug: dam?.workspaceSlug ?? '-' }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -87,8 +111,40 @@
|
||||
<v-icon :icon="mdiCloudSyncOutline" />
|
||||
<span>{{ t('mediaLibrary.statusLabel') }}</span>
|
||||
</div>
|
||||
<strong>{{ t('mediaLibrary.pendingTitle') }}</strong>
|
||||
<p>{{ t('mediaLibrary.pendingDescription') }}</p>
|
||||
<strong>{{ dam?.backingStore?.isConfigured ? t('mediaLibrary.configuredTitle') : t('mediaLibrary.pendingTitle') }}</strong>
|
||||
<p>{{ dam?.backingStore?.isConfigured ? t('mediaLibrary.configuredDescription') : t('mediaLibrary.pendingDescription') }}</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<div class="panel-header">
|
||||
<strong>{{ t('mediaLibrary.assetsTitle') }}</strong>
|
||||
<span>{{ t('mediaLibrary.assetsDescription') }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!assets.length"
|
||||
class="empty-state"
|
||||
>
|
||||
{{ t('mediaLibrary.emptyAssets') }}
|
||||
</div>
|
||||
<div
|
||||
v-for="asset in assets"
|
||||
:key="asset.id"
|
||||
class="asset-row"
|
||||
>
|
||||
<div>
|
||||
<strong>{{ asset.displayName }}</strong>
|
||||
<span>{{ asset.assetType }} · {{ asset.googleDriveWorkspaceFolderPath || folderPath }}</span>
|
||||
</div>
|
||||
<a
|
||||
v-if="asset.googleDriveLink"
|
||||
:href="asset.googleDriveLink"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ t('mediaLibrary.openDrive') }}
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
@@ -189,7 +245,8 @@
|
||||
}
|
||||
|
||||
.media-type-list,
|
||||
.workflow-list {
|
||||
.workflow-list,
|
||||
.asset-row-list {
|
||||
@apply flex flex-col gap-3;
|
||||
}
|
||||
|
||||
@@ -204,6 +261,23 @@
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.asset-row {
|
||||
@apply flex items-center justify-between gap-4 rounded-[1.1rem] border px-4 py-3;
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
background: rgba(248, 250, 252, 0.9);
|
||||
}
|
||||
|
||||
.asset-row div {
|
||||
@apply flex min-w-0 flex-col gap-1;
|
||||
}
|
||||
|
||||
.asset-row span,
|
||||
.asset-row a,
|
||||
.empty-state {
|
||||
@apply text-sm;
|
||||
color: #526178;
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
background: linear-gradient(135deg, rgba(255, 247, 237, 0.95), rgba(255, 255, 255, 0.98));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export const useOrganizationStore = defineStore('organization', () => {
|
||||
const isLoadingMembershipTiers = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const isUpdatingMembershipTier = ref(false);
|
||||
const isSavingGoogleDriveDam = ref(false);
|
||||
const isAddingMember = ref(false);
|
||||
const isUploadingLogo = ref(false);
|
||||
const error = ref(null);
|
||||
@@ -260,6 +261,45 @@ export const useOrganizationStore = defineStore('organization', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateGoogleDriveDam(organizationId, payload) {
|
||||
if (!authStore.isAuthenticated || !organizationId) {
|
||||
throw new Error('You must be authenticated to update organization connectors.');
|
||||
}
|
||||
|
||||
isSavingGoogleDriveDam.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await client.put(`/api/organizations/${organizationId}/google-drive-dam`, payload);
|
||||
const googleDriveDam = response.data;
|
||||
const currentDetails = detailsById.value[organizationId];
|
||||
|
||||
if (currentDetails) {
|
||||
detailsById.value = {
|
||||
...detailsById.value,
|
||||
[organizationId]: {
|
||||
...currentDetails,
|
||||
googleDriveDam,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
organizations.value = organizations.value.map(organization =>
|
||||
organization.id === organizationId
|
||||
? { ...organization, googleDriveDam }
|
||||
: organization
|
||||
);
|
||||
|
||||
return googleDriveDam;
|
||||
} catch (updateError) {
|
||||
console.error('Failed to update Google Drive DAM configuration:', updateError);
|
||||
error.value = 'Failed to update Google Drive DAM configuration.';
|
||||
throw updateError;
|
||||
} finally {
|
||||
isSavingGoogleDriveDam.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function addMember(organizationId, payload) {
|
||||
if (!authStore.isAuthenticated || !organizationId) {
|
||||
throw new Error('You must be authenticated to add an organization member.');
|
||||
@@ -371,6 +411,7 @@ export const useOrganizationStore = defineStore('organization', () => {
|
||||
isLoadingMembershipTiers,
|
||||
isSaving,
|
||||
isUpdatingMembershipTier,
|
||||
isSavingGoogleDriveDam,
|
||||
isAddingMember,
|
||||
isUploadingLogo,
|
||||
error,
|
||||
@@ -383,6 +424,7 @@ export const useOrganizationStore = defineStore('organization', () => {
|
||||
createOrganization,
|
||||
updateOrganization,
|
||||
updateMembershipTier,
|
||||
updateGoogleDriveDam,
|
||||
addMember,
|
||||
uploadLogo,
|
||||
};
|
||||
|
||||
@@ -38,6 +38,12 @@
|
||||
const membershipTierForm = reactive({
|
||||
membershipTierId: null,
|
||||
});
|
||||
const googleDriveDamForm = reactive({
|
||||
isEnabled: false,
|
||||
rootFolderId: '',
|
||||
rootFolderName: '',
|
||||
rootFolderUrl: '',
|
||||
});
|
||||
const memberRoleOptions = ['Member', 'Admin', 'BillingManager', 'ConnectorManager'];
|
||||
|
||||
const organizationId = computed(() => route.params.organizationId);
|
||||
@@ -195,6 +201,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function submitGoogleDriveDam() {
|
||||
settingsError.value = null;
|
||||
settingsStatus.value = null;
|
||||
|
||||
if (
|
||||
googleDriveDamForm.isEnabled &&
|
||||
(!googleDriveDamForm.rootFolderId.trim() ||
|
||||
!googleDriveDamForm.rootFolderName.trim() ||
|
||||
!googleDriveDamForm.rootFolderUrl.trim())
|
||||
) {
|
||||
settingsError.value = t('organizationSettings.sections.connections.googleDrive.required');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await organizationStore.updateGoogleDriveDam(organizationId.value, {
|
||||
isEnabled: googleDriveDamForm.isEnabled,
|
||||
rootFolderId: googleDriveDamForm.rootFolderId.trim() || null,
|
||||
rootFolderName: googleDriveDamForm.rootFolderName.trim() || null,
|
||||
rootFolderUrl: googleDriveDamForm.rootFolderUrl.trim() || null,
|
||||
});
|
||||
settingsStatus.value = t('organizationSettings.sections.connections.googleDrive.saved');
|
||||
} catch (error) {
|
||||
console.error('Failed to save Google Drive DAM configuration:', error);
|
||||
settingsError.value = t('organizationSettings.sections.connections.googleDrive.saveFailed');
|
||||
}
|
||||
}
|
||||
|
||||
function formatTierSummary(tier) {
|
||||
const price = tier.isCustom || tier.monthlyPriceCents === null || tier.monthlyPriceCents === undefined
|
||||
? t('organizationSettings.tiers.customPrice')
|
||||
@@ -232,6 +266,10 @@
|
||||
currentOrganization => {
|
||||
profileForm.name = currentOrganization?.name ?? '';
|
||||
membershipTierForm.membershipTierId = currentOrganization?.membershipTier?.id ?? null;
|
||||
googleDriveDamForm.isEnabled = Boolean(currentOrganization?.googleDriveDam?.isEnabled);
|
||||
googleDriveDamForm.rootFolderId = currentOrganization?.googleDriveDam?.rootFolderId ?? '';
|
||||
googleDriveDamForm.rootFolderName = currentOrganization?.googleDriveDam?.rootFolderName ?? '';
|
||||
googleDriveDamForm.rootFolderUrl = currentOrganization?.googleDriveDam?.rootFolderUrl ?? '';
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
@@ -457,10 +495,55 @@
|
||||
|
||||
<div
|
||||
v-else-if="activeSection.key === 'connections'"
|
||||
class="placeholder-panel"
|
||||
class="settings-form"
|
||||
>
|
||||
<strong>{{ t('organizationSettings.sections.connections.placeholderTitle') }}</strong>
|
||||
<span>{{ t('organizationSettings.sections.connections.placeholderText') }}</span>
|
||||
<div class="placeholder-panel">
|
||||
<strong>{{ t('organizationSettings.sections.connections.googleDrive.title') }}</strong>
|
||||
<span>{{ t('organizationSettings.sections.connections.googleDrive.description') }}</span>
|
||||
</div>
|
||||
|
||||
<v-form
|
||||
class="settings-form"
|
||||
@submit.prevent="submitGoogleDriveDam"
|
||||
>
|
||||
<v-switch
|
||||
v-model="googleDriveDamForm.isEnabled"
|
||||
:label="t('organizationSettings.sections.connections.googleDrive.enabled')"
|
||||
color="primary"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="googleDriveDamForm.rootFolderName"
|
||||
:label="t('organizationSettings.sections.connections.googleDrive.rootFolderName')"
|
||||
:disabled="!googleDriveDamForm.isEnabled || organizationStore.isSavingGoogleDriveDam"
|
||||
maxlength="256"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="googleDriveDamForm.rootFolderId"
|
||||
:label="t('organizationSettings.sections.connections.googleDrive.rootFolderId')"
|
||||
:disabled="!googleDriveDamForm.isEnabled || organizationStore.isSavingGoogleDriveDam"
|
||||
maxlength="256"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="googleDriveDamForm.rootFolderUrl"
|
||||
:label="t('organizationSettings.sections.connections.googleDrive.rootFolderUrl')"
|
||||
:disabled="!googleDriveDamForm.isEnabled || organizationStore.isSavingGoogleDriveDam"
|
||||
maxlength="2048"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-btn
|
||||
color="primary"
|
||||
type="submit"
|
||||
:loading="organizationStore.isSavingGoogleDriveDam"
|
||||
>
|
||||
{{ t('organizationSettings.sections.connections.googleDrive.save') }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
const activeTab = ref('general');
|
||||
const settingsForm = reactive({
|
||||
name: '',
|
||||
slug: '',
|
||||
timeZone: '',
|
||||
approvalMode: 'Required',
|
||||
schedulePostsAutomaticallyOnApproval: false,
|
||||
@@ -56,6 +57,7 @@
|
||||
const workspaceApprovalSteps = normalizeApprovalSteps(workspace.approvalSteps ?? []);
|
||||
|
||||
return settingsForm.name.trim() !== workspace.name ||
|
||||
settingsForm.slug.trim() !== workspace.slug ||
|
||||
settingsForm.timeZone.trim() !== workspace.timeZone ||
|
||||
settingsForm.approvalMode !== (workspace.approvalMode ?? 'Required') ||
|
||||
settingsForm.schedulePostsAutomaticallyOnApproval !== Boolean(workspace.schedulePostsAutomaticallyOnApproval) ||
|
||||
@@ -196,6 +198,7 @@
|
||||
() => workspaceStore.activeWorkspace,
|
||||
workspace => {
|
||||
settingsForm.name = workspace?.name ?? '';
|
||||
settingsForm.slug = workspace?.slug ?? '';
|
||||
settingsForm.timeZone = workspace?.timeZone ?? '';
|
||||
settingsForm.approvalMode = workspace?.approvalMode ?? 'Required';
|
||||
settingsForm.schedulePostsAutomaticallyOnApproval = Boolean(workspace?.schedulePostsAutomaticallyOnApproval);
|
||||
@@ -237,9 +240,10 @@
|
||||
settingsStatus.value = null;
|
||||
|
||||
const name = settingsForm.name.trim();
|
||||
const slug = settingsForm.slug.trim();
|
||||
const timeZone = settingsForm.timeZone.trim();
|
||||
|
||||
if (!name || !timeZone) {
|
||||
if (!name || !slug || !timeZone) {
|
||||
settingsError.value = t('workspaceSettings.errors.required');
|
||||
return;
|
||||
}
|
||||
@@ -254,6 +258,7 @@
|
||||
try {
|
||||
await workspaceStore.updateWorkspace(workspace.id, {
|
||||
name,
|
||||
slug,
|
||||
timeZone,
|
||||
approvalMode: settingsForm.approvalMode,
|
||||
schedulePostsAutomaticallyOnApproval: settingsForm.schedulePostsAutomaticallyOnApproval,
|
||||
@@ -504,6 +509,15 @@
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="settingsForm.slug"
|
||||
:label="t('workspaceSettings.fields.slug')"
|
||||
:hint="t('workspaceSettings.fields.slugHint')"
|
||||
:disabled="workspaceStore.isUpdating"
|
||||
variant="outlined"
|
||||
persistent-hint
|
||||
/>
|
||||
|
||||
<label class="field">
|
||||
<span>{{ t('workspaceSettings.fields.timeZone') }}</span>
|
||||
<TimeZoneSelect
|
||||
|
||||
@@ -504,7 +504,19 @@
|
||||
"title": "Connections",
|
||||
"description": "Organization-level connectors and data mappings.",
|
||||
"placeholderTitle": "No organization connections configured",
|
||||
"placeholderText": "Connector authorization flows are intentionally out of scope for this UI shell."
|
||||
"placeholderText": "Connector authorization flows are intentionally out of scope for this UI shell.",
|
||||
"googleDrive": {
|
||||
"title": "Google Drive DAM",
|
||||
"description": "Use an organization Drive folder as the media library backing store. Workspace media is organized by workspace slug.",
|
||||
"enabled": "Use Google Drive as the DAM backing store",
|
||||
"rootFolderName": "Root folder name",
|
||||
"rootFolderId": "Root folder ID",
|
||||
"rootFolderUrl": "Root folder URL",
|
||||
"save": "Save Google Drive DAM",
|
||||
"saved": "Google Drive DAM configuration saved.",
|
||||
"required": "Root folder name, ID, and URL are required when Google Drive DAM is enabled.",
|
||||
"saveFailed": "Google Drive DAM configuration could not be saved."
|
||||
}
|
||||
},
|
||||
"workspaces": {
|
||||
"title": "Workspaces",
|
||||
@@ -1120,6 +1132,8 @@
|
||||
},
|
||||
"fields": {
|
||||
"name": "Workspace name",
|
||||
"slug": "Workspace slug",
|
||||
"slugHint": "Used as the folder name under the organization DAM root.",
|
||||
"timeZone": "Time zone",
|
||||
"memberEmail": "Member email",
|
||||
"memberRole": "Role"
|
||||
@@ -1295,6 +1309,17 @@
|
||||
"organizeLibrary": "Review, tag, and reuse media from one workspace-level place."
|
||||
},
|
||||
"statusLabel": "Status",
|
||||
"loading": "Loading media library...",
|
||||
"notConfigured": "Google Drive DAM not configured",
|
||||
"notConfiguredDescription": "Configure the organization Google Drive DAM connection before this workspace can resolve a backing folder.",
|
||||
"damRootTitle": "DAM folder",
|
||||
"workspaceSlug": "Workspace slug: {slug}",
|
||||
"configuredTitle": "Google Drive backing store configured",
|
||||
"configuredDescription": "Socialize is using the organization Drive root and this workspace slug to resolve media library metadata.",
|
||||
"assetsTitle": "Workspace assets",
|
||||
"assetsDescription": "Google Drive assets currently linked to content in this workspace.",
|
||||
"emptyAssets": "No workspace assets have been linked yet.",
|
||||
"openDrive": "Open in Drive",
|
||||
"pendingTitle": "Management UI pending",
|
||||
"pendingDescription": "The navigation and page entry point are in place. Next step is wiring actual Drive sync, listing, filters, and asset actions."
|
||||
},
|
||||
|
||||
@@ -504,7 +504,19 @@
|
||||
"title": "Connexions",
|
||||
"description": "Connecteurs et regles de donnees au niveau de l'organisation.",
|
||||
"placeholderTitle": "Aucune connexion d'organisation configuree",
|
||||
"placeholderText": "Les flux d'autorisation des connecteurs sont volontairement hors portee de cette interface."
|
||||
"placeholderText": "Les flux d'autorisation des connecteurs sont volontairement hors portee de cette interface.",
|
||||
"googleDrive": {
|
||||
"title": "DAM Google Drive",
|
||||
"description": "Utilisez un dossier Drive de l'organisation comme stockage de la bibliotheque media. Les medias sont organises par slug d'espace.",
|
||||
"enabled": "Utiliser Google Drive comme stockage DAM",
|
||||
"rootFolderName": "Nom du dossier racine",
|
||||
"rootFolderId": "ID du dossier racine",
|
||||
"rootFolderUrl": "URL du dossier racine",
|
||||
"save": "Enregistrer le DAM Google Drive",
|
||||
"saved": "Configuration DAM Google Drive enregistree.",
|
||||
"required": "Le nom, l'ID et l'URL du dossier racine sont requis quand le DAM Google Drive est active.",
|
||||
"saveFailed": "La configuration DAM Google Drive n'a pas pu etre enregistree."
|
||||
}
|
||||
},
|
||||
"workspaces": {
|
||||
"title": "Espaces",
|
||||
@@ -1120,6 +1132,8 @@
|
||||
},
|
||||
"fields": {
|
||||
"name": "Nom de l'espace",
|
||||
"slug": "Slug de l'espace",
|
||||
"slugHint": "Utilise comme nom de dossier sous la racine DAM de l'organisation.",
|
||||
"timeZone": "Fuseau horaire",
|
||||
"memberEmail": "Email du membre",
|
||||
"memberRole": "Rôle"
|
||||
@@ -1295,6 +1309,17 @@
|
||||
"organizeLibrary": "Reviser, etiqueter et reutiliser les medias depuis un seul endroit au niveau de l'espace."
|
||||
},
|
||||
"statusLabel": "Statut",
|
||||
"loading": "Chargement de la bibliotheque media...",
|
||||
"notConfigured": "DAM Google Drive non configure",
|
||||
"notConfiguredDescription": "Configurez la connexion DAM Google Drive de l'organisation avant de resoudre le dossier de cet espace.",
|
||||
"damRootTitle": "Dossier DAM",
|
||||
"workspaceSlug": "Slug de l'espace : {slug}",
|
||||
"configuredTitle": "Stockage Google Drive configure",
|
||||
"configuredDescription": "Socialize utilise la racine Drive de l'organisation et le slug de cet espace pour resoudre les metadonnees media.",
|
||||
"assetsTitle": "Ressources de l'espace",
|
||||
"assetsDescription": "Ressources Google Drive actuellement liees au contenu de cet espace.",
|
||||
"emptyAssets": "Aucune ressource d'espace n'a encore ete liee.",
|
||||
"openDrive": "Ouvrir dans Drive",
|
||||
"pendingTitle": "Interface de gestion en attente",
|
||||
"pendingDescription": "L'entree de navigation et la page sont en place. La prochaine etape est de brancher la vraie synchro Drive, le listing, les filtres et les actions sur les ressources."
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user