1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-26 05:27:35 +02:00

adding not support video format warning

This commit is contained in:
Patrik J. Braun 2019-12-10 12:50:02 +01:00
parent 1685f4ed28
commit 00d3ae55e7
14 changed files with 249 additions and 79 deletions

View File

@ -29,11 +29,18 @@ export const SupportedFormats = {
Photos: <string[]>[], Photos: <string[]>[],
Videos: <string[]>[], Videos: <string[]>[],
MetaFiles: <string[]>[], MetaFiles: <string[]>[],
TranscodeNeed: {
Photos: <string[]>[],
Videos: <string[]>[],
}
} }
}; };
SupportedFormats.Photos = SupportedFormats.Photos.concat(SupportedFormats.TranscodeNeed.Photos); SupportedFormats.Photos = SupportedFormats.Photos.concat(SupportedFormats.TranscodeNeed.Photos);
SupportedFormats.Videos = SupportedFormats.Videos.concat(SupportedFormats.TranscodeNeed.Videos); SupportedFormats.Videos = SupportedFormats.Videos.concat(SupportedFormats.TranscodeNeed.Videos);
SupportedFormats.WithDots.Photos = SupportedFormats.Photos.map(f => '.' + f); SupportedFormats.WithDots.Photos = SupportedFormats.Photos.map(f => '.' + f);
SupportedFormats.WithDots.Videos = SupportedFormats.Videos.map(f => '.' + f); SupportedFormats.WithDots.Videos = SupportedFormats.Videos.map(f => '.' + f);
SupportedFormats.WithDots.MetaFiles = SupportedFormats.MetaFiles.map(f => '.' + f); SupportedFormats.WithDots.MetaFiles = SupportedFormats.MetaFiles.map(f => '.' + f);
SupportedFormats.WithDots.TranscodeNeed.Photos = SupportedFormats.TranscodeNeed.Photos.map(f => '.' + f);
SupportedFormats.WithDots.TranscodeNeed.Videos = SupportedFormats.TranscodeNeed.Videos.map(f => '.' + f);

View File

@ -11,6 +11,7 @@ export interface MediaDTO extends FileDTO {
metadata: MediaMetadata; metadata: MediaMetadata;
readyThumbnails: Array<number>; readyThumbnails: Array<number>;
readyIcon: boolean; readyIcon: boolean;
} }
@ -64,6 +65,17 @@ export module MediaDTO {
return false; return false;
}; };
export const isVideoTranscodingNeeded = (media: MediaDTO): boolean => {
const lower = media.name.toLowerCase();
for (const ext of SupportedFormats.WithDots.TranscodeNeed.Videos) {
if (lower.endsWith(ext)) {
return true;
}
}
return false;
};
export const getRotatedSize = (photo: MediaDTO): MediaDimension => { export const getRotatedSize = (photo: MediaDTO): MediaDimension => {
if (isSideWay(photo)) { if (isSideWay(photo)) {
// noinspection JSSuspiciousNameCombination // noinspection JSSuspiciousNameCombination

View File

@ -15,6 +15,14 @@ export class GridMedia extends Media {
return (<PhotoDTO>this.media).metadata.orientation || OrientationTypes.TOP_LEFT; return (<PhotoDTO>this.media).metadata.orientation || OrientationTypes.TOP_LEFT;
} }
get Video(): VideoDTO {
return <VideoDTO>this.media;
}
get Photo(): PhotoDTO {
return <PhotoDTO>this.media;
}
isPhoto(): boolean { isPhoto(): boolean {
return MediaDTO.isPhoto(this.media); return MediaDTO.isPhoto(this.media);
} }
@ -23,12 +31,7 @@ export class GridMedia extends Media {
return MediaDTO.isVideo(this.media); return MediaDTO.isVideo(this.media);
} }
get Video(): VideoDTO { public isVideoTranscodingNeeded() {
return <VideoDTO>this.media; return MediaDTO.isVideoTranscodingNeeded(this.media);
} }
get Photo(): PhotoDTO {
return <PhotoDTO>this.media;
}
} }

View File

@ -41,3 +41,4 @@ app-info-panel {
-moz-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out;
-webkit-transition: all 0.3s ease-in-out; -webkit-transition: all 0.3s ease-in-out;
} }

View File

