Suporte TypeScript
A API Rybená oferece suporte completo a TypeScript, permitindo que você aproveite os benefícios de tipagem estática, autocompletar e verificação de erros em tempo de compilação.
Definições de Tipos
Para usar TypeScript com a API Rybená, você precisa declarar os tipos globais. Adicione o seguinte ao seu arquivo de declarações de tipos (ex: src/types/rybena.d.ts):
declare global {
interface Window {
RybenaApi?: {
getInstance: () => RybenaApiInstance;
};
RybenaDOM?: {
getInstance: () => RybenaDOMInstance;
};
}
}
interface RybenaApiInstance {
// Player Control
openPlayer: () => void;
closePlayer: () => void;
setToNormalMode: () => void;
setToTransparentMode: () => void;
setCoordinates: (x: number, y: number) => void;
setSize: (value: number) => void;
// Translation Control
translate: (text: string) => void;
switchToLibras: () => void;
switchToVoz: () => void;
pause: () => void;
play: () => void;
stop: () => void;
setSpeed: (speed: number) => void;
// Event Handlers
handleLoaded: (callback: () => void) => void;
handleTranslate: (callback: () => void) => void;
isTranslating: () => boolean;
// Accessibility Features
// Visual Adjustments
toggleLineHeight: () => void;
nextLineHeight: () => void;
previousLineHeight: () => void;
startLineHeight: () => void;
stopLineHeight: () => void;
toggleLetterSpacing: () => void;
nextLetterSpacing: () => void;
previousLetterSpacing: () => void;
startLetterSpacing: () => void;
stopLetterSpacing: () => void;
toggleZoom: () => void;
nextZoom: () => void;
previousZoom: () => void;
startZoom: () => void;
stopZoom: () => void;
toggleDarkContrast: () => void;
startDarkContrast: () => void;
stopDarkContrast: () => void;
toggleLightContrast: () => void;
startLightContrast: () => void;
stopLightContrast: () => void;
toggleInvertedContrast: () => void;
startInvertedContrast: () => void;
stopInvertedContrast: () => void;
toggleHighSaturation: () => void;
startHighSaturation: () => void;
stopHighSaturation: () => void;
toggleLowSaturation: () => void;
startLowSaturation: () => void;
stopLowSaturation: () => void;
toggleNoSaturation: () => void;
startNoSaturation: () => void;
stopNoSaturation: () => void;
// Reading Aids
toggleCursorSize: () => void;
startCursorSize: () => void;
stopCursorSize: () => void;
toggleCursorGuide: () => void;
startCursorGuide: () => void;
stopCursorGuide: () => void;
toggleAmplifyCursor: () => void;
startAmplifyCursor: () => void;
stopAmplifyCursor: () => void;
toggleDislexiaFont: () => void;
startDislexiaFont: () => void;
stopDislexiaFont: () => void;
toggleLinkHighlight: () => void;
startLinkHighlight: () => void;
stopLinkHighlight: () => void;
toggleTitleHighlight: () => void;
startTitleHighlight: () => void;
stopTitleHighlight: () => void;
toggleReadingMask: () => void;
startReadingMask: () => void;
stopReadingMask: () => void;
toggleReadingMode: () => void;
startReadingMode: () => void;
stopReadingMode: () => void;
// Navigation & Other
toggleDictionary: () => void;
startDictionary: () => void;
stopDictionary: () => void;
toggleImageDescription: () => void;
startImageDescription: () => void;
stopImageDescription: () => void;
togglePauseAnimations: () => void;
startPauseAnimations: () => void;
stopPauseAnimations: () => void;
toggleKeyboardNavigation: () => void;
startKeyboardNavigation: () => void;
stopKeyboardNavigation: () => void;
}
interface RybenaDOMInstance {
getRybenaScripts: (mode?: string) => void;
}
export {};Tipos Auxiliares
Você pode criar tipos auxiliares para melhorar a segurança do seu código:
// Tipos de velocidade de tradução
type TranslationSpeed = 0.5 | 0.75 | 1.0 | 1.25 | 1.5;
// Tipos de modo de tradução
type TranslationMode = 'libras' | 'voz';
// Tipos de idioma
type Language = 'ptBR' | 'esES' | 'enUS';
// Tipos de contraste
type ContrastMode = 'dark' | 'light' | 'inverted';
// Tipos de saturação
type SaturationMode = 'high' | 'low' | 'none';
// Interface para configurações da Rybená
interface RybenaConfig {
language?: Language;
mode?: TranslationMode;
speed?: TranslationSpeed;
autoOpen?: boolean;
}
// Interface para preferências de acessibilidade
interface AccessibilityPreferences {
lineHeight?: boolean;
letterSpacing?: boolean;
zoom?: boolean;
contrast?: ContrastMode;
saturation?: SaturationMode;
cursorSize?: boolean;
cursorGuide?: boolean;
dislexiaFont?: boolean;
linkHighlight?: boolean;
titleHighlight?: boolean;
readingMask?: boolean;
readingMode?: boolean;
dictionary?: boolean;
imageDescription?: boolean;
pauseAnimations?: boolean;
keyboardNavigation?: boolean;
}Exemplos com TypeScript
Exemplo 1: Hook Customizado React
Este exemplo mostra como criar um hook customizado React com TypeScript:
import { useEffect, useState, useCallback, useRef } from 'react';
type TranslationMode = 'libras' | 'voz';
type TranslationSpeed = 0.5 | 0.75 | 1.0 | 1.25 | 1.5;
interface UseRybenaReturn {
isLoaded: boolean;
isTranslating: boolean;
translate: (text: string) => void;
pause: () => void;
play: () => void;
stop: () => void;
setMode: (mode: TranslationMode) => void;
setSpeed: (speed: TranslationSpeed) => void;
openPlayer: () => void;
closePlayer: () => void;
}
export function useRybena(): UseRybenaReturn {
const [isLoaded, setIsLoaded] = useState<boolean>(false);
const [isTranslating, setIsTranslating] = useState<boolean>(false);
const apiRef = useRef<RybenaApiInstance | null>(null);
useEffect(() => {
// Carrega o script Rybená
const script = document.createElement('script');
script.src = 'https://cdn.rybena.com.br/dom/master/latest/rybena.js?mode=api';
script.async = true;
script.onload = () => {
if (window.RybenaApi) {
const api = window.RybenaApi.getInstance();
apiRef.current = api;
api.handleLoaded(() => {
setIsLoaded(true);
api.openPlayer();
api.switchToLibras();
});
api.handleTranslate(() => {
setIsTranslating(false);
});
}
};
document.head.appendChild(script);
return () => {
if (document.head.contains(script)) {
document.head.removeChild(script);
}
};
}, []);
const translate = useCallback((text: string) => {
if (apiRef.current && text.trim()) {
setIsTranslating(true);
apiRef.current.translate(text);
}
}, []);
const pause = useCallback(() => {
if (apiRef.current) {
apiRef.current.pause();
}
}, []);
const play = useCallback(() => {
if (apiRef.current) {
apiRef.current.play();
}
}, []);
const stop = useCallback(() => {
if (apiRef.current) {
apiRef.current.stop();
setIsTranslating(false);
}
}, []);
const setMode = useCallback((mode: TranslationMode) => {
if (apiRef.current) {
if (mode === 'libras') {
apiRef.current.switchToLibras();
} else {
apiRef.current.switchToVoz();
}
}
}, []);
const setSpeed = useCallback((speed: TranslationSpeed) => {
if (apiRef.current) {
apiRef.current.setSpeed(speed);
}
}, []);
const openPlayer = useCallback(() => {
if (apiRef.current) {
apiRef.current.openPlayer();
}
}, []);
const closePlayer = useCallback(() => {
if (apiRef.current) {
apiRef.current.closePlayer();
}
}, []);
return {
isLoaded,
isTranslating,
translate,
pause,
play,
stop,
setMode,
setSpeed,
openPlayer,
closePlayer,
};
}Exemplo 2: Classe de Serviço
Este exemplo mostra como criar uma classe de serviço com TypeScript:
type TranslationMode = 'libras' | 'voz';
type TranslationSpeed = 0.5 | 0.75 | 1.0 | 1.25 | 1.5;
interface RybenaServiceConfig {
language?: 'ptBR' | 'esES' | 'enUS';
mode?: TranslationMode;
speed?: TranslationSpeed;
autoOpen?: boolean;
}
class RybenaService {
private api: RybenaApiInstance | null = null;
private isLoaded: boolean = false;
private config: RybenaServiceConfig;
constructor(config: RybenaServiceConfig = {}) {
this.config = {
language: config.language || 'ptBR',
mode: config.mode || 'libras',
speed: config.speed || 1.0,
autoOpen: config.autoOpen ?? true,
};
}
async initialize(): Promise<void> {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = `https://cdn.rybena.com.br/dom/master/latest/rybena.js?mode=api&lang=${this.config.language}`;
script.async = true;
script.onload = () => {
if (window.RybenaApi) {
this.api = window.RybenaApi.getInstance();
this.api.handleLoaded(() => {
this.isLoaded = true;
if (this.config.autoOpen) {
this.api.openPlayer();
}
if (this.config.mode === 'libras') {
this.api.switchToLibras();
} else {
this.api.switchToVoz();
}
if (this.config.speed) {
this.api.setSpeed(this.config.speed);
}
resolve();
});
} else {
reject(new Error('Failed to load Rybena API'));
}
};
script.onerror = () => {
reject(new Error('Failed to load Rybena script'));
};
document.head.appendChild(script);
});
}
translate(text: string): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
if (!text.trim()) {
throw new Error('Text cannot be empty');
}
this.api.translate(text);
}
pause(): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
this.api.pause();
}
play(): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
this.api.play();
}
stop(): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
this.api.stop();
}
setMode(mode: TranslationMode): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
if (mode === 'libras') {
this.api.switchToLibras();
} else {
this.api.switchToVoz();
}
}
setSpeed(speed: TranslationSpeed): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
this.api.setSpeed(speed);
}
isTranslating(): boolean {
if (!this.api || !this.isLoaded) {
return false;
}
return this.api.isTranslating();
}
openPlayer(): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
this.api.openPlayer();
}
closePlayer(): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
this.api.closePlayer();
}
onTranslateComplete(callback: () => void): void {
if (!this.api || !this.isLoaded) {
throw new Error('Rybena API not initialized');
}
this.api.handleTranslate(callback);
}
}
// Uso
const rybenaService = new RybenaService({
language: 'ptBR',
mode: 'libras',
speed: 1.0,
autoOpen: true,
});
await rybenaService.initialize();
rybenaService.translate('Olá, mundo!');Exemplo 3: Gerenciador de Acessibilidade
Este exemplo mostra como criar um gerenciador de acessibilidade com TypeScript:
interface AccessibilityPreferences {
lineHeight?: boolean;
letterSpacing?: boolean;
zoom?: boolean;
contrast?: 'dark' | 'light' | 'inverted';
saturation?: 'high' | 'low' | 'none';
cursorSize?: boolean;
cursorGuide?: boolean;
dislexiaFont?: boolean;
linkHighlight?: boolean;
titleHighlight?: boolean;
readingMask?: boolean;
readingMode?: boolean;
dictionary?: boolean;
imageDescription?: boolean;
pauseAnimations?: boolean;
keyboardNavigation?: boolean;
}
class AccessibilityManager {
private api: RybenaApiInstance | null = null;
private preferences: AccessibilityPreferences = {};
constructor(api: RybenaApiInstance) {
this.api = api;
this.loadPreferences();
}
private loadPreferences(): void {
const saved = localStorage.getItem('rybenaAccessibility');
if (saved) {
try {
this.preferences = JSON.parse(saved);
this.applyPreferences();
} catch (error) {
console.error('Failed to load accessibility preferences:', error);
}
}
}
private savePreferences(): void {
localStorage.setItem('rybenaAccessibility', JSON.stringify(this.preferences));
}
private applyPreferences(): void {
if (!this.api) return;
// Visual Adjustments
if (this.preferences.lineHeight) {
this.api.toggleLineHeight();
}
if (this.preferences.letterSpacing) {
this.api.toggleLetterSpacing();
}
if (this.preferences.zoom) {
this.api.toggleZoom();
}
if (this.preferences.contrast === 'dark') {
this.api.toggleDarkContrast();
} else if (this.preferences.contrast === 'light') {
this.api.toggleLightContrast();
} else if (this.preferences.contrast === 'inverted') {
this.api.toggleInvertedContrast();
}
if (this.preferences.saturation === 'high') {
this.api.toggleHighSaturation();
} else if (this.preferences.saturation === 'low') {
this.api.toggleLowSaturation();
} else if (this.preferences.saturation === 'none') {
this.api.toggleNoSaturation();
}
// Reading Aids
if (this.preferences.cursorSize) {
this.api.toggleCursorSize();
}
if (this.preferences.cursorGuide) {
this.api.toggleCursorGuide();
}
if (this.preferences.dislexiaFont) {
this.api.toggleDislexiaFont();
}
if (this.preferences.linkHighlight) {
this.api.toggleLinkHighlight();
}
if (this.preferences.titleHighlight) {
this.api.toggleTitleHighlight();
}
if (this.preferences.readingMask) {
this.api.toggleReadingMask();
}
if (this.preferences.readingMode) {
this.api.toggleReadingMode();
}
// Navigation & Other
if (this.preferences.dictionary) {
this.api.toggleDictionary();
}
if (this.preferences.imageDescription) {
this.api.toggleImageDescription();
}
if (this.preferences.pauseAnimations) {
this.api.togglePauseAnimations();
}
if (this.preferences.keyboardNavigation) {
this.api.toggleKeyboardNavigation();
}
}
toggleLineHeight(): void {
if (!this.api) return;
this.api.toggleLineHeight();
this.preferences.lineHeight = !this.preferences.lineHeight;
this.savePreferences();
}
toggleLetterSpacing(): void {
if (!this.api) return;
this.api.toggleLetterSpacing();
this.preferences.letterSpacing = !this.preferences.letterSpacing;
this.savePreferences();
}
toggleZoom(): void {
if (!this.api) return;
this.api.toggleZoom();
this.preferences.zoom = !this.preferences.zoom;
this.savePreferences();
}
setContrast(mode: 'dark' | 'light' | 'inverted' | null): void {
if (!this.api) return;
// Remove contraste anterior se existir
if (this.preferences.contrast) {
if (this.preferences.contrast === 'dark') {
this.api.toggleDarkContrast();
} else if (this.preferences.contrast === 'light') {
this.api.toggleLightContrast();
} else if (this.preferences.contrast === 'inverted') {
this.api.toggleInvertedContrast();
}
}
// Aplica novo contraste
if (mode === 'dark') {
this.api.toggleDarkContrast();
} else if (mode === 'light') {
this.api.toggleLightContrast();
} else if (mode === 'inverted') {
this.api.toggleInvertedContrast();
}
this.preferences.contrast = mode || undefined;
this.savePreferences();
}
setSaturation(mode: 'high' | 'low' | 'none' | null): void {
if (!this.api) return;
// Remove saturação anterior se existir
if (this.preferences.saturation) {
if (this.preferences.saturation === 'high') {
this.api.toggleHighSaturation();
} else if (this.preferences.saturation === 'low') {
this.api.toggleLowSaturation();
} else if (this.preferences.saturation === 'none') {
this.api.toggleNoSaturation();
}
}
// Aplica nova saturação
if (mode === 'high') {
this.api.toggleHighSaturation();
} else if (mode === 'low') {
this.api.toggleLowSaturation();
} else if (mode === 'none') {
this.api.toggleNoSaturation();
}
this.preferences.saturation = mode || undefined;
this.savePreferences();
}
getPreferences(): AccessibilityPreferences {
return { ...this.preferences };
}
}
// Uso
RybenaApi.getInstance().handleLoaded(() => {
const api = RybenaApi.getInstance();
const accessibilityManager = new AccessibilityManager(api);
// Ativa altura da linha
accessibilityManager.toggleLineHeight();
// Define contraste escuro
accessibilityManager.setContrast('dark');
// Obtém preferências atuais
const prefs = accessibilityManager.getPreferences();
console.log('Preferências:', prefs);
});Benefícios do TypeScript
Autocompletar: Com tipos definidos, seu IDE fornecerá autocompletar inteligente para todos os métodos da API Rybená.
Verificação de Erros: TypeScript detectará erros em tempo de compilação, como passar parâmetros incorretos ou chamar métodos que não existem.
Refatoração Segura: Com tipos, você pode refatorar seu código com confiança, sabendo que o TypeScript detectará qualquer quebra.
Documentação Viva: Os tipos servem como documentação, mostrando claramente quais parâmetros cada método aceita e quais valores retorna.
Boas Práticas
Sugestão: Crie tipos auxiliares para valores que têm um conjunto limitado de opções, como TranslationSpeed e TranslationMode.
Atenção: Sempre verifique se a API está carregada antes de chamar métodos. Use isLoaded ou verifique se api não é null.
Dica: Use classes de serviço ou hooks customizados para encapsular a lógica da API Rybená e tornar seu código mais reutilizável.
Sugestão: Salve as preferências do usuário em localStorage e use tipos para garantir que os dados sejam válidos.