@ -10,8 +10,28 @@
[zoom]="controls ? controls.Zoom : 1" [zoom]="controls ? controls.Zoom : 1"
[drag]="controls ? controls.drag : {x:0,y:0}" [drag]="controls ? controls.drag : {x:0,y:0}"
[windowAspect]="photoFrameDim.aspect" [windowAspect]="photoFrameDim.aspect"
(videoSourceError)="onVideoSourceError()"
#photo> #photo>
</app-gallery-lightbox-media> </app-gallery-lightbox-media>
<div class="container h-100 flex-column" *ngIf="videoSourceError && activePhoto">
<div class="row justify-content-center align-items-center h-100">
<div class="col-md-8 text-white text-center">
<span class="oi oi-warning h2 mr-2"></span><span class="h2" i18n>Error during loading the video.</span>
<br/>
<ng-container *ngIf="activePhoto.gridMedia.isVideoTranscodingNeeded()" i18n>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</ng-container>
<ng-container *ngFor="let ext of transcodeNeedVideos;let last = last">*.{{ext}}
<ng-container *ngIf="!last">,&nbsp;</ng-container>
</ng-container>
</div>
</div>
</div>
<app-lightbox-controls <app-lightbox-controls
*ngIf="isOpen()" *ngIf="isOpen()"
#controls #controls

View File

@ -14,6 +14,7 @@ import {QueryParams} from '../../../../../common/QueryParams';
import {GalleryService} from '../gallery.service'; import {GalleryService} from '../gallery.service';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {ControlsLightboxComponent} from './controls/controls.lightbox.gallery.component'; import {ControlsLightboxComponent} from './controls/controls.lightbox.gallery.component';
import {SupportedFormats} from '../../../../../common/SupportedFormats';
export enum LightboxStates { export enum LightboxStates {
Open = 1, Open = 1,
@ -41,6 +42,8 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
public infoPanelWidth = 0; public infoPanelWidth = 0;
public animating = false; public animating = false;
public photoFrameDim = {width: 1, height: 1, aspect: 1}; public photoFrameDim = {width: 1, height: 1, aspect: 1};
public videoSourceError = false;
public transcodeNeedVideos = SupportedFormats.TranscodeNeed.Videos;
private startPhotoDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0}; private startPhotoDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
private iPvisibilityTimer: number = null; private iPvisibilityTimer: number = null;
private visibilityTimer: number = null; private visibilityTimer: number = null;
@ -55,7 +58,6 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
route: null route: null
}; };
constructor(public fullScreenService: FullScreenService, constructor(public fullScreenService: FullScreenService,
private changeDetector: ChangeDetectorRef, private changeDetector: ChangeDetectorRef,
private overlayService: OverlayService, private overlayService: OverlayService,
@ -325,10 +327,14 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
public isVisible(): boolean { public isVisible(): boolean {
return this.status !== LightboxStates.Closed; return this.status !== LightboxStates.Closed;
} }
public isOpen(): boolean { public isOpen(): boolean {
return this.status === LightboxStates.Open; return this.status === LightboxStates.Open;
} }
onVideoSourceError() {
this.videoSourceError = true;
}
private updatePhotoFrameDim = () => { private updatePhotoFrameDim = () => {
this.photoFrameDim.width = Math.max(window.innerWidth - this.infoPanelWidth, 0); this.photoFrameDim.width = Math.max(window.innerWidth - this.infoPanelWidth, 0);
@ -389,6 +395,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) { if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) {
throw new Error('Can\'t find the media'); throw new Error('Can\'t find the media');
} }
this.videoSourceError = false;
this.activePhotoId = photoIndex; this.activePhotoId = photoIndex;
this.activePhoto = pcList[photoIndex]; this.activePhoto = pcList[photoIndex];
@ -407,7 +414,6 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
} }
private findPhotoComponent(media: MediaDTO): GalleryPhotoComponent { private findPhotoComponent(media: MediaDTO): GalleryPhotoComponent {
const galleryPhotoComponents = this.gridPhotoQL.toArray(); const galleryPhotoComponents = this.gridPhotoQL.toArray();
for (let i = 0; i < galleryPhotoComponents.length; i++) { for (let i = 0; i < galleryPhotoComponents.length; i++) {

View File

@ -6,4 +6,3 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }

View File

@ -22,9 +22,11 @@
(error)="onImageError()" (error)="onImageError()"
(timeupdate)="onVideoProgress()" (timeupdate)="onVideoProgress()"
#video> #video>
<source [src]="gridMedia.getBestFitMediaPath()"> <source [src]="gridMedia.getBestFitMediaPath()" (error)="onSourceError($event)">
Something went wrong.
</video> </video>
</div> </div>

View File

@ -1,8 +1,9 @@
import {Component, ElementRef, Input, Output, OnChanges, ViewChild} from '@angular/core'; import {Component, ElementRef, EventEmitter, Input, OnChanges, Output, ViewChild} from '@angular/core';
import {GridMedia} from '../../grid/GridMedia'; import {GridMedia} from '../../grid/GridMedia';
import {FixOrientationPipe} from '../../../../pipes/FixOrientationPipe'; import {FixOrientationPipe} from '../../../../pipes/FixOrientationPipe';
import {MediaDTO} from '../../../../../../common/entities/MediaDTO'; import {MediaDTO} from '../../../../../../common/entities/MediaDTO';
import {DomSanitizer, SafeStyle} from '@angular/platform-browser'; import {DomSanitizer, SafeStyle} from '@angular/platform-browser';
import {SupportedFormats} from '../../../../../../common/SupportedFormats';
@Component({ @Component({
selector: 'app-gallery-lightbox-media', selector: 'app-gallery-lightbox-media',
@ -16,18 +17,18 @@ export class GalleryLightboxMediaComponent implements OnChanges {
@Input() windowAspect = 1; @Input() windowAspect = 1;
@Input() zoom = 1; @Input() zoom = 1;
@Input() drag = {x: 0, y: 0}; @Input() drag = {x: 0, y: 0};
@Output() videoSourceError = new EventEmitter();
@ViewChild('video', {static: false}) video: ElementRef<HTMLVideoElement>; @ViewChild('video', {static: false}) video: ElementRef<HTMLVideoElement>;
prevGirdPhoto: GridMedia = null; prevGirdPhoto: GridMedia = null;
public imageSize = {width: 'auto', height: '100'}; public imageSize = {width: 'auto', height: '100'};
private imageLoaded = false;
public imageLoadFinished = false; public imageLoadFinished = false;
thumbnailSrc: string = null; thumbnailSrc: string = null;
photoSrc: string = null; photoSrc: string = null;
public transcodeNeedVideos = SupportedFormats.TranscodeNeed.Videos;
private mediaLoaded = false;
private videoProgress = 0; private videoProgress = 0;
constructor(public elementRef: ElementRef, constructor(public elementRef: ElementRef,
@ -40,35 +41,19 @@ export class GalleryLightboxMediaComponent implements OnChanges {
-50 / this.zoom + '% + ' + this.drag.y / this.zoom + 'px))'); -50 / this.zoom + '% + ' + this.drag.y / this.zoom + 'px))');
} }
ngOnChanges() {
if (this.prevGirdPhoto !== this.gridMedia) {
this.prevGirdPhoto = this.gridMedia;
this.thumbnailSrc = null;
this.photoSrc = null;
this.imageLoaded = false;
this.imageLoadFinished = false;
this.setImageSize();
}
if (this.thumbnailSrc == null && this.gridMedia && this.ThumbnailUrl !== null) {
FixOrientationPipe.transform(this.ThumbnailUrl, this.gridMedia.Orientation)
.then((src) => this.thumbnailSrc = src);
}
if (this.photoSrc == null && this.gridMedia && this.loadMedia) {
FixOrientationPipe.transform(this.gridMedia.getMediaPath(), this.gridMedia.Orientation)
.then((src) => this.photoSrc = src);
}
}
/** Video **/
private onVideoProgress() {
this.videoProgress = (100 / this.video.nativeElement.duration) * this.video.nativeElement.currentTime;
}
public get VideoProgress(): number { public get VideoProgress(): number {
return this.videoProgress; return this.videoProgress;
} }
public set VideoProgress(value: number) {
if (!this.video && value === null && typeof value === 'undefined') {
return;
}
this.video.nativeElement.currentTime = this.video.nativeElement.duration * (value / 100);
if (this.video.nativeElement.paused) {
this.video.nativeElement.play().catch(console.error);
}
}
public get VideoVolume(): number { public get VideoVolume(): number {
if (!this.video) { if (!this.video) {
@ -85,17 +70,6 @@ export class GalleryLightboxMediaComponent implements OnChanges {
this.video.nativeElement.volume = value; this.video.nativeElement.volume = value;
} }
public set VideoProgress(value: number) {
if (!this.video && value === null && typeof value === 'undefined') {
return;
}
this.video.nativeElement.currentTime = this.video.nativeElement.duration * (value / 100);
if (this.video.nativeElement.paused) {
this.video.nativeElement.play().catch(console.error);
}
}
public get Muted(): boolean { public get Muted(): boolean {
if (!this.video) { if (!this.video) {
return false; return false;
@ -103,6 +77,48 @@ export class GalleryLightboxMediaComponent implements OnChanges {
return this.video.nativeElement.muted; return this.video.nativeElement.muted;
} }
public get Paused(): boolean {
if (!this.video) {
return true;
}
return this.video.nativeElement.paused;
}
public get PhotoSrc(): string {
return this.gridMedia.getMediaPath();
}
private get ThumbnailUrl(): string {
if (this.gridMedia.isThumbnailAvailable() === true) {
return this.gridMedia.getThumbnailPath();
}
if (this.gridMedia.isReplacementThumbnailAvailable() === true) {
return this.gridMedia.getReplacementThumbnailPath();
}
return null;
}
ngOnChanges() {
if (this.prevGirdPhoto !== this.gridMedia) {
this.prevGirdPhoto = this.gridMedia;
this.thumbnailSrc = null;
this.photoSrc = null;
this.mediaLoaded = false;
this.imageLoadFinished = false;
this.setImageSize();
}
if (this.thumbnailSrc == null && this.gridMedia && this.ThumbnailUrl !== null) {
FixOrientationPipe.transform(this.ThumbnailUrl, this.gridMedia.Orientation)
.then((src) => this.thumbnailSrc = src);
}
if (this.photoSrc == null && this.gridMedia && this.loadMedia) {
FixOrientationPipe.transform(this.gridMedia.getMediaPath(), this.gridMedia.Orientation)
.then((src) => this.photoSrc = src);
}
}
public mute() { public mute() {
if (!this.video) { if (!this.video) {
return; return;
@ -122,48 +138,34 @@ export class GalleryLightboxMediaComponent implements OnChanges {
} }
} }
public get Paused(): boolean {
if (!this.video) {
return true;
}
return this.video.nativeElement.paused;
}
onImageError() { onImageError() {
// TODO:handle error // TODO:handle error
this.imageLoadFinished = true; this.imageLoadFinished = true;
console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getMediaPath()); console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getMediaPath());
} }
onImageLoad() { onImageLoad() {
this.imageLoadFinished = true; this.imageLoadFinished = true;
this.imageLoaded = true; this.mediaLoaded = true;
}
private get ThumbnailUrl(): string {
if (this.gridMedia.isThumbnailAvailable() === true) {
return this.gridMedia.getThumbnailPath();
}
if (this.gridMedia.isReplacementThumbnailAvailable() === true) {
return this.gridMedia.getReplacementThumbnailPath();
}
return null;
}
public get PhotoSrc(): string {
return this.gridMedia.getMediaPath();
} }
public showThumbnail(): boolean { public showThumbnail(): boolean {
return this.gridMedia && return this.gridMedia &&
!this.imageLoaded && !this.mediaLoaded &&
this.thumbnailSrc !== null && this.thumbnailSrc !== null &&
(this.gridMedia.isThumbnailAvailable() || this.gridMedia.isReplacementThumbnailAvailable()); (this.gridMedia.isThumbnailAvailable() || this.gridMedia.isReplacementThumbnailAvailable());
} }
onSourceError($event: any) {
this.mediaLoaded = false;
this.videoSourceError.emit();
}
/** Video **/
private onVideoProgress() {
this.videoProgress = (100 / this.video.nativeElement.duration) * this.video.nativeElement.currentTime;
}
private setImageSize() { private setImageSize() {
if (!this.gridMedia) { if (!this.gridMedia) {
return; return;
@ -180,6 +182,5 @@ export class GalleryLightboxMediaComponent implements OnChanges {
this.imageSize.width = '100'; this.imageSize.width = '100';
} }
} }
} }

View File

@ -180,6 +180,30 @@
</context-group> </context-group>
<target>Zoom in, key: '+'</target> <target>Zoom in, key: '+'</target>
</trans-unit> </trans-unit>
<trans-unit id="19654bbc78ebdc3916d5912faafcf20b4ff74069" datatype="html">
<source>Error during loading the video.</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">20</context>
</context-group>
<target>Error during loading the video.</target>
</trans-unit>
<trans-unit id="2e8bc19dbafcdd19549c005b6a5fbd506d16317b" datatype="html">
<source>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">22</context>
</context-group>
<target>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</target>
</trans-unit>
<trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html"> <trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html">
<source>Link availability</source> <source>Link availability</source>
<context-group purpose="location"> <context-group purpose="location">

View File

@ -180,6 +180,30 @@
</context-group> </context-group>
<target>Zoom avant, touche : '+'</target> <target>Zoom avant, touche : '+'</target>
</trans-unit> </trans-unit>
<trans-unit id="19654bbc78ebdc3916d5912faafcf20b4ff74069" datatype="html">
<source>Error during loading the video.</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">20</context>
</context-group>
<target>Error during loading the video.</target>
</trans-unit>
<trans-unit id="2e8bc19dbafcdd19549c005b6a5fbd506d16317b" datatype="html">
<source>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">22</context>
</context-group>
<target>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</target>
</trans-unit>
<trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html"> <trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html">
<source>Link availability</source> <source>Link availability</source>
<context-group purpose="location"> <context-group purpose="location">

View File

@ -180,6 +180,29 @@
</context-group> </context-group>
<target>Nagyítás, gyorsgomb: '+'</target> <target>Nagyítás, gyorsgomb: '+'</target>
</trans-unit> </trans-unit>
<trans-unit id="19654bbc78ebdc3916d5912faafcf20b4ff74069" datatype="html">
<source>Error during loading the video.</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">20</context>
</context-group>
<target>Hiba a videó betöltése közben.</target>
</trans-unit>
<trans-unit id="2e8bc19dbafcdd19549c005b6a5fbd506d16317b" datatype="html">
<source>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">22</context>
</context-group>
<target>
Valószínűleg a videó nincs átkonvertálva. Ezt a beállításokban megteheted.
Ezek a videó formátumok csak átkonvertálás után játszhatók le:
</target>
</trans-unit>
<trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html"> <trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html">
<source>Link availability</source> <source>Link availability</source>
<context-group purpose="location"> <context-group purpose="location">
@ -2480,4 +2503,4 @@
</trans-unit> </trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -180,6 +180,30 @@
</context-group> </context-group>
<target>Mărește, tasta: '+'</target> <target>Mărește, tasta: '+'</target>
</trans-unit> </trans-unit>
<trans-unit id="19654bbc78ebdc3916d5912faafcf20b4ff74069" datatype="html">
<source>Error during loading the video.</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">20</context>
</context-group>
<target>Error during loading the video.</target>
</trans-unit>
<trans-unit id="2e8bc19dbafcdd19549c005b6a5fbd506d16317b" datatype="html">
<source>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">22</context>
</context-group>
<target>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</target>
</trans-unit>
<trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html"> <trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html">
<source>Link availability</source> <source>Link availability</source>
<context-group purpose="location"> <context-group purpose="location">

View File

@ -180,6 +180,30 @@
</context-group> </context-group>
<target>Увеличить, клавиша: '+'</target> <target>Увеличить, клавиша: '+'</target>
</trans-unit> </trans-unit>
<trans-unit id="19654bbc78ebdc3916d5912faafcf20b4ff74069" datatype="html">
<source>Error during loading the video.</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">20</context>
</context-group>
<target>Error during loading the video.</target>
</trans-unit>
<trans-unit id="2e8bc19dbafcdd19549c005b6a5fbd506d16317b" datatype="html">
<source>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ui/gallery/lightbox/lightbox.gallery.component.html</context>
<context context-type="linenumber">22</context>
</context-group>
<target>
Most likely the video is not transcoded.
It can be done in the settings.
You need to transcode these videos to watch them online:
</target>
</trans-unit>
<trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html"> <trans-unit id="f3cda2936c4e70d5b920b2c243ec634222743ba1" datatype="html">
<source>Link availability</source> <source>Link availability</source>
<context-group purpose="location"> <context-group purpose="location